Add ReconcileStrategy to HelmChart

This commit adds a `ReconcileStrategy` field to the `HelmChart` resource, which
allows defining when a new chart should be packaged and/or published if it
originates from a `Bucket` or `GitRepository` resource.

The two available strategies are:

- `ChartVersion`: creates a new artifact when the version of the Helm chart as
  defined in the `Chart.yaml` from the Source is different from the current
  version.
- `Revision`: creates a new artifact when the revision of the Source is
  different from the current revision.

For the `Revision` strategy, the (checksum part of the) revision of the
artifact the chart originatesfrom is added as SemVer metadata.

A chart from a `GitRepository` with Artifact revision
`main/f0faacd5164a875ebdbd9e3fab778f49c5aadbbc` and a chart with e.g. SemVer
`0.1.0` will be published as `0.1.0+f0faacd5164a875ebdbd9e3fab778f49c5aadbbc`.

A chart from a `Bucket` with Artifact revision
`f0faacd5164a875ebdbd9e3fab778f49c5aadbbc` and a chart with e.g. SemVer `0.1.0`
will be published as `0.1.0+f0faacd5164a875ebdbd9e3fab778f49c5aadbbc`.

Signed-off-by: Dylan Arbour <arbourd@users.noreply.github.com>
This commit is contained in:
Dylan Arbour 2021-03-12 23:46:32 -05:00 committed by Hidde Beydals
parent c4cc0a7ccf
commit 27c385b957
6 changed files with 131 additions and 3 deletions

View File

@ -45,6 +45,15 @@ type HelmChartSpec struct {
// +required
Interval metav1.Duration `json:"interval"`
// Determines what enables the creation of a new artifact. Valid values are
// ('ChartVersion', 'Revision').
// See the documentation of the values for an explanation on their behavior.
// Defaults to ChartVersion when omitted.
// +kubebuilder:validation:Enum=ChartVersion;Revision
// +kubebuilder:default:=ChartVersion
// +optional
ReconcileStrategy string `json:"reconcileStrategy,omitempty"`
// Alternative list of values files to use as the chart values (values.yaml
// is not included by default), expected to be a relative path in the SourceRef.
// Values files are merged in the order of this list with the last file overriding
@ -65,6 +74,14 @@ type HelmChartSpec struct {
Suspend bool `json:"suspend,omitempty"`
}
const (
// ReconcileStrategyChartVersion reconciles when the version of the Helm chart is different.
ReconcileStrategyChartVersion string = "ChartVersion"
// ReconcileStrategyRevision reconciles when the Revision of the source is different.
ReconcileStrategyRevision string = "Revision"
)
// LocalHelmChartSourceReference contains enough information to let you locate
// the typed referenced object at namespace level.
type LocalHelmChartSourceReference struct {

View File

@ -62,6 +62,13 @@ spec:
interval:
description: The interval at which to check the Source for updates.
type: string
reconcileStrategy:
default: ChartVersion
description: Determines what enables the creation of a new artifact. Valid values are ('ChartVersion', 'Revision'). See the documentation of the values for an explanation on their behavior. Defaults to ChartVersion when omitted.
enum:
- ChartVersion
- Revision
type: string
sourceRef:
description: The reference to the Source the chart is available at.
properties:

View File

@ -27,6 +27,7 @@ import (
"strings"
"time"
"github.com/Masterminds/semver/v3"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-logr/logr"
helmchart "helm.sh/helm/v3/pkg/chart"
@ -526,9 +527,29 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
v, err := semver.NewVersion(helmChart.Metadata.Version)
if err != nil {
err = fmt.Errorf("semver error: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
version := v.String()
if chart.Spec.ReconcileStrategy == sourcev1.ReconcileStrategyRevision {
// Isolate the commit SHA from GitRepository type artifacts by removing the branch/ prefix.
splitRev := strings.Split(artifact.Revision, "/")
v, err := v.SetMetadata(splitRev[len(splitRev)-1])
if err != nil {
err = fmt.Errorf("semver error: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
version = v.String()
helmChart.Metadata.Version = v.String()
}
// Return early if the revision is still the same as the current chart artifact
newArtifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), helmChart.Metadata.Version,
fmt.Sprintf("%s-%s.tgz", helmChart.Metadata.Name, helmChart.Metadata.Version))
newArtifact := r.Storage.NewArtifactFor(chart.Kind, chart.ObjectMeta.GetObjectMeta(), version,
fmt.Sprintf("%s-%s.tgz", helmChart.Metadata.Name, version))
if !force && apimeta.IsStatusConditionTrue(chart.Status.Conditions, meta.ReadyCondition) && chart.GetArtifact().HasRevision(newArtifact.Revision) {
if newArtifact.URL != artifact.URL {
r.Storage.SetArtifactURL(chart.GetArtifact())

View File

@ -709,7 +709,7 @@ var _ = Describe("HelmChartReconciler", func() {
err = f.Close()
Expect(err).NotTo(HaveOccurred())
_, err = wt.Commit("Chart version bump", &git.CommitOptions{
commit, err := wt.Commit("Chart version bump", &git.CommitOptions{
Author: &object.Signature{
Name: "John Doe",
Email: "john@example.com",
@ -735,6 +735,21 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeFalse())
When("Setting reconcileStrategy to Revision", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ReconcileStrategy = sourcev1.ReconcileStrategyRevision
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Revision != updated.Status.Artifact.Revision &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
Expect(got.Status.Artifact.Revision).To(ContainSubstring(updated.Status.Artifact.Revision))
Expect(got.Status.Artifact.Revision).To(ContainSubstring(commit.String()))
})
When("Setting valid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())

View File

@ -555,6 +555,21 @@ Kubernetes meta/v1.Duration
</tr>
<tr>
<td>
<code>reconcileStrategy</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Determines what enables reconciliation. Valid values are (&lsquo;ChartVersion&rsquo;,
&lsquo;Revision&rsquo;). See the documentation of the values for an explanation on their
behavior.
Defaults to ChartVersion when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>valuesFiles</code><br>
<em>
[]string
@ -1613,6 +1628,21 @@ Kubernetes meta/v1.Duration
</tr>
<tr>
<td>
<code>reconcileStrategy</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Determines what enables reconciliation. Valid values are (&lsquo;ChartVersion&rsquo;,
&lsquo;Revision&rsquo;). See the documentation of the values for an explanation on their
behavior.
Defaults to ChartVersion when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>valuesFiles</code><br>
<em>
[]string

View File

@ -28,6 +28,15 @@ type HelmChartSpec struct {
// +required
Interval metav1.Duration `json:"interval"`
// Determines what enables the creation of a new artifact. Valid values are
// ('ChartVersion', 'Revision').
// See the documentation of the values for an explanation on their behavior.
// Defaults to ChartVersion when omitted.
// +kubebuilder:validation:Enum=ChartVersion;Revision
// +kubebuilder:default:=ChartVersion
// +optional
ReconcileStrategy string `json:"reconcileStrategy,omitempty"`
// Alternative list of values files to use as the chart values (values.yaml
// is not included by default), expected to be a relative path in the SourceRef.
// Values files are merged in the order of this list with the last file overriding
@ -49,6 +58,18 @@ type HelmChartSpec struct {
}
```
### Reconciliation strategies
```go
const (
// ReconcileStrategyChartVersion creates a new chart artifact when the version of the Helm chart is different.
ReconcileStrategyChartVersion string = "ChartVersion"
// ReconcileStrategyRevision creates a new chart artifact when the Revision of the SourceRef is different.
ReconcileStrategyRevision string = "Revision"
)
```
### Reference types
```go
@ -230,6 +251,23 @@ spec:
- ./charts/podinfo/values-production.yaml
```
Reconcile with every change to the source revision:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmChart
metadata:
name: podinfo
namespace: default
spec:
chart: ./charts/podinfo
sourceRef:
name: podinfo
kind: GitRepository
interval: 10m
reconcileStrategy: Revision
```
## Status examples
Successful chart pull: