diff --git a/cmd/flux/create_source_helm.go b/cmd/flux/create_source_helm.go index d294aace..4b56f37a 100644 --- a/cmd/flux/create_source_helm.go +++ b/cmd/flux/create_source_helm.go @@ -44,23 +44,34 @@ var createSourceHelmCmd = &cobra.Command{ Short: "Create or update a HelmRepository source", Long: `The create source helm command generates a HelmRepository resource and waits for it to fetch the index. For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.`, - Example: ` # Create a source for a public Helm repository + Example: ` # Create a source for an HTTPS public Helm repository flux create source helm podinfo \ --url=https://stefanprodan.github.io/podinfo \ --interval=10m - # Create a source for a Helm repository using basic authentication + # Create a source for an HTTPS Helm repository using basic authentication flux create source helm podinfo \ --url=https://stefanprodan.github.io/podinfo \ --username=username \ --password=password - # Create a source for a Helm repository using TLS authentication + # Create a source for an HTTPS Helm repository using TLS authentication flux create source helm podinfo \ --url=https://stefanprodan.github.io/podinfo \ --cert-file=./cert.crt \ --key-file=./key.crt \ - --ca-file=./ca.crt`, + --ca-file=./ca.crt + + # Create a source for an OCI Helm repository + flux create source helm podinfo \ + --url=oci://ghcr.io/stefanprodan/charts/podinfo + --username=username \ + --password=password + + # Create a source for an OCI Helm repository using an existing secret with basic auth or dockerconfig credentials + flux create source helm podinfo \ + --url=oci://ghcr.io/stefanprodan/charts/podinfo + --secret-ref=docker-config`, RunE: createSourceHelmCmdRun, } @@ -84,7 +95,7 @@ func init() { createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.keyFile, "key-file", "", "TLS authentication key file path") createSourceHelmCmd.Flags().StringVar(&sourceHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path") - createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS or basic auth credentials") + createSourceHelmCmd.Flags().StringVarP(&sourceHelmArgs.secretRef, "secret-ref", "", "", "the name of an existing secret containing TLS, basic auth or docker-config credentials") createSourceHelmCmd.Flags().BoolVarP(&sourceHelmArgs.passCredentials, "pass-credentials", "", false, "pass credentials to all domains") createSourceCmd.AddCommand(createSourceHelmCmd) @@ -126,6 +137,14 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error { }, } + url, err := url.Parse(sourceHelmArgs.url) + if err != nil { + return fmt.Errorf("failed to parse URL: %w", err) + } + if url.Scheme == sourcev1.HelmRepositoryTypeOCI { + helmRepository.Spec.Type = sourcev1.HelmRepositoryTypeOCI + } + if createSourceArgs.fetchTimeout > 0 { helmRepository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout} } @@ -196,6 +215,11 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error { } logger.Successf("HelmRepository source reconciliation completed") + if helmRepository.Spec.Type == sourcev1.HelmRepositoryTypeOCI { + // OCI repos don't expose any artifact so we just return early here + return nil + } + if helmRepository.Status.Artifact == nil { return fmt.Errorf("HelmRepository source reconciliation completed but no artifact was found") } diff --git a/cmd/flux/create_source_helm_test.go b/cmd/flux/create_source_helm_test.go new file mode 100644 index 00000000..ceaa959f --- /dev/null +++ b/cmd/flux/create_source_helm_test.go @@ -0,0 +1,81 @@ +//go:build unit +// +build unit + +/* +Copyright 2022 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 TestCreateSourceHelm(t *testing.T) { + tests := []struct { + name string + args string + resultFile string + assertFunc string + }{ + { + name: "no args", + args: "create source helm", + resultFile: "name is required", + assertFunc: "assertError", + }, + { + name: "OCI repo", + args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --export", + resultFile: "./testdata/create_source_helm/oci.golden", + assertFunc: "assertGoldenTemplateFile", + }, + { + name: "OCI repo with Secret ref", + args: "create source helm podinfo --url=oci://ghcr.io/stefanprodan/charts/podinfo --interval 5m --secret-ref=creds --export", + resultFile: "./testdata/create_source_helm/oci-with-secret.golden", + assertFunc: "assertGoldenTemplateFile", + }, + { + name: "HTTPS repo", + args: "create source helm podinfo --url=https://stefanprodan.github.io/charts/podinfo --interval 5m --export", + resultFile: "./testdata/create_source_helm/https.golden", + assertFunc: "assertGoldenTemplateFile", + }, + } + + tmpl := map[string]string{ + "fluxns": allocateNamespace("flux-system"), + } + setup(t, tmpl) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var assert assertFunc + switch tt.assertFunc { + case "assertGoldenTemplateFile": + assert = assertGoldenTemplateFile(tt.resultFile, tmpl) + case "assertError": + assert = assertError(tt.resultFile) + } + + cmd := cmdTestCase{ + args: tt.args + " -n " + tmpl["fluxns"], + assert: assert, + } + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index 23d9d58f..50127de7 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -386,6 +386,7 @@ func executeCommand(cmd string) (string, error) { func resetCmdArgs() { createArgs = createFlags{} getArgs = GetFlags{} + sourceHelmArgs = sourceHelmFlags{} secretGitArgs = NewSecretGitFlags() } diff --git a/cmd/flux/testdata/create_source_helm/https.golden b/cmd/flux/testdata/create_source_helm/https.golden new file mode 100644 index 00000000..a0aad146 --- /dev/null +++ b/cmd/flux/testdata/create_source_helm/https.golden @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 5m0s + url: https://stefanprodan.github.io/charts/podinfo + diff --git a/cmd/flux/testdata/create_source_helm/oci-with-secret.golden b/cmd/flux/testdata/create_source_helm/oci-with-secret.golden new file mode 100644 index 00000000..0c182048 --- /dev/null +++ b/cmd/flux/testdata/create_source_helm/oci-with-secret.golden @@ -0,0 +1,13 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 5m0s + secretRef: + name: creds + type: oci + url: oci://ghcr.io/stefanprodan/charts/podinfo + diff --git a/cmd/flux/testdata/create_source_helm/oci.golden b/cmd/flux/testdata/create_source_helm/oci.golden new file mode 100644 index 00000000..af361810 --- /dev/null +++ b/cmd/flux/testdata/create_source_helm/oci.golden @@ -0,0 +1,11 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: podinfo + namespace: {{ .fluxns }} +spec: + interval: 5m0s + type: oci + url: oci://ghcr.io/stefanprodan/charts/podinfo + diff --git a/go.mod b/go.mod index 891c3a17..67ed89dd 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/fluxcd/pkg/ssh v0.4.1 github.com/fluxcd/pkg/untar v0.1.0 github.com/fluxcd/pkg/version v0.1.0 - github.com/fluxcd/source-controller/api v0.24.4 + github.com/fluxcd/source-controller/api v0.25.0 github.com/go-git/go-git/v5 v5.4.2 github.com/gonvenience/bunt v1.3.3 github.com/gonvenience/ytbx v1.4.4 diff --git a/go.sum b/go.sum index 3ed8efc1..b99be204 100644 --- a/go.sum +++ b/go.sum @@ -219,8 +219,8 @@ github.com/fluxcd/pkg/untar v0.1.0 h1:k97V/xV5hFrAkIkVPuv5AVhyxh1ZzzAKba/lbDfGo6 github.com/fluxcd/pkg/untar v0.1.0/go.mod h1:aGswNyzB1mlz/T/kpOS58mITBMxMKc9tlJBH037A2HY= github.com/fluxcd/pkg/version v0.1.0 h1:v+SmCanmCB5Tj2Cx9TXlj+kNRfPGbAvirkeqsp7ZEAQ= github.com/fluxcd/pkg/version v0.1.0/go.mod h1:V7Z/w8dxLQzv0FHqa5ox5TeyOd2zOd49EeuWFgnwyj4= -github.com/fluxcd/source-controller/api v0.24.4 h1:m54sS1rJlgJf5j9qDRgKLhbPJAnJ9dY+VrstPKj0aQo= -github.com/fluxcd/source-controller/api v0.24.4/go.mod h1:b0MmMPGE8gcpgSyGXe5m7see77tBW26eZrvGkkPstUs= +github.com/fluxcd/source-controller/api v0.25.0 h1:+uL+hQb/6h2MHuE9/Iq054TrDWF70puAuWBcoBrZK5M= +github.com/fluxcd/source-controller/api v0.25.0/go.mod h1:tuMrqHHpRt7mxdLeRXGIMtTKAMufLwLTm5uXkEOJWFw= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= diff --git a/manifests/bases/source-controller/kustomization.yaml b/manifests/bases/source-controller/kustomization.yaml index dbb9714e..72ce57dc 100644 --- a/manifests/bases/source-controller/kustomization.yaml +++ b/manifests/bases/source-controller/kustomization.yaml @@ -1,8 +1,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- https://github.com/fluxcd/source-controller/releases/download/v0.24.4/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v0.24.4/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v0.25.0/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v0.25.0/source-controller.deployment.yaml - account.yaml patchesJson6902: - target: