1
0
mirror of synced 2026-02-06 19:05:55 +00:00

Add subcommands for image-repository

This adds all the standard subcommands for the ImageRepository type.

Following `source`, I have put them under a namespace: `auto`,
referring to automation.

NB For `create` I use controllerutil.CreateOrUpdate, which looks to me
like a slightly more rounded version of the upsert* funcs.

Signed-off-by: Michael Bridgen <michael@weave.works>
This commit is contained in:
Michael Bridgen
2020-12-01 16:13:47 +00:00
parent 16f52610ab
commit b66bdec61a
38 changed files with 1756 additions and 10 deletions

39
cmd/flux/create_auto.go Normal file
View File

@@ -0,0 +1,39 @@
/*
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 (
"strings"
"github.com/spf13/cobra"
)
const createAutoLong = `
The create auto sub-cmmands works with automation objects; that is,
object controlling updates to git based on e.g., new container images
being available.`
var createAutoCmd = &cobra.Command{
Use: "auto",
Aliases: []string{"automation"},
Short: "Create or update resources dealing with automation",
Long: strings.TrimSpace(createAutoLong),
}
func init() {
createCmd.AddCommand(createAutoCmd)
}

View File

@@ -0,0 +1,187 @@
/*
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"
"time"
"github.com/google/go-containerregistry/pkg/name"
"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 createAutoImageRepositoryCmd = &cobra.Command{
Use: "image-repository <name>",
Short: "Create or update an ImageRepository object",
Long: `The create auto image-repository command generates an ImageRepository resource.
An ImageRepository object specifies an image repository to scan.`,
RunE: createAutoImageRepositoryRun,
}
type imageRepoFlags struct {
image string
secretRef string
timeout time.Duration
}
var imageRepoArgs = imageRepoFlags{}
func init() {
flags := createAutoImageRepositoryCmd.Flags()
flags.StringVar(&imageRepoArgs.image, "image", "", "the image repository to scan; e.g., library/alpine")
flags.StringVar(&imageRepoArgs.secretRef, "secret-ref", "", "the name of a docker-registry secret to use for credentials")
// NB there is already a --timeout in the global flags, for
// controlling timeout on operations while e.g., creating objects.
flags.DurationVar(&imageRepoArgs.timeout, "scan-timeout", 0, "a timeout for scanning; this defaults to the interval if not set")
createAutoCmd.AddCommand(createAutoImageRepositoryCmd)
}
func createAutoImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("ImageRepository name is required")
}
objectName := args[0]
if imageRepoArgs.image == "" {
return fmt.Errorf("an image repository (--image) is required")
}
if _, err := name.NewRepository(imageRepoArgs.image); err != nil {
return fmt.Errorf("unable to parse image value: %w", err)
}
labels, err := parseLabels()
if err != nil {
return err
}
var repo = imagev1.ImageRepository{
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
Namespace: namespace,
Labels: labels,
},
Spec: imagev1.ImageRepositorySpec{
Image: imageRepoArgs.image,
Interval: metav1.Duration{Duration: interval},
},
}
if imageRepoArgs.timeout != 0 {
repo.Spec.Timeout = &metav1.Duration{Duration: imageRepoArgs.timeout}
}
if imageRepoArgs.secretRef != "" {
repo.Spec.SecretRef = &corev1.LocalObjectReference{
Name: imageRepoArgs.secretRef,
}
}
if export {
return exportImageRepo(repo) // 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 ImageRepository")
logger.Actionf("applying ImageRepository")
namespacedName, err := upsertImageRepository(ctx, kubeClient, &repo)
if err != nil {
return err
}
logger.Waitingf("waiting for ImageRepository reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isImageRepositoryReady(ctx, kubeClient, namespacedName, &repo)); err != nil {
return err
}
logger.Successf("ImageRepository reconciliation completed")
return nil
}
func upsertImageRepository(ctx context.Context, kubeClient client.Client, repo *imagev1.ImageRepository) (types.NamespacedName, error) {
nsname := types.NamespacedName{
Namespace: repo.GetNamespace(),
Name: repo.GetName(),
}
var existing imagev1.ImageRepository
existing.SetName(nsname.Name)
existing.SetNamespace(nsname.Namespace)
op, err := controllerutil.CreateOrUpdate(ctx, kubeClient, &existing, func() error {
existing.Spec = repo.Spec
existing.Labels = repo.Labels
return nil
})
if err != nil {
return nsname, err
}
switch op {
case controllerutil.OperationResultCreated:
logger.Successf("ImageRepository created")
case controllerutil.OperationResultUpdated:
logger.Successf("ImageRepository updated")
}
return nsname, nil
}
func isImageRepositoryReady(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, imageRepository *imagev1.ImageRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, imageRepository)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if imageRepository.Generation != imageRepository.Status.ObservedGeneration {
return false, nil
}
if c := apimeta.FindStatusCondition(imageRepository.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
}
}

31
cmd/flux/delete_auto.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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"
)
var deleteAutoCmd = &cobra.Command{
Use: "auto",
Short: "Delete automation objects",
Long: "The delete auto sub-commands delete automation objects.",
}
func init() {
deleteCmd.AddCommand(deleteAutoCmd)
}

View File

@@ -0,0 +1,88 @@
/*
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/manifoldco/promptui"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/internal/utils"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var deleteImageRepositoryCmd = &cobra.Command{
Use: "image-repository [name]",
Short: "Delete an ImageRepository object",
Long: "The delete auto image-repository command deletes the given ImageRepository from the cluster.",
Example: ` # Delete an image repository
flux delete auto image-repository alpine
`,
RunE: deleteImageRepositoryRun,
}
func init() {
deleteAutoCmd.AddCommand(deleteImageRepositoryCmd)
}
func deleteImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("image repository name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var repo imagev1.ImageRepository
err = kubeClient.Get(ctx, namespacedName, &repo)
if err != nil {
return err
}
if !deleteSilent {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete this image repository",
IsConfirm: true,
}
if _, err := prompt.Run(); err != nil {
return fmt.Errorf("aborting")
}
}
logger.Actionf("deleting image repository %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &repo)
if err != nil {
return err
}
logger.Successf("image repository deleted")
return nil
}

31
cmd/flux/export_auto.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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"
)
var exportAutoCmd = &cobra.Command{
Use: "auto",
Short: "Export automation objects",
Long: "The export auto sub-commands export automation object in YAML format.",
}
func init() {
exportCmd.AddCommand(exportAutoCmd)
}

View File

@@ -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 exportImageRepositoryCmd = &cobra.Command{
Use: "image-repository [name]",
Short: "Export ImageRepository resources in YAML format",
Long: "The export image-repository command exports one or all ImageRepository resources in YAML format.",
Example: ` # Export all ImageRepository resources
flux export auto image-repository --all > image-repositories.yaml
# Export a Provider
flux export auto image-repository alpine > alpine.yaml
`,
RunE: exportImageRepositoryCmdRun,
}
func init() {
exportAutoCmd.AddCommand(exportImageRepositoryCmd)
}
func exportImageRepositoryCmdRun(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.ImageRepositoryList
err = kubeClient.List(ctx, &list, client.InNamespace(namespace))
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no imagerepository objects found in %s namespace", namespace)
return nil
}
for _, imageRepo := range list.Items {
if err := exportImageRepo(imageRepo); err != nil {
return err
}
}
} else {
name := args[0]
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var imageRepo imagev1.ImageRepository
err = kubeClient.Get(ctx, namespacedName, &imageRepo)
if err != nil {
return err
}
return exportImageRepo(imageRepo)
}
return nil
}
func exportImageRepo(repo imagev1.ImageRepository) error {
gvk := imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)
export := imagev1.ImageRepository{
TypeMeta: metav1.TypeMeta{
Kind: gvk.Kind,
APIVersion: gvk.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: repo.Name,
Namespace: repo.Namespace,
Labels: repo.Labels,
Annotations: repo.Annotations,
},
Spec: repo.Spec,
}
data, err := yaml.Marshal(export)
if err != nil {
return err
}
fmt.Println("---")
fmt.Println(resourceToString(data))
return nil
}

31
cmd/flux/get_auto.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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"
)
var getAutoCmd = &cobra.Command{
Use: "auto",
Short: "Get automation statuses",
Long: "The get auto sub-commands print the statuses of the automation objects.",
}
func init() {
getCmd.AddCommand(getAutoCmd)
}

View File

@@ -0,0 +1,113 @@
/*
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"
"os"
"strconv"
"strings"
"time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var getImageRepositoryCmd = &cobra.Command{
Use: "image-repository",
Short: "Get ImageRepository statuses",
Long: "The get auto image-repository command prints the status of ImageRepository objects.",
Example: ` # List all image repositories and their status
flux get auto image-repository
# List image repositories from all namespaces
flux get auto image-repository --all-namespaces
`,
RunE: getImageRepositoryRun,
}
func init() {
getAutoCmd.AddCommand(getImageRepositoryCmd)
}
func getImageRepositoryRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
var listOpts []client.ListOption
if !allNamespaces {
listOpts = append(listOpts, client.InNamespace(namespace))
}
var list imagev1.ImageRepositoryList
err = kubeClient.List(ctx, &list, listOpts...)
if err != nil {
return err
}
if len(list.Items) == 0 {
logger.Failuref("no imagerepository objects found in %s namespace", namespace)
return nil
}
header := []string{"Name", "Ready", "Message", "Last scan", "Suspended"}
if allNamespaces {
header = append([]string{"Namespace"}, header...)
}
var rows [][]string
for _, repo := range list.Items {
var row []string
var lastScan string
if repo.Status.LastScanResult != nil {
lastScan = repo.Status.LastScanResult.ScanTime.Time.Format(time.RFC3339)
}
if c := apimeta.FindStatusCondition(repo.Status.Conditions, meta.ReadyCondition); c != nil {
row = []string{
repo.GetName(),
string(c.Status),
c.Message,
lastScan,
strings.Title(strconv.FormatBool(repo.Spec.Suspend)),
}
} else {
row = []string{
repo.GetName(),
string(metav1.ConditionFalse),
"waiting to be reconciled",
lastScan,
strings.Title(strconv.FormatBool(repo.Spec.Suspend)),
}
}
if allNamespaces {
row = append([]string{repo.Namespace}, row...)
}
rows = append(rows, row)
}
utils.PrintTable(os.Stdout, header, rows)
return nil
}

View File

@@ -0,0 +1,31 @@
/*
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"
)
var reconcileAutoCmd = &cobra.Command{
Use: "auto",
Short: "Reconcile automation objects",
Long: "The reconcile auto sub-commands trigger a reconciliation of automation objects.",
}
func init() {
reconcileCmd.AddCommand(reconcileAutoCmd)
}

View File

@@ -0,0 +1,126 @@
/*
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"
"time"
"github.com/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var reconcileImageRepositoryCmd = &cobra.Command{
Use: "image-repository [name]",
Short: "Reconcile an ImageRepository",
Long: `The reconcile auto image-repository command triggers a reconciliation of an ImageRepository resource and waits for it to finish.`,
Example: ` # Trigger an scan for an existing image repository
flux reconcile auto image-repository alpine
`,
RunE: reconcileImageRepositoryRun,
}
func init() {
reconcileAutoCmd.AddCommand(reconcileImageRepositoryCmd)
}
func reconcileImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var repo imagev1.ImageRepository
err = kubeClient.Get(ctx, namespacedName, &repo)
if err != nil {
return err
}
if repo.Spec.Suspend {
return fmt.Errorf("resource is suspended")
}
logger.Actionf("annotating ImageRepository %s in %s namespace", name, namespace)
if err := requestImageRepositoryReconciliation(ctx, kubeClient, namespacedName, &repo); err != nil {
return err
}
logger.Successf("ImageRepository annotated")
lastHandledReconcileAt := repo.Status.LastHandledReconcileAt
logger.Waitingf("waiting for ImageRepository reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
imageRepositoryReconciliationHandled(ctx, kubeClient, namespacedName, &repo, lastHandledReconcileAt)); err != nil {
return err
}
logger.Successf("ImageRepository reconciliation completed")
if apimeta.IsStatusConditionFalse(repo.Status.Conditions, meta.ReadyCondition) {
return fmt.Errorf("ImageRepository reconciliation failed")
}
logger.Successf("scan fetched %d tags", repo.Status.LastScanResult.TagCount)
return nil
}
func imageRepositoryReconciliationHandled(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, repo *imagev1.ImageRepository, lastHandledReconcileAt string) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, repo)
if err != nil {
return false, err
}
return repo.Status.LastHandledReconcileAt != lastHandledReconcileAt, nil
}
}
func requestImageRepositoryReconciliation(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, repo *imagev1.ImageRepository) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if err := kubeClient.Get(ctx, namespacedName, repo); err != nil {
return err
}
if repo.Annotations == nil {
repo.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
}
} else {
repo.Annotations[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
}
return kubeClient.Update(ctx, repo)
})
}

31
cmd/flux/resume_auto.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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"
)
var resumeAutoCmd = &cobra.Command{
Use: "auto",
Short: "Resume automation objects",
Long: "The resume auto sub-commands resume a suspended automation object.",
}
func init() {
resumeCmd.AddCommand(resumeAutoCmd)
}

View File

@@ -0,0 +1,114 @@
/*
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/fluxcd/flux2/internal/utils"
"github.com/fluxcd/pkg/apis/meta"
"github.com/spf13/cobra"
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"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var resumeImageRepositoryCmd = &cobra.Command{
Use: "image-repository [name]",
Short: "Resume a suspended ImageRepository",
Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`,
Example: ` # Resume reconciliation for an existing ImageRepository
flux resume auto image-repository alpine
`,
RunE: resumeImageRepositoryRun,
}
func init() {
resumeAutoCmd.AddCommand(resumeImageRepositoryCmd)
}
func resumeImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("source name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var repo imagev1.ImageRepository
err = kubeClient.Get(ctx, namespacedName, &repo)
if err != nil {
return err
}
logger.Actionf("resuming image repository %s in %s namespace", name, namespace)
repo.Spec.Suspend = false
if err := kubeClient.Update(ctx, &repo); err != nil {
return err
}
logger.Successf("image repository resumed")
logger.Waitingf("waiting for ImageRepository reconciliation")
if err := wait.PollImmediate(pollInterval, timeout,
isImageRepositoryResumed(ctx, kubeClient, namespacedName, &repo)); err != nil {
return err
}
logger.Successf("ImageRepository reconciliation completed")
logger.Successf("scanned %d tags", repo.Status.LastScanResult.TagCount)
return nil
}
func isImageRepositoryResumed(ctx context.Context, kubeClient client.Client,
namespacedName types.NamespacedName, repo *imagev1.ImageRepository) wait.ConditionFunc {
return func() (bool, error) {
err := kubeClient.Get(ctx, namespacedName, repo)
if err != nil {
return false, err
}
// Confirm the state we are observing is for the current generation
if repo.Generation != repo.Status.ObservedGeneration {
return false, nil
}
if c := apimeta.FindStatusCondition(repo.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
}
}

31
cmd/flux/suspend_auto.go Normal file
View File

@@ -0,0 +1,31 @@
/*
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"
)
var suspendAutoCmd = &cobra.Command{
Use: "auto",
Short: "Suspend automation objects",
Long: "The suspend auto sub-commands suspend the reconciliation of an automation object.",
}
func init() {
suspendCmd.AddCommand(suspendAutoCmd)
}

View File

@@ -0,0 +1,76 @@
/*
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"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/flux2/internal/utils"
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
var suspendImageRepositoryCmd = &cobra.Command{
Use: "image-repository [name]",
Short: "Suspend reconciliation of an ImageRepository",
Long: "The suspend command disables the reconciliation of a ImageRepository resource.",
Example: ` # Suspend reconciliation for an existing ImageRepository
flux suspend auto image-repository alpine
`,
RunE: suspendImageRepositoryRun,
}
func init() {
suspendAutoCmd.AddCommand(suspendImageRepositoryCmd)
}
func suspendImageRepositoryRun(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("image repository name is required")
}
name := args[0]
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
kubeClient, err := utils.KubeClient(kubeconfig, kubecontext)
if err != nil {
return err
}
namespacedName := types.NamespacedName{
Namespace: namespace,
Name: name,
}
var repository imagev1.ImageRepository
err = kubeClient.Get(ctx, namespacedName, &repository)
if err != nil {
return err
}
logger.Actionf("suspending image repository %s in %s namespace", name, namespace)
repository.Spec.Suspend = true
if err := kubeClient.Update(ctx, &repository); err != nil {
return err
}
logger.Successf("image repository suspended")
return nil
}