diff --git a/Makefile b/Makefile index 88d6ebf4a..a8fed8872 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ SUDO := $(shell docker info > /dev/null 2> /dev/null || echo "sudo") TEST_FLAGS?= include docker/kubectl.version +include docker/helm.version # NB because this outputs absolute file names, you have to be careful # if you're testing out the Makefile with `-W` (pretend a file is @@ -49,7 +50,7 @@ build/.%.done: docker/Dockerfile.% touch $@ build/.flux.done: build/fluxd build/kubectl docker/ssh_config docker/kubeconfig docker/verify_known_hosts.sh -build/.helm-operator.done: build/helm-operator build/kubectl docker/ssh_config docker/verify_known_hosts.sh +build/.helm-operator.done: build/helm-operator build/kubectl build/helm docker/ssh_config docker/verify_known_hosts.sh docker/helm-repositories.yaml build/fluxd: $(FLUXD_DEPS) build/fluxd: cmd/fluxd/*.go @@ -64,9 +65,23 @@ build/kubectl: cache/kubectl-$(KUBECTL_VERSION) docker/kubectl.version strip $@ chmod a+x $@ +build/helm: cache/helm-$(HELM_VERSION) docker/helm.version + cp cache/helm-$(HELM_VERSION) $@ + strip $@ + chmod a+x $@ + cache/kubectl-$(KUBECTL_VERSION): mkdir -p cache curl -L -o $@ "https://storage.googleapis.com/kubernetes-release/release/$(KUBECTL_VERSION)/bin/linux/amd64/kubectl" + +cache/helm-$(HELM_VERSION): + mkdir -p cache + curl -L -o ./cache/helm-$(HELM_VERSION).tar.gz "https://storage.googleapis.com/kubernetes-helm/helm-v$(HELM_VERSION)-linux-amd64.tar.gz" + echo "$(HELM_CHECKSUM) ./cache/helm-$(HELM_VERSION).tar.gz" > ./cache/helm-$(HELM_VERSION).checksum + sha256sum -c ./cache/helm-$(HELM_VERSION).checksum + tar -C ./cache -xzf ./cache/helm-$(HELM_VERSION).tar.gz linux-amd64/helm + mv ./cache/linux-amd64/helm $@ + $(GOPATH)/bin/fluxctl: $(FLUXCTL_DEPS) $(GOPATH)/bin/fluxctl: ./cmd/fluxctl/*.go go install ./cmd/fluxctl diff --git a/cmd/helm-operator/main.go b/cmd/helm-operator/main.go index da4443be7..aee1745b5 100644 --- a/cmd/helm-operator/main.go +++ b/cmd/helm-operator/main.go @@ -51,6 +51,7 @@ var ( chartsSyncInterval *time.Duration chartsSyncTimeout *time.Duration logReleaseDiffs *bool + updateDependencies *bool gitURL *string gitBranch *string @@ -103,6 +104,7 @@ func init() { chartsSyncInterval = fs.Duration("charts-sync-interval", 3*time.Minute, "Interval at which to check for changed charts") chartsSyncTimeout = fs.Duration("charts-sync-timeout", 1*time.Minute, "Timeout when checking for changed charts") logReleaseDiffs = fs.Bool("log-release-diffs", false, "Log the diff when a chart release diverges; potentially insecure") + updateDependencies = fs.Bool("update-chart-deps", true, "Update chart dependencies before installing/upgrading a release") gitURL = fs.String("git-url", "", "URL of git repo with Helm Charts; e.g., git@github.com:weaveworks/flux-example") gitBranch = fs.String("git-branch", "master", "branch of git repo") @@ -214,6 +216,7 @@ func main() { releaseConfig := release.Config{ ChartsPath: *gitChartsPath, + UpdateDeps: *updateDependencies, } repoConfig := helmop.RepoConfig{ Repo: repo, diff --git a/docker/Dockerfile.helm-operator b/docker/Dockerfile.helm-operator index 7bbde9902..c9bcf595e 100644 --- a/docker/Dockerfile.helm-operator +++ b/docker/Dockerfile.helm-operator @@ -15,6 +15,8 @@ ADD ./verify_known_hosts.sh /home/flux/verify_known_hosts.sh RUN sh /home/flux/verify_known_hosts.sh /etc/ssh/ssh_known_hosts && rm /home/flux/verify_known_hosts.sh COPY ./kubectl /usr/local/bin/ +# The Helm client is included as a convenience for troubleshooting +COPY ./helm /usr/local/bin/ # These are pretty static LABEL maintainer="Weaveworks " \ @@ -32,6 +34,10 @@ LABEL maintainer="Weaveworks " \ ENTRYPOINT [ "/sbin/tini", "--", "helm-operator" ] +ENV HELM_HOME=/var/fluxd/helm +COPY ./helm-repositories.yaml /var/fluxd/helm/repository/repositories.yaml +RUN mkdir -p /var/fluxd/helm/repository/cache/ + COPY ./helm-operator /usr/local/bin/ ARG BUILD_DATE diff --git a/docker/helm-repositories.yaml b/docker/helm-repositories.yaml new file mode 100644 index 000000000..d3ddc7e42 --- /dev/null +++ b/docker/helm-repositories.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +repositories: +- caFile: "" + cache: /var/fluxd/helm/repository/cache/stable-index.yaml + certFile: "" + keyFile: "" + name: stable + password: "" + url: https://kubernetes-charts.storage.googleapis.com + username: "" diff --git a/docker/helm.version b/docker/helm.version new file mode 100644 index 000000000..428e8a1a5 --- /dev/null +++ b/docker/helm.version @@ -0,0 +1,6 @@ +# NB Helm clients will refuse to play with Tiller that is older. 2.8.2 +# is the first release that had checksums; but 2.9.1 appears the first +# that reliably supports authenticating against chart repos, so that +# wins. +HELM_VERSION=2.9.1 +HELM_CHECKSUM=56ae2d5d08c68d6e7400d462d6ed10c929effac929fedce18d2636a9b4e166ba diff --git a/integrations/helm/release/deps.go b/integrations/helm/release/deps.go new file mode 100644 index 000000000..2e018afbb --- /dev/null +++ b/integrations/helm/release/deps.go @@ -0,0 +1,46 @@ +package release + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) + +func updateDependencies(chartDir string) error { + var hasLockFile bool + + // We are going to use `helm dep build`, which tries to update the + // dependencies in charts/ by looking at the file + // `requirements.lock` in the chart directory. If the lockfile + // does not match what is specified in requirements.yaml, it will + // error out. + // + // If that file doesn't exist, `helm dep build` will fall back on + // `helm dep update`, which populates the charts/ directory _and_ + // creates the lockfile. So that it will have the same behaviour + // the next time it attempts a release, remove the lockfile if it + // was created by helm. + lockfilePath := filepath.Join(chartDir, "requirements.lock") + info, err := os.Stat(lockfilePath) + hasLockFile = (err == nil && !info.IsDir()) + if !hasLockFile { + defer os.Remove(lockfilePath) + } + + cmd := exec.Command("helm", "repo", "update") + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("could not update repo: %s", string(out)) + } + + cmd = exec.Command("helm", "dep", "build", ".") + cmd.Dir = chartDir + + out, err = cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("could not update dependencies in %s: %s", chartDir, string(out)) + } + + return nil +} diff --git a/integrations/helm/release/release.go b/integrations/helm/release/release.go index 9e59e5bb5..9d6eb93ec 100644 --- a/integrations/helm/release/release.go +++ b/integrations/helm/release/release.go @@ -30,6 +30,7 @@ const ( type Config struct { ChartsPath string + UpdateDeps bool } // Release contains clients needed to provide functionality related to helm releases @@ -147,6 +148,13 @@ func (r *Release) Install(repoDir, releaseName string, fhr ifv1.FluxHelmRelease, chartDir := filepath.Join(repoDir, r.config.ChartsPath, chartPath) + if r.config.UpdateDeps { + if err := updateDependencies(chartDir); err != nil { + r.logger.Log("error", "problem updating dependencies of chart", "releaseName", releaseName, "dir", chartDir, "err", err) + return nil, err + } + } + strVals, err := fhr.Spec.Values.YAML() if err != nil { r.logger.Log("error", fmt.Sprintf("Problem with supplied customizations for Chart release [%s]: %#v", releaseName, err))