diff --git a/pkg/apis/helm.fluxcd.io/v1/types.go b/pkg/apis/helm.fluxcd.io/v1/types.go index fa8a8baa2..cf2fd8789 100644 --- a/pkg/apis/helm.fluxcd.io/v1/types.go +++ b/pkg/apis/helm.fluxcd.io/v1/types.go @@ -8,12 +8,11 @@ import ( "github.com/fluxcd/flux/pkg/resource" "github.com/ghodss/yaml" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/helm/pkg/chartutil" - - "github.com/fluxcd/helm-operator/pkg/helm/v2" + "github.com/fluxcd/helm-operator/pkg/helm" + v2 "github.com/fluxcd/helm-operator/pkg/helm/v2" ) // +genclient @@ -280,7 +279,7 @@ const ( // FluxHelmValues embeds chartutil.Values so we can implement deepcopy on map[string]interface{} // +k8s:deepcopy-gen=false type HelmValues struct { - chartutil.Values `json:"values,omitempty"` + helm.Values `json:"values,omitempty"` } // DeepCopyInto implements deepcopy-gen method for use in generated code @@ -293,7 +292,7 @@ func (in *HelmValues) DeepCopyInto(out *HelmValues) { if err != nil { return } - var values chartutil.Values + var values helm.Values err = yaml.Unmarshal(b, &values) if err != nil { return diff --git a/pkg/helm/release.go b/pkg/helm/release.go index 831e1829b..69f55218c 100644 --- a/pkg/helm/release.go +++ b/pkg/helm/release.go @@ -47,6 +47,7 @@ type Chart struct { Name string Version string AppVersion string + Values Values Files []*File Templates []*File Dependencies []*Chart diff --git a/pkg/helm/v2/release.go b/pkg/helm/v2/release.go index a5d566c34..146e0333c 100644 --- a/pkg/helm/v2/release.go +++ b/pkg/helm/v2/release.go @@ -36,10 +36,12 @@ func chartToGenericChart(c *chart.Chart) *helm.Chart { if c == nil || c.Metadata == nil { return nil } + return &helm.Chart{ Name: c.Metadata.Name, Version: c.Metadata.Version, AppVersion: c.Metadata.AppVersion, + Values: valuesToGenericValues(c.Values), Files: filesToGenericFiles(c.Files), Templates: templatesToGenericFiles(c.Templates), Dependencies: dependenciesToGenericDependencies(c.Dependencies), diff --git a/pkg/helm/v3/release.go b/pkg/helm/v3/release.go index 099569e37..8f445cc04 100644 --- a/pkg/helm/v3/release.go +++ b/pkg/helm/v3/release.go @@ -33,6 +33,7 @@ func chartToGenericChart(c *chart.Chart) *helm.Chart { Name: c.Name(), Version: formatVersion(c), AppVersion: c.AppVersion(), + Values: c.Values, Files: filesToGenericFiles(c.Files), Templates: filesToGenericFiles(c.Templates), Dependencies: dependenciesToGenericDependencies(c.Dependencies()), diff --git a/pkg/helm/values.go b/pkg/helm/values.go new file mode 100644 index 000000000..5df60035d --- /dev/null +++ b/pkg/helm/values.go @@ -0,0 +1,29 @@ +package helm + +import ( + "crypto/sha256" + "encoding/hex" + + "github.com/ghodss/yaml" +) + +// Values represents a collection of (Helm) values. +// We define our own type to avoid working with two `chartutil` +// versions. +type Values map[string]interface{} + +// YAML encodes the values into YAML bytes. +func (v Values) YAML() ([]byte, error) { + b, err := yaml.Marshal(v) + return b, err +} + +// Checksum calculates and returns the SHA256 checksum of the YAML +// encoded values. +func (v Values) Checksum() string { + b, _ := v.YAML() + + hasher := sha256.New() + hasher.Write(b) + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/pkg/release/release.go b/pkg/release/release.go index 88033d2ca..4a9324ab4 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -305,7 +305,7 @@ func (r *Release) Uninstall(client helm.Client, hr *v1.HelmRelease) { // before running the dry-run release to determine if any undefined // mutations have occurred. func shouldSync(logger log.Logger, client helm.Client, hr *v1.HelmRelease, curRel *helm.Release, - chartPath string, values values, logDiffs bool) (bool, error) { + chartPath string, values helm.Values, logDiffs bool) (bool, error) { if curRel == nil { logger.Log("info", "no existing release", "action", "install") diff --git a/pkg/release/values.go b/pkg/release/values.go index 077bf93b8..03cfa1418 100644 --- a/pkg/release/values.go +++ b/pkg/release/values.go @@ -1,53 +1,30 @@ package release import ( - "crypto/sha256" - "encoding/hex" "fmt" "io/ioutil" "net/http" "net/url" "path/filepath" + v1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" + "github.com/fluxcd/helm-operator/pkg/helm" "github.com/ghodss/yaml" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1" ) -// values represents a collection of (Helm) values. -// We define our own type to avoid working with two `chartutil` -// versions. -type values map[string]interface{} - -// YAML encodes the values into YAML bytes. -func (v values) YAML() ([]byte, error) { - b, err := yaml.Marshal(v) - return b, err -} - -// Checksum calculates and returns the SHA256 checksum of the YAML -// encoded values. -func (v values) Checksum() string { - b, _ := v.YAML() - - hasher := sha256.New() - hasher.Write(b) - return hex.EncodeToString(hasher.Sum(nil)) -} - // values attempts to compose the final values for the given // `HelmRelease`. It returns the values as bytes and a checksum, // or an error in case anything went wrong. -func composeValues(coreV1Client corev1.CoreV1Interface, hr *v1.HelmRelease, chartPath string) (values, error) { - result := values{} +func composeValues(coreV1Client corev1.CoreV1Interface, hr *v1.HelmRelease, chartPath string) (helm.Values, error) { + result := helm.Values{} ns := hr.Namespace for _, v := range hr.GetValuesFromSources() { - var valueFile values + var valueFile helm.Values switch { case v.ConfigMapKeyRef != nil: diff --git a/test/e2e/15_upgrade.bats b/test/e2e/15_upgrade.bats index b4be6f6ec..2913b634e 100644 --- a/test/e2e/15_upgrade.bats +++ b/test/e2e/15_upgrade.bats @@ -50,6 +50,34 @@ function setup() { poll_until_equals 'podinfo-git HelmRelease revision matches' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" } +@test "Git values.yaml change causes upgrade" { + # Apply the HelmRelease fixtures + kubectl apply -f "$FIXTURES_DIR/releases/git.yaml" >&3 + + # Wait for it to be deployed + poll_until_equals 'podinfo-git HelmRelease' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" + + # Clone the charts repository + local clone_dir + clone_dir="$(mktemp -d)" + defer rm -rf "'$clone_dir'" + git clone -b master ssh://git@localhost/git-server/repos/cluster.git "$clone_dir" + cd "$clone_dir" + + # Make a values.yaml mutation in Git + sed -i 's%replicaCount: 1%replicaCount: 2%' charts/podinfo/values.yaml + git add charts/podinfo/values.yaml + git -c 'user.email=foo@bar.com' -c 'user.name=Foo' commit -m "Change replicaCount to 2" + + # Record new HEAD and push change + head_hash=$(git rev-list -n 1 HEAD) + git push >&3 + + # Assert change is rolled out + poll_until_equals 'podinfo-git HelmRelease chart update' "successfully cloned chart revision: $head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"ChartFetched\")].message}'" + poll_until_equals 'podinfo-git HelmRelease revision matches' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" +} + function teardown() { run_deferred