Add support for creating HR with .spec.ChartRef

Signed-off-by: Soule BA <bah.soule@gmail.com>
pull/4766/head
Soule BA 8 months ago
parent 0d0285ad09
commit 5feee5c73d
No known key found for this signature in database
GPG Key ID: 4D40965192802994

@ -36,6 +36,8 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/transform"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/fluxcd/flux2/v2/internal/flags"
"github.com/fluxcd/flux2/v2/internal/utils"
@ -104,7 +106,17 @@ var createHelmReleaseCmd = &cobra.Command{
--source=HelmRepository/podinfo \
--chart=podinfo \
--values=./values.yaml \
--export > podinfo-release.yaml`,
--export > podinfo-release.yaml
# Create a HelmRelease using a chart from a HelmChart resource
flux create hr podinfo \
--namespace=default \
--chart-ref=HelmChart/podinfo.flux-system \
# Create a HelmRelease using a chart from an OCIRepository resource
flux create hr podinfo \
--namespace=default \
--chart-ref=OCIRepository/podinfo.flux-system`,
RunE: createHelmReleaseCmdRun,
}
@ -114,6 +126,7 @@ type helmReleaseFlags struct {
dependsOn []string
chart string
chartVersion string
chartRef string
targetNamespace string
createNamespace bool
valuesFiles []string
@ -129,6 +142,8 @@ var helmReleaseArgs helmReleaseFlags
var supportedHelmReleaseValuesFromKinds = []string{"Secret", "ConfigMap"}
var supportedHelmReleaseReferenceKinds = []string{sourcev1b2.OCIRepositoryKind, sourcev1.HelmChartKind}
func init() {
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.name, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.source, "source", helmReleaseArgs.source.Description())
@ -144,14 +159,15 @@ func init() {
createHelmReleaseCmd.Flags().StringSliceVar(&helmReleaseArgs.valuesFrom, "values-from", nil, "a Kubernetes object reference that contains the values.yaml data key in the format '<kind>/<name>', where kind must be one of: (Secret,ConfigMap)")
createHelmReleaseCmd.Flags().Var(&helmReleaseArgs.crds, "crds", helmReleaseArgs.crds.Description())
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.kubeConfigSecretRef, "kubeconfig-secret-ref", "", "the name of the Kubernetes Secret that contains a key with the kubeconfig file for connecting to a remote cluster")
createHelmReleaseCmd.Flags().StringVar(&helmReleaseArgs.chartRef, "chart-ref", "", "the name of the HelmChart resource to use as source for the HelmRelease, in the format '<kind>/<name>.<namespace>', where kind must be one of: (OCIRepository,HelmChart)")
createCmd.AddCommand(createHelmReleaseCmd)
}
func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
name := args[0]
if helmReleaseArgs.chart == "" {
return fmt.Errorf("chart name or path is required")
if helmReleaseArgs.chart == "" && helmReleaseArgs.chartRef == "" {
return fmt.Errorf("chart or chart-ref is required")
}
sourceLabels, err := parseLabels()
@ -181,21 +197,40 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
Duration: createArgs.interval,
},
TargetNamespace: helmReleaseArgs.targetNamespace,
Suspend: false,
},
}
Chart: &helmv2.HelmChartTemplate{
Spec: helmv2.HelmChartTemplateSpec{
Chart: helmReleaseArgs.chart,
Version: helmReleaseArgs.chartVersion,
SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: helmReleaseArgs.source.Kind,
Name: helmReleaseArgs.source.Name,
Namespace: helmReleaseArgs.source.Namespace,
},
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
switch {
case helmReleaseArgs.chart != "":
helmRelease.Spec.Chart = &helmv2.HelmChartTemplate{
Spec: helmv2.HelmChartTemplateSpec{
Chart: helmReleaseArgs.chart,
Version: helmReleaseArgs.chartVersion,
SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: helmReleaseArgs.source.Kind,
Name: helmReleaseArgs.source.Name,
Namespace: helmReleaseArgs.source.Namespace,
},
ReconcileStrategy: helmReleaseArgs.reconcileStrategy,
},
Suspend: false,
},
}
if helmReleaseArgs.chartInterval != 0 {
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
Duration: helmReleaseArgs.chartInterval,
}
}
case helmReleaseArgs.chartRef != "":
kind, name, ns := utils.ParseObjectKindNameNamespace(helmReleaseArgs.chartRef)
if kind != sourcev1.HelmChartKind && kind != sourcev1b2.OCIRepositoryKind {
return fmt.Errorf("chart reference kind '%s' is not supported, must be one of: %s",
kind, strings.Join(supportedHelmReleaseReferenceKinds, ", "))
}
helmRelease.Spec.ChartRef = &helmv2.CrossNamespaceSourceReference{
Kind: kind,
Name: name,
Namespace: ns,
}
}
if helmReleaseArgs.kubeConfigSecretRef != "" {
@ -206,12 +241,6 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
}
}
if helmReleaseArgs.chartInterval != 0 {
helmRelease.Spec.Chart.Spec.Interval = &metav1.Duration{
Duration: helmReleaseArgs.chartInterval,
}
}
if helmReleaseArgs.createNamespace {
if helmRelease.Spec.Install == nil {
helmRelease.Spec.Install = &helmv2.Install{}

@ -0,0 +1,86 @@
//go:build unit
// +build unit
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "testing"
func TestCreateHelmRelease(t *testing.T) {
tmpl := map[string]string{
"fluxns": allocateNamespace("flux-system"),
}
setupHRSource(t, tmpl)
tests := []struct {
name string
args string
assert assertFunc
}{
{
name: "missing name",
args: "create helmrelease --export",
assert: assertError("name is required"),
},
{
name: "missing chart template and chartRef",
args: "create helmrelease podinfo --export",
assert: assertError("chart or chart-ref is required"),
},
{
name: "unknown source kind",
args: "create helmrelease podinfo --source foobar/podinfo --chart podinfo --export",
assert: assertError(`invalid argument "foobar/podinfo" for "--source" flag: source kind 'foobar' is not supported, must be one of: HelmRepository, GitRepository, Bucket`),
},
{
name: "unknown chart reference kind",
args: "create helmrelease podinfo --chart-ref foobar/podinfo --export",
assert: assertError(`chart reference kind 'foobar' is not supported, must be one of: OCIRepository, HelmChart`),
},
{
name: "basic helmrelease",
args: "create helmrelease podinfo --source Helmrepository/podinfo --chart podinfo --interval=1m0s --export",
assert: assertGoldenTemplateFile("testdata/create_hr/basic.yaml", tmpl),
},
{
name: "chart with OCIRepository source",
args: "create helmrelease podinfo --chart-ref OCIRepository/podinfo --interval=1m0s --export",
assert: assertGoldenTemplateFile("testdata/create_hr/or_basic.yaml", tmpl),
},
{
name: "chart with HelmChart source",
args: "create helmrelease podinfo --chart-ref HelmChart/podinfo --interval=1m0s --export",
assert: assertGoldenTemplateFile("testdata/create_hr/hc_basic.yaml", tmpl),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := cmdTestCase{
args: tt.args + " -n " + tmpl["fluxns"],
assert: tt.assert,
}
cmd.runTestCmd(t)
})
}
}
func setupHRSource(t *testing.T, tmpl map[string]string) {
t.Helper()
testEnv.CreateObjectFile("./testdata/create_hr/setup-source.yaml", tmpl, t)
}

@ -24,6 +24,7 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
)
var reconcileHrCmd = &cobra.Command{
@ -68,32 +69,46 @@ func (obj helmReleaseAdapter) reconcileSource() bool {
}
func (obj helmReleaseAdapter) getSource() (reconcileSource, types.NamespacedName) {
cmd := reconcileWithSourceCommand{
apiType: helmChartType,
object: helmChartAdapter{&sourcev1.HelmChart{}},
force: true,
}
var (
name string
ns string
ns string
)
switch {
case obj.Spec.Chart != &helmv2.HelmChart{}:
case obj.Spec.ChartRef != nil:
name, ns = obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace
if ns == "" {
ns = obj.Namespace
}
namespacedName := types.NamespacedName{
Name: name,
Namespace: ns,
}
if obj.Spec.ChartRef.Kind == sourcev1.HelmChartKind {
return reconcileWithSourceCommand{
apiType: helmChartType,
object: helmChartAdapter{&sourcev1.HelmChart{}},
force: true,
}, namespacedName
}
return reconcileCommand{
apiType: ociRepositoryType,
object: ociRepositoryAdapter{&sourcev1b2.OCIRepository{}},
}, namespacedName
default:
// default case assumes the HelmRelease is using a HelmChartTemplate
ns = obj.Spec.Chart.Spec.SourceRef.Namespace
if ns == "" {
ns = obj.Namespace
}
name = fmt.Sprintf("%s-%s", obj.Namespace, obj.Name)
case obj.Spec.ChartRef != nil:
ns = obj.Spec.ChartRef.Namespace
name = obj.Spec.ChartRef.Name
}
if ns == "" {
ns = obj.Namespace
}
return cmd, types.NamespacedName{
Name: name,
Namespace: ns,
return reconcileWithSourceCommand{
apiType: helmChartType,
object: helmChartAdapter{&sourcev1.HelmChart{}},
force: true,
}, types.NamespacedName{
Name: name,
Namespace: ns,
}
}
}

@ -0,0 +1,15 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
chart:
spec:
chart: podinfo
reconcileStrategy: ChartVersion
sourceRef:
kind: HelmRepository
name: podinfo
interval: 1m0s

@ -0,0 +1,11 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
chartRef:
kind: HelmChart
name: podinfo
interval: 1m0s

@ -0,0 +1,11 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
chartRef:
kind: OCIRepository
name: podinfo
interval: 1m0s

@ -0,0 +1,39 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .fluxns }}
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
interval: 1m0s
provider: generic
type: oci
url: oci://ghcr.io/stefanprodan/charts
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmChart
metadata:
name: podinfo
namespace: {{ .fluxns }}
spec:
interval: 1m0s
chart: podinfo
sourceRef:
kind: HelmRepository
name: podinfo
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 10m
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
tag: latest
Loading…
Cancel
Save