diff --git a/cmd/flux/create_auto_imagepolicy.go b/cmd/flux/create_auto_imagepolicy.go new file mode 100644 index 00000000..f87e537c --- /dev/null +++ b/cmd/flux/create_auto_imagepolicy.go @@ -0,0 +1,182 @@ +/* +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 ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + 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" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/fluxcd/flux2/internal/utils" + imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" + "github.com/fluxcd/pkg/apis/meta" +) + +var createAutoImagePolicyCmd = &cobra.Command{ + Use: "image-policy ", + Short: "Create or update an ImagePolicy object", + Long: `The create auto image-policy command generates an ImagePolicy resource. +An ImagePolicy object calculates a "latest image" given an image +repository and a policy, e.g., semver. + +The image that sorts highest according to the policy is recorded in +the status of the object.`, + RunE: createAutoImagePolicyRun} + +type imagePolicyFlags struct { + imageRef string + semver string +} + +var imagePolicyArgs = imagePolicyFlags{} + +func init() { + flags := createAutoImagePolicyCmd.Flags() + flags.StringVar(&imagePolicyArgs.imageRef, "image-ref", "", "the name of an image repository object") + flags.StringVar(&imagePolicyArgs.semver, "semver", "", "a semver range to apply to tags; e.g., '1.x'") + + createAutoCmd.AddCommand(createAutoImagePolicyCmd) +} + +func createAutoImagePolicyRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("ImagePolicy name is required") + } + objectName := args[0] + + if imagePolicyArgs.imageRef == "" { + return fmt.Errorf("the name of an ImageRepository in the namespace is required (--image-ref)") + } + + labels, err := parseLabels() + if err != nil { + return err + } + + var policy = imagev1.ImagePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: objectName, + Namespace: namespace, + Labels: labels, + }, + Spec: imagev1.ImagePolicySpec{ + ImageRepositoryRef: corev1.LocalObjectReference{ + Name: imagePolicyArgs.imageRef, + }, + }, + } + + switch { + case imagePolicyArgs.semver != "": + policy.Spec.Policy.SemVer = &imagev1.SemVerPolicy{ + Range: imagePolicyArgs.semver, + } + default: + return fmt.Errorf("a policy must be provided with --semver") + } + + if export { + return exportImagePolicy(policy) // defined with export command + } + + // I don't need these until attempting to upsert the object, but + // for consistency with other create commands, the following are + // given a chance to error out before reporting any progress. + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) + if err != nil { + return err + } + + logger.Generatef("generating ImagePolicy") + logger.Actionf("applying ImagePolicy") + namespacedName, err := upsertImagePolicy(ctx, kubeClient, &policy) + if err != nil { + return err + } + + logger.Waitingf("waiting for ImagePolicy reconciliation") + if err := wait.PollImmediate(pollInterval, timeout, + isImagePolicyReady(ctx, kubeClient, namespacedName, &policy)); err != nil { + return err + } + logger.Successf("ImagePolicy reconciliation completed") + + return nil +} + +func upsertImagePolicy(ctx context.Context, kubeClient client.Client, policy *imagev1.ImagePolicy) (types.NamespacedName, error) { + nsname := types.NamespacedName{ + Namespace: policy.GetNamespace(), + Name: policy.GetName(), + } + + var existing imagev1.ImagePolicy + existing.SetName(nsname.Name) + existing.SetNamespace(nsname.Namespace) + op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, &existing, func() error { + existing.Spec = policy.Spec + existing.SetLabels(policy.Labels) + return nil + }) + if err != nil { + return nsname, err + } + + switch op { + case controllerutil.OperationResultCreated: + logger.Successf("ImagePolicy created") + case controllerutil.OperationResultUpdated: + logger.Successf("ImagePolicy updated") + } + return nsname, nil +} + +func isImagePolicyReady(ctx context.Context, kubeClient client.Client, + namespacedName types.NamespacedName, policy *imagev1.ImagePolicy) wait.ConditionFunc { + return func() (bool, error) { + err := kubeClient.Get(ctx, namespacedName, policy) + if err != nil { + return false, err + } + + // Confirm the state we are observing is for the current generation + if policy.Generation != policy.Status.ObservedGeneration { + return false, nil + } + + if c := apimeta.FindStatusCondition(policy.Status.Conditions, meta.ReadyCondition); c != nil { + 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/export_auto_imagepolicy.go b/cmd/flux/export_auto_imagepolicy.go new file mode 100644 index 00000000..4bb376c8 --- /dev/null +++ b/cmd/flux/export_auto_imagepolicy.go @@ -0,0 +1,120 @@ +/* +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 ( + "context" + "fmt" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/flux2/internal/utils" + imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" +) + +var exportImagePolicyCmd = &cobra.Command{ + Use: "image-policy [name]", + Short: "Export ImagePolicy resources in YAML format", + Long: "The export image-policy command exports one or all ImagePolicy resources in YAML format.", + Example: ` # Export all ImagePolicy resources + flux export auto image-policy --all > image-policies.yaml + + # Export a specific policy + flux export auto image-policy alpine1x > alpine1x.yaml +`, + RunE: exportImagePolicyRun, +} + +func init() { + exportAutoCmd.AddCommand(exportImagePolicyCmd) +} + +func exportImagePolicyRun(cmd *cobra.Command, args []string) error { + if !exportAll && len(args) < 1 { + return fmt.Errorf("name is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfig, kubecontext) + if err != nil { + return err + } + + if exportAll { + var list imagev1.ImagePolicyList + err = kubeClient.List(ctx, &list, client.InNamespace(namespace)) + if err != nil { + return err + } + + if len(list.Items) == 0 { + logger.Failuref("no imagepolicy objects found in %s namespace", namespace) + return nil + } + + for _, policy := range list.Items { + if err := exportImagePolicy(policy); err != nil { + return err + } + } + } else { + name := args[0] + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + var policy imagev1.ImagePolicy + err = kubeClient.Get(ctx, namespacedName, &policy) + if err != nil { + return err + } + return exportImagePolicy(policy) + } + return nil +} + +func exportImagePolicy(policy imagev1.ImagePolicy) error { + gvk := imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind) + export := imagev1.ImagePolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: gvk.Kind, + APIVersion: gvk.GroupVersion().String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: policy.Name, + Namespace: policy.Namespace, + Labels: policy.Labels, + Annotations: policy.Annotations, + }, + Spec: policy.Spec, + } + + data, err := yaml.Marshal(export) + if err != nil { + return err + } + + fmt.Println("---") + fmt.Println(resourceToString(data)) + return nil +} diff --git a/docs/cmd/flux_create_auto.md b/docs/cmd/flux_create_auto.md index 8e474926..dd24e331 100644 --- a/docs/cmd/flux_create_auto.md +++ b/docs/cmd/flux_create_auto.md @@ -30,5 +30,6 @@ being available. ### SEE ALSO * [flux create](flux_create.md) - Create or update sources and resources +* [flux create auto image-policy](flux_create_auto_image-policy.md) - Create or update an ImagePolicy object * [flux create auto image-repository](flux_create_auto_image-repository.md) - Create or update an ImageRepository object diff --git a/docs/cmd/flux_create_auto_image-policy.md b/docs/cmd/flux_create_auto_image-policy.md new file mode 100644 index 00000000..4336309c --- /dev/null +++ b/docs/cmd/flux_create_auto_image-policy.md @@ -0,0 +1,42 @@ +## flux create auto image-policy + +Create or update an ImagePolicy object + +### Synopsis + +The create auto image-policy command generates an ImagePolicy resource. +An ImagePolicy object calculates a "latest image" given an image +repository and a policy, e.g., semver. + +The image that sorts highest according to the policy is recorded in +the status of the object. + +``` +flux create auto image-policy [flags] +``` + +### Options + +``` + -h, --help help for image-policy + --image-ref string the name of an image repository object + --semver string a semver range to apply to tags; e.g., '1.x' +``` + +### Options inherited from parent commands + +``` + --context string kubernetes context to use + --export export in YAML format to stdout + --interval duration source sync interval (default 1m0s) + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2) + -n, --namespace string the namespace scope for this operation (default "flux-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [flux create auto](flux_create_auto.md) - Create or update resources dealing with automation + diff --git a/docs/cmd/flux_export_auto.md b/docs/cmd/flux_export_auto.md index c3a4ec30..f709c831 100644 --- a/docs/cmd/flux_export_auto.md +++ b/docs/cmd/flux_export_auto.md @@ -26,5 +26,6 @@ The export auto sub-commands export automation object in YAML format. ### SEE ALSO * [flux export](flux_export.md) - Export resources in YAML format +* [flux export auto image-policy](flux_export_auto_image-policy.md) - Export ImagePolicy resources in YAML format * [flux export auto image-repository](flux_export_auto_image-repository.md) - Export ImageRepository resources in YAML format diff --git a/docs/cmd/flux_export_auto_image-policy.md b/docs/cmd/flux_export_auto_image-policy.md new file mode 100644 index 00000000..ff09023a --- /dev/null +++ b/docs/cmd/flux_export_auto_image-policy.md @@ -0,0 +1,44 @@ +## flux export auto image-policy + +Export ImagePolicy resources in YAML format + +### Synopsis + +The export image-policy command exports one or all ImagePolicy resources in YAML format. + +``` +flux export auto image-policy [name] [flags] +``` + +### Examples + +``` + # Export all ImagePolicy resources + flux export auto image-policy --all > image-policies.yaml + + # Export a Provider + flux export auto image-policy alpine1x > alpine1x.yaml + +``` + +### Options + +``` + -h, --help help for image-policy +``` + +### Options inherited from parent commands + +``` + --all select all resources + --context string kubernetes context to use + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + -n, --namespace string the namespace scope for this operation (default "flux-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [flux export auto](flux_export_auto.md) - Export automation objects +