diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 14c38578a7..9f4fcb669d 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -168,6 +168,27 @@ jobs: - name: flux delete source git run: | /tmp/flux delete source git podinfo --silent + - name: flux oci + run: | + /tmp/flux create source oci podinfo-oci \ + --url ghcr.io/stefanprodan/manifests/podinfo \ + --tag 6.1.6 \ + --interval 10m + /tmp/flux create kustomization podinfo-oci \ + --source=OCIRepository/podinfo-oci \ + --path="./kustomize" \ + --prune=true \ + --interval=5m \ + --target-namespace=default \ + --wait=true \ + --health-check-timeout=3m + /tmp/flux reconcile source oci podinfo-oci + /tmp/flux suspend source oci podinfo-oci + /tmp/flux get sources oci + /tmp/flux resume source oci podinfo-oci + /tmp/flux export source oci podinfo-oci + /tmp/flux delete ks podinfo-oci --silent + /tmp/flux delete source oci podinfo-oci --silent - name: flux create tenant run: | /tmp/flux create tenant dev-team --with-namespace=apps diff --git a/cmd/flux/create_kustomization.go b/cmd/flux/create_kustomization.go index f808a99fea..26d1e3ea2c 100644 --- a/cmd/flux/create_kustomization.go +++ b/cmd/flux/create_kustomization.go @@ -68,6 +68,13 @@ var createKsCmd = &cobra.Command{ --prune=true \ --interval=5m + # Create a Kustomization resource that references an OCIRepository + flux create kustomization podinfo \ + --source=OCIRepository/podinfo \ + --target-namespace=default \ + --prune=true \ + --interval=5m + # Create a Kustomization resource that references a Bucket flux create kustomization secrets \ --source=Bucket/secrets \ diff --git a/cmd/flux/create_source_oci.go b/cmd/flux/create_source_oci.go new file mode 100644 index 0000000000..e1d26c4351 --- /dev/null +++ b/cmd/flux/create_source_oci.go @@ -0,0 +1,222 @@ +/* +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 ( + "context" + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/fluxcd/flux2/internal/utils" +) + +var createSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Create or update an OCIRepository", + Long: `The create source oci command generates an OCIRepository resource and waits for it to be ready.`, + Example: ` # Create an OCIRepository for a public container image + flux create source oci podinfo \ + --url=ghcr.io/stefanprodan/manifests/podinfo \ + --tag=6.1.6 \ + --interval=10m +`, + RunE: createSourceOCIRepositoryCmdRun, +} + +type sourceOCIRepositoryFlags struct { + url string + tag string + digest string + secretRef string + ignorePaths []string +} + +var sourceOCIRepositoryArgs = sourceOCIRepositoryFlags{} + +func init() { + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.url, "url", "", "the OCI repository URL") + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.tag, "tag", "", "the OCI artifact tag") + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.digest, "digest", "", "the OCI artifact digest") + createSourceOCIRepositoryCmd.Flags().StringVar(&sourceOCIRepositoryArgs.secretRef, "secret-ref", "", "the name of an existing secret containing credentials") + createSourceOCIRepositoryCmd.Flags().StringSliceVar(&sourceOCIRepositoryArgs.ignorePaths, "ignore-paths", nil, "set paths to ignore resources (can specify multiple paths with commas: path1,path2)") + + createSourceCmd.AddCommand(createSourceOCIRepositoryCmd) +} + +func createSourceOCIRepositoryCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + + if sourceOCIRepositoryArgs.url == "" { + return fmt.Errorf("url is required") + } + + if sourceOCIRepositoryArgs.tag == "" && sourceOCIRepositoryArgs.digest == "" { + return fmt.Errorf("--tag or --digest is required") + } + + sourceLabels, err := parseLabels() + if err != nil { + return err + } + + tmpDir, err := os.MkdirTemp("", name) + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + var ignorePaths *string + if len(sourceOCIRepositoryArgs.ignorePaths) > 0 { + ignorePathsStr := strings.Join(sourceOCIRepositoryArgs.ignorePaths, "\n") + ignorePaths = &ignorePathsStr + } + + repository := &sourcev1.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: *kubeconfigArgs.Namespace, + Labels: sourceLabels, + }, + Spec: sourcev1.OCIRepositorySpec{ + URL: sourceOCIRepositoryArgs.url, + Interval: metav1.Duration{ + Duration: createArgs.interval, + }, + Reference: &sourcev1.OCIRepositoryRef{}, + Ignore: ignorePaths, + }, + } + + if sourceOCIRepositoryArgs.tag != "" { + repository.Spec.Reference.Tag = sourceOCIRepositoryArgs.tag + } + if sourceOCIRepositoryArgs.digest != "" { + repository.Spec.Reference.Digest = sourceOCIRepositoryArgs.digest + } + + if createSourceArgs.fetchTimeout > 0 { + repository.Spec.Timeout = &metav1.Duration{Duration: createSourceArgs.fetchTimeout} + } + + if sourceOCIRepositoryArgs.secretRef != "" { + repository.Spec.SecretRef = &meta.LocalObjectReference{ + Name: sourceOCIRepositoryArgs.secretRef, + } + } + + if createArgs.export { + return printExport(exportOCIRepository(repository)) + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + logger.Actionf("applying OCIRepository") + namespacedName, err := upsertOCIRepository(ctx, kubeClient, repository) + if err != nil { + return err + } + + logger.Waitingf("waiting for OCIRepository reconciliation") + if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, + isOCIRepositoryReady(ctx, kubeClient, namespacedName, repository)); err != nil { + return err + } + logger.Successf("OCIRepository reconciliation completed") + + if repository.Status.Artifact == nil { + return fmt.Errorf("no artifact was found") + } + logger.Successf("fetched revision: %s", repository.Status.Artifact.Revision) + return nil +} + +func upsertOCIRepository(ctx context.Context, kubeClient client.Client, + ociRepository *sourcev1.OCIRepository) (types.NamespacedName, error) { + namespacedName := types.NamespacedName{ + Namespace: ociRepository.GetNamespace(), + Name: ociRepository.GetName(), + } + + var existing sourcev1.OCIRepository + err := kubeClient.Get(ctx, namespacedName, &existing) + if err != nil { + if errors.IsNotFound(err) { + if err := kubeClient.Create(ctx, ociRepository); err != nil { + return namespacedName, err + } else { + logger.Successf("OCIRepository created") + return namespacedName, nil + } + } + return namespacedName, err + } + + existing.Labels = ociRepository.Labels + existing.Spec = ociRepository.Spec + if err := kubeClient.Update(ctx, &existing); err != nil { + return namespacedName, err + } + ociRepository = &existing + logger.Successf("OCIRepository updated") + return namespacedName, nil +} + +func isOCIRepositoryReady(ctx context.Context, kubeClient client.Client, + namespacedName types.NamespacedName, ociRepository *sourcev1.OCIRepository) wait.ConditionFunc { + return func() (bool, error) { + err := kubeClient.Get(ctx, namespacedName, ociRepository) + if err != nil { + return false, err + } + + if c := conditions.Get(ociRepository, meta.ReadyCondition); c != nil { + // Confirm the Ready condition we are observing is for the + // current generation + if c.ObservedGeneration != ociRepository.GetGeneration() { + return false, nil + } + + // Further check the Status + switch c.Status { + case metav1.ConditionTrue: + return true, nil + case metav1.ConditionFalse: + return false, fmt.Errorf(c.Message) + } + } + return false, nil + } +} diff --git a/cmd/flux/delete_source_oci.go b/cmd/flux/delete_source_oci.go new file mode 100644 index 0000000000..1d641bbf51 --- /dev/null +++ b/cmd/flux/delete_source_oci.go @@ -0,0 +1,40 @@ +/* +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 ( + "github.com/spf13/cobra" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var deleteSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Delete a OCIRepository source", + Long: "The delete source oci command deletes the given OCIRepository from the cluster.", + Example: ` # Delete an OCIRepository + flux delete source oci podinfo`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: deleteCommand{ + apiType: ociRepositoryType, + object: universalAdapter{&sourcev1.OCIRepository{}}, + }.run, +} + +func init() { + deleteSourceCmd.AddCommand(deleteSourceOCIRepositoryCmd) +} diff --git a/cmd/flux/export_source_oci.go b/cmd/flux/export_source_oci.go new file mode 100644 index 0000000000..c6ea9c7703 --- /dev/null +++ b/cmd/flux/export_source_oci.go @@ -0,0 +1,92 @@ +/* +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 ( + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var exportSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Export OCIRepository sources in YAML format", + Long: "The export source oci command exports one or all OCIRepository sources in YAML format.", + Example: ` # Export all OCIRepository sources + flux export source oci --all > sources.yaml + + # Export a OCIRepository including the static credentials + flux export source oci my-app --with-credentials > source.yaml`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: exportWithSecretCommand{ + list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, + object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, + }.run, +} + +func init() { + exportSourceCmd.AddCommand(exportSourceOCIRepositoryCmd) +} + +func exportOCIRepository(source *sourcev1.OCIRepository) interface{} { + gvk := sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind) + export := sourcev1.OCIRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: gvk.Kind, + APIVersion: gvk.GroupVersion().String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: source.Name, + Namespace: source.Namespace, + Labels: source.Labels, + Annotations: source.Annotations, + }, + Spec: source.Spec, + } + return export +} + +func getOCIRepositorySecret(source *sourcev1.OCIRepository) *types.NamespacedName { + if source.Spec.SecretRef != nil { + namespacedName := types.NamespacedName{ + Namespace: source.Namespace, + Name: source.Spec.SecretRef.Name, + } + + return &namespacedName + } + + return nil +} + +func (ex ociRepositoryAdapter) secret() *types.NamespacedName { + return getOCIRepositorySecret(ex.OCIRepository) +} + +func (ex ociRepositoryListAdapter) secretItem(i int) *types.NamespacedName { + return getOCIRepositorySecret(&ex.OCIRepositoryList.Items[i]) +} + +func (ex ociRepositoryAdapter) export() interface{} { + return exportOCIRepository(ex.OCIRepository) +} + +func (ex ociRepositoryListAdapter) exportItem(i int) interface{} { + return exportOCIRepository(&ex.OCIRepositoryList.Items[i]) +} diff --git a/cmd/flux/get_source_all.go b/cmd/flux/get_source_all.go index b9b6255532..ac6560fe83 100644 --- a/cmd/flux/get_source_all.go +++ b/cmd/flux/get_source_all.go @@ -40,6 +40,10 @@ var getSourceAllCmd = &cobra.Command{ } var allSourceCmd = []getCommand{ + { + apiType: ociRepositoryType, + list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, + }, { apiType: bucketType, list: &bucketListAdapter{&sourcev1.BucketList{}}, diff --git a/cmd/flux/get_source_oci.go b/cmd/flux/get_source_oci.go new file mode 100644 index 0000000000..8f948d8122 --- /dev/null +++ b/cmd/flux/get_source_oci.go @@ -0,0 +1,98 @@ +/* +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 ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var getSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci", + Short: "Get OCIRepository status", + Long: "The get sources oci command prints the status of the OCIRepository sources.", + Example: ` # List all OCIRepositories and their status + flux get sources oci + + # List OCIRepositories from all namespaces + flux get sources oci --all-namespaces`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: func(cmd *cobra.Command, args []string) error { + get := getCommand{ + apiType: ociRepositoryType, + list: &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, + funcMap: make(typeMap), + } + + err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) { + o, ok := obj.(*sourcev1.OCIRepository) + if !ok { + return nil, fmt.Errorf("impossible to cast type %#v to OCIRepository", obj) + } + + sink := &ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{ + Items: []sourcev1.OCIRepository{ + *o, + }}} + return sink, nil + }) + + if err != nil { + return err + } + + if err := get.run(cmd, args); err != nil { + return err + } + + return nil + }, +} + +func init() { + getSourceCmd.AddCommand(getSourceOCIRepositoryCmd) +} + +func (a *ociRepositoryListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string { + item := a.Items[i] + var revision string + if item.GetArtifact() != nil { + revision = item.GetArtifact().Revision + } + status, msg := statusAndMessage(item.Status.Conditions) + return append(nameColumns(&item, includeNamespace, includeKind), + revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg) +} + +func (a ociRepositoryListAdapter) headers(includeNamespace bool) []string { + headers := []string{"Name", "Revision", "Suspended", "Ready", "Message"} + if includeNamespace { + headers = append([]string{"Namespace"}, headers...) + } + return headers +} + +func (a ociRepositoryListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool { + item := a.Items[i] + return statusMatches(conditionType, conditionStatus, item.Status.Conditions) +} diff --git a/cmd/flux/reconcile_source_oci.go b/cmd/flux/reconcile_source_oci.go new file mode 100644 index 0000000000..17df7174f8 --- /dev/null +++ b/cmd/flux/reconcile_source_oci.go @@ -0,0 +1,50 @@ +/* +Copyright 2020 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 ( + "fmt" + + "github.com/spf13/cobra" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var reconcileSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Reconcile an OCIRepository", + Long: `The reconcile source command triggers a reconciliation of an OCIRepository resource and waits for it to finish.`, + Example: ` # Trigger a reconciliation for an existing source + flux reconcile source oci podinfo`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: reconcileCommand{ + apiType: ociRepositoryType, + object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, + }.run, +} + +func init() { + reconcileSourceCmd.AddCommand(reconcileSourceOCIRepositoryCmd) +} + +func (obj ociRepositoryAdapter) lastHandledReconcileRequest() string { + return obj.Status.GetLastHandledReconcileRequest() +} + +func (obj ociRepositoryAdapter) successMessage() string { + return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) +} diff --git a/cmd/flux/resume_source_oci.go b/cmd/flux/resume_source_oci.go new file mode 100644 index 0000000000..42ad48ab1d --- /dev/null +++ b/cmd/flux/resume_source_oci.go @@ -0,0 +1,53 @@ +/* +Copyright 2020 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 ( + "github.com/spf13/cobra" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var resumeSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Resume a suspended OCIRepository", + Long: `The resume command marks a previously suspended OCIRepository resource for reconciliation and waits for it to finish.`, + Example: ` # Resume reconciliation for an existing OCIRepository + flux resume source oci podinfo`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: resumeCommand{ + apiType: ociRepositoryType, + object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, + list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, + }.run, +} + +func init() { + resumeSourceCmd.AddCommand(resumeSourceOCIRepositoryCmd) +} + +func (obj ociRepositoryAdapter) getObservedGeneration() int64 { + return obj.OCIRepository.Status.ObservedGeneration +} + +func (obj ociRepositoryAdapter) setUnsuspended() { + obj.OCIRepository.Spec.Suspend = false +} + +func (a ociRepositoryListAdapter) resumeItem(i int) resumable { + return &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]} +} diff --git a/cmd/flux/source.go b/cmd/flux/source.go index b6c9077cbe..4177c159a5 100644 --- a/cmd/flux/source.go +++ b/cmd/flux/source.go @@ -26,6 +26,40 @@ import ( // the various commands. The *List adapters implement len(), since // it's used in at least a couple of commands. +// sourcev1.ociRepository + +var ociRepositoryType = apiType{ + kind: sourcev1.OCIRepositoryKind, + humanKind: "source oci", + groupVersion: sourcev1.GroupVersion, +} + +type ociRepositoryAdapter struct { + *sourcev1.OCIRepository +} + +func (a ociRepositoryAdapter) asClientObject() client.Object { + return a.OCIRepository +} + +func (a ociRepositoryAdapter) deepCopyClientObject() client.Object { + return a.OCIRepository.DeepCopy() +} + +// sourcev1.OCIRepositoryList + +type ociRepositoryListAdapter struct { + *sourcev1.OCIRepositoryList +} + +func (a ociRepositoryListAdapter) asClientList() client.ObjectList { + return a.OCIRepositoryList +} + +func (a ociRepositoryListAdapter) len() int { + return len(a.OCIRepositoryList.Items) +} + // sourcev1.Bucket var bucketType = apiType{ diff --git a/cmd/flux/suspend_source_oci.go b/cmd/flux/suspend_source_oci.go new file mode 100644 index 0000000000..06ab4c6c35 --- /dev/null +++ b/cmd/flux/suspend_source_oci.go @@ -0,0 +1,53 @@ +/* +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 ( + "github.com/spf13/cobra" + + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +var suspendSourceOCIRepositoryCmd = &cobra.Command{ + Use: "oci [name]", + Short: "Suspend reconciliation of a OCIRepository", + Long: "The suspend command disables the reconciliation of an OCIRepository resource.", + Example: ` # Suspend reconciliation for an existing OCIRepository + flux suspend source oci podinfo`, + ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), + RunE: suspendCommand{ + apiType: ociRepositoryType, + object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, + list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, + }.run, +} + +func init() { + suspendSourceCmd.AddCommand(suspendSourceOCIRepositoryCmd) +} + +func (obj ociRepositoryAdapter) isSuspended() bool { + return obj.OCIRepository.Spec.Suspend +} + +func (obj ociRepositoryAdapter) setSuspended() { + obj.OCIRepository.Spec.Suspend = true +} + +func (a ociRepositoryListAdapter) item(i int) suspendable { + return &ociRepositoryAdapter{&a.OCIRepositoryList.Items[i]} +} diff --git a/internal/flags/kustomization_source.go b/internal/flags/kustomization_source.go index b506230d0a..f3455b25fb 100644 --- a/internal/flags/kustomization_source.go +++ b/internal/flags/kustomization_source.go @@ -25,7 +25,7 @@ import ( "github.com/fluxcd/flux2/internal/utils" ) -var supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind} +var supportedKustomizationSourceKinds = []string{sourcev1.OCIRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind} type KustomizationSource struct { Kind string