Implement suspend, resume, reconcile image-update
.. and refactor. These are all amenable to the adapter refactoring that has served well so far. Signed-off-by: Michael Bridgen <michael@weave.works>
This commit is contained in:
@@ -17,7 +17,20 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
"github.com/spf13/cobra"
|
"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"
|
||||||
|
"k8s.io/client-go/util/retry"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reconcileCmd = &cobra.Command{
|
var reconcileCmd = &cobra.Command{
|
||||||
@@ -29,3 +42,101 @@ var reconcileCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(reconcileCmd)
|
rootCmd.AddCommand(reconcileCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reconcileCommand struct {
|
||||||
|
humanKind string
|
||||||
|
adapter reconcilable
|
||||||
|
}
|
||||||
|
|
||||||
|
type reconcilable interface {
|
||||||
|
adapter // to be able to load from the cluster
|
||||||
|
suspendable // to tell if it's suspended
|
||||||
|
|
||||||
|
// these are implemented by anything embedding metav1.ObjectMeta
|
||||||
|
GetAnnotations() map[string]string
|
||||||
|
SetAnnotations(map[string]string)
|
||||||
|
|
||||||
|
// this is usually implemented by GOTK types, since it's used for meta.SetResourceCondition
|
||||||
|
GetStatusConditions() *[]metav1.Condition
|
||||||
|
|
||||||
|
lastHandledReconcileRequest() string // what was the last handled reconcile request?
|
||||||
|
successMessage() string // what do you want to tell people when successfully reconciled?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reconcile reconcileCommand) run(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("%s name is required", reconcile.humanKind)
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = kubeClient.Get(ctx, namespacedName, reconcile.adapter.asRuntimeObject())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reconcile.adapter.isSuspended() {
|
||||||
|
return fmt.Errorf("resource is suspended")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("annotating %s %s in %s namespace", reconcile.humanKind, name, namespace)
|
||||||
|
if err := requestReconciliation(ctx, kubeClient, namespacedName, reconcile.adapter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("%s annotated", reconcile.humanKind)
|
||||||
|
|
||||||
|
lastHandledReconcileAt := reconcile.adapter.lastHandledReconcileRequest()
|
||||||
|
logger.Waitingf("waiting for %s reconciliation", reconcile.humanKind)
|
||||||
|
if err := wait.PollImmediate(pollInterval, timeout,
|
||||||
|
reconciliationHandled(ctx, kubeClient, namespacedName, reconcile.adapter, lastHandledReconcileAt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("%s reconciliation completed", reconcile.humanKind)
|
||||||
|
|
||||||
|
if apimeta.IsStatusConditionFalse(*reconcile.adapter.GetStatusConditions(), meta.ReadyCondition) {
|
||||||
|
return fmt.Errorf("%s reconciliation failed", reconcile.humanKind)
|
||||||
|
}
|
||||||
|
logger.Successf(reconcile.adapter.successMessage())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reconciliationHandled(ctx context.Context, kubeClient client.Client,
|
||||||
|
namespacedName types.NamespacedName, obj reconcilable, lastHandledReconcileAt string) wait.ConditionFunc {
|
||||||
|
return func() (bool, error) {
|
||||||
|
err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return obj.lastHandledReconcileRequest() != lastHandledReconcileAt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestReconciliation(ctx context.Context, kubeClient client.Client,
|
||||||
|
namespacedName types.NamespacedName, obj reconcilable) error {
|
||||||
|
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||||
|
if err := kubeClient.Get(ctx, namespacedName, obj.asRuntimeObject()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ann := obj.GetAnnotations(); ann == nil {
|
||||||
|
obj.SetAnnotations(map[string]string{
|
||||||
|
meta.ReconcileAtAnnotation: time.Now().Format(time.RFC3339Nano),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ann[meta.ReconcileAtAnnotation] = time.Now().Format(time.RFC3339Nano)
|
||||||
|
obj.SetAnnotations(ann)
|
||||||
|
}
|
||||||
|
return kubeClient.Update(ctx, obj.asRuntimeObject())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,19 +17,9 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"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"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
||||||
)
|
)
|
||||||
@@ -41,86 +31,20 @@ var reconcileImageRepositoryCmd = &cobra.Command{
|
|||||||
Example: ` # Trigger an scan for an existing image repository
|
Example: ` # Trigger an scan for an existing image repository
|
||||||
flux reconcile auto image-repository alpine
|
flux reconcile auto image-repository alpine
|
||||||
`,
|
`,
|
||||||
RunE: reconcileImageRepositoryRun,
|
RunE: reconcileCommand{
|
||||||
|
humanKind: imagev1.ImageRepositoryKind,
|
||||||
|
adapter: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reconcileAutoCmd.AddCommand(reconcileImageRepositoryCmd)
|
reconcileAutoCmd.AddCommand(reconcileImageRepositoryCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconcileImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
func (obj imageRepositoryAdapter) lastHandledReconcileRequest() string {
|
||||||
if len(args) < 1 {
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
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,
|
func (obj imageRepositoryAdapter) successMessage() string {
|
||||||
namespacedName types.NamespacedName, repo *imagev1.ImageRepository, lastHandledReconcileAt string) wait.ConditionFunc {
|
return fmt.Sprintf("scan fetched %d tags", obj.Status.LastScanResult.TagCount)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
62
cmd/flux/reconcile_auto_imageupdateauto.go
Normal file
62
cmd/flux/reconcile_auto_imageupdateauto.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
|
||||||
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
|
||||||
|
meta "github.com/fluxcd/pkg/apis/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var reconcileImageUpdateCmd = &cobra.Command{
|
||||||
|
Use: "image-update [name]",
|
||||||
|
Short: "Reconcile an ImageUpdateAutomation",
|
||||||
|
Long: `The reconcile auto image-update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.`,
|
||||||
|
Example: ` # Trigger an automation run for an existing image update automation
|
||||||
|
flux reconcile auto image-update latest-images
|
||||||
|
`,
|
||||||
|
RunE: reconcileCommand{
|
||||||
|
humanKind: autov1.ImageUpdateAutomationKind,
|
||||||
|
adapter: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
}.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reconcileAutoCmd.AddCommand(reconcileImageUpdateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) suspended() bool {
|
||||||
|
return obj.ImageUpdateAutomation.Spec.Suspend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) lastHandledReconcileRequest() string {
|
||||||
|
return obj.Status.GetLastHandledReconcileRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) successMessage() string {
|
||||||
|
if rc := apimeta.FindStatusCondition(obj.Status.Conditions, meta.ReadyCondition); rc != nil {
|
||||||
|
return rc.Message
|
||||||
|
}
|
||||||
|
if obj.Status.LastAutomationRunTime != nil {
|
||||||
|
return "last run " + obj.Status.LastAutomationRunTime.Time.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
return "automation not yet run"
|
||||||
|
}
|
||||||
@@ -17,7 +17,14 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var resumeCmd = &cobra.Command{
|
var resumeCmd = &cobra.Command{
|
||||||
@@ -29,3 +36,56 @@ var resumeCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(resumeCmd)
|
rootCmd.AddCommand(resumeCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type resumable interface {
|
||||||
|
adapter
|
||||||
|
statusable
|
||||||
|
setUnsuspended()
|
||||||
|
}
|
||||||
|
|
||||||
|
type resumeCommand struct {
|
||||||
|
kind string
|
||||||
|
humanKind string
|
||||||
|
object resumable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resume resumeCommand) run(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("%s name is required", resume.humanKind)
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = kubeClient.Get(ctx, namespacedName, resume.object.asRuntimeObject())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, name, namespace)
|
||||||
|
resume.object.setUnsuspended()
|
||||||
|
if err := kubeClient.Update(ctx, resume.object.asRuntimeObject()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("%s resumed", resume.humanKind)
|
||||||
|
|
||||||
|
logger.Waitingf("waiting for %s reconciliation", resume.kind)
|
||||||
|
if err := wait.PollImmediate(pollInterval, timeout,
|
||||||
|
isReady(ctx, kubeClient, namespacedName, resume.object)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("%s reconciliation completed", resume.kind)
|
||||||
|
logger.Successf(resume.object.successMessage())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,18 +17,7 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
||||||
)
|
)
|
||||||
@@ -40,75 +29,21 @@ var resumeImageRepositoryCmd = &cobra.Command{
|
|||||||
Example: ` # Resume reconciliation for an existing ImageRepository
|
Example: ` # Resume reconciliation for an existing ImageRepository
|
||||||
flux resume auto image-repository alpine
|
flux resume auto image-repository alpine
|
||||||
`,
|
`,
|
||||||
RunE: resumeImageRepositoryRun,
|
RunE: resumeCommand{
|
||||||
|
kind: imagev1.ImageRepositoryKind,
|
||||||
|
humanKind: "image repository",
|
||||||
|
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
resumeAutoCmd.AddCommand(resumeImageRepositoryCmd)
|
resumeAutoCmd.AddCommand(resumeImageRepositoryCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resumeImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
func (obj imageRepositoryAdapter) getObservedGeneration() int64 {
|
||||||
if len(args) < 1 {
|
return obj.ImageRepository.Status.ObservedGeneration
|
||||||
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,
|
func (obj imageRepositoryAdapter) setUnsuspended() {
|
||||||
namespacedName types.NamespacedName, repo *imagev1.ImageRepository) wait.ConditionFunc {
|
obj.ImageRepository.Spec.Suspend = false
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
49
cmd/flux/resume_auto_imageupdateauto.go
Normal file
49
cmd/flux/resume_auto_imageupdateauto.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var resumeImageUpdateCmd = &cobra.Command{
|
||||||
|
Use: "image-update [name]",
|
||||||
|
Short: "Resume a suspended ImageUpdateAutomation",
|
||||||
|
Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`,
|
||||||
|
Example: ` # Resume reconciliation for an existing ImageUpdateAutomation
|
||||||
|
flux resume auto image-update latest-images
|
||||||
|
`,
|
||||||
|
RunE: resumeCommand{
|
||||||
|
kind: autov1.ImageUpdateAutomationKind,
|
||||||
|
humanKind: "image update automation",
|
||||||
|
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
}.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resumeAutoCmd.AddCommand(resumeImageUpdateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) setUnsuspended() {
|
||||||
|
obj.ImageUpdateAutomation.Spec.Suspend = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj imageUpdateAutomationAdapter) getObservedGeneration() int64 {
|
||||||
|
return obj.ImageUpdateAutomation.Status.ObservedGeneration
|
||||||
|
}
|
||||||
67
cmd/flux/status.go
Normal file
67
cmd/flux/status.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
// statusable is used to see if a resource is considered ready in the usual way
|
||||||
|
type statusable interface {
|
||||||
|
adapter
|
||||||
|
// this is implemented by ObjectMeta
|
||||||
|
GetGeneration() int64
|
||||||
|
getObservedGeneration() int64
|
||||||
|
// this is usually implemented by GOTK API objects because it's used by pkg/apis/meta
|
||||||
|
GetStatusConditions() *[]metav1.Condition
|
||||||
|
// successMessage gives a short summary of the successful reconciliation
|
||||||
|
successMessage() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func isReady(ctx context.Context, kubeClient client.Client,
|
||||||
|
namespacedName types.NamespacedName, object statusable) wait.ConditionFunc {
|
||||||
|
return func() (bool, error) {
|
||||||
|
err := kubeClient.Get(ctx, namespacedName, object.asRuntimeObject())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm the state we are observing is for the current generation
|
||||||
|
if object.GetGeneration() != object.getObservedGeneration() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := apimeta.FindStatusCondition(*object.GetStatusConditions(), 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,13 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var suspendCmd = &cobra.Command{
|
var suspendCmd = &cobra.Command{
|
||||||
@@ -29,3 +35,47 @@ var suspendCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(suspendCmd)
|
rootCmd.AddCommand(suspendCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type suspendable interface {
|
||||||
|
adapter
|
||||||
|
isSuspended() bool
|
||||||
|
setSuspended()
|
||||||
|
}
|
||||||
|
|
||||||
|
type suspendCommand struct {
|
||||||
|
object suspendable
|
||||||
|
humanKind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("%s name is required", suspend.humanKind)
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
err = kubeClient.Get(ctx, namespacedName, suspend.object.asRuntimeObject())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("suspending %s %s in %s namespace", suspend.humanKind, name, namespace)
|
||||||
|
suspend.object.setSuspended()
|
||||||
|
if err := kubeClient.Update(ctx, suspend.object.asRuntimeObject()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Successf("%s suspended", suspend.humanKind)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,13 +17,8 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
|
||||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,43 +29,20 @@ var suspendImageRepositoryCmd = &cobra.Command{
|
|||||||
Example: ` # Suspend reconciliation for an existing ImageRepository
|
Example: ` # Suspend reconciliation for an existing ImageRepository
|
||||||
flux suspend auto image-repository alpine
|
flux suspend auto image-repository alpine
|
||||||
`,
|
`,
|
||||||
RunE: suspendImageRepositoryRun,
|
RunE: suspendCommand{
|
||||||
|
humanKind: "image repository",
|
||||||
|
object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
|
||||||
|
}.run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
suspendAutoCmd.AddCommand(suspendImageRepositoryCmd)
|
suspendAutoCmd.AddCommand(suspendImageRepositoryCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func suspendImageRepositoryRun(cmd *cobra.Command, args []string) error {
|
func (obj imageRepositoryAdapter) isSuspended() bool {
|
||||||
if len(args) < 1 {
|
return obj.ImageRepository.Spec.Suspend
|
||||||
return fmt.Errorf("image repository name is required")
|
}
|
||||||
}
|
|
||||||
name := args[0]
|
func (obj imageRepositoryAdapter) setSuspended() {
|
||||||
|
obj.ImageRepository.Spec.Suspend = true
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
48
cmd/flux/suspend_auto_imageupdateauto.go
Normal file
48
cmd/flux/suspend_auto_imageupdateauto.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
autov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var suspendImageUpdateCmd = &cobra.Command{
|
||||||
|
Use: "image-update [name]",
|
||||||
|
Short: "Suspend reconciliation of an ImageUpdateAutomation",
|
||||||
|
Long: "The suspend command disables the reconciliation of a ImageUpdateAutomation resource.",
|
||||||
|
Example: ` # Suspend reconciliation for an existing ImageUpdateAutomation
|
||||||
|
flux suspend auto image-update latest-images
|
||||||
|
`,
|
||||||
|
RunE: suspendCommand{
|
||||||
|
humanKind: "image update automation",
|
||||||
|
object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
|
||||||
|
}.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
suspendAutoCmd.AddCommand(suspendImageUpdateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (update imageUpdateAutomationAdapter) isSuspended() bool {
|
||||||
|
return update.ImageUpdateAutomation.Spec.Suspend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (update imageUpdateAutomationAdapter) setSuspended() {
|
||||||
|
update.ImageUpdateAutomation.Spec.Suspend = true
|
||||||
|
}
|
||||||
@@ -26,4 +26,5 @@ The reconcile auto sub-commands trigger a reconciliation of automation objects.
|
|||||||
|
|
||||||
* [flux reconcile](flux_reconcile.md) - Reconcile sources and resources
|
* [flux reconcile](flux_reconcile.md) - Reconcile sources and resources
|
||||||
* [flux reconcile auto image-repository](flux_reconcile_auto_image-repository.md) - Reconcile an ImageRepository
|
* [flux reconcile auto image-repository](flux_reconcile_auto_image-repository.md) - Reconcile an ImageRepository
|
||||||
|
* [flux reconcile auto image-update](flux_reconcile_auto_image-update.md) - Reconcile an ImageUpdateAutomation
|
||||||
|
|
||||||
|
|||||||
40
docs/cmd/flux_reconcile_auto_image-update.md
Normal file
40
docs/cmd/flux_reconcile_auto_image-update.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
## flux reconcile auto image-update
|
||||||
|
|
||||||
|
Reconcile an ImageUpdateAutomation
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
The reconcile auto image-update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.
|
||||||
|
|
||||||
|
```
|
||||||
|
flux reconcile auto image-update [name] [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
# Trigger an automation run for an existing image update automation
|
||||||
|
flux reconcile auto image-update latest-images
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for image-update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--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 reconcile auto](flux_reconcile_auto.md) - Reconcile automation objects
|
||||||
|
|
||||||
@@ -26,4 +26,5 @@ The resume auto sub-commands resume a suspended automation object.
|
|||||||
|
|
||||||
* [flux resume](flux_resume.md) - Resume suspended resources
|
* [flux resume](flux_resume.md) - Resume suspended resources
|
||||||
* [flux resume auto image-repository](flux_resume_auto_image-repository.md) - Resume a suspended ImageRepository
|
* [flux resume auto image-repository](flux_resume_auto_image-repository.md) - Resume a suspended ImageRepository
|
||||||
|
* [flux resume auto image-update](flux_resume_auto_image-update.md) - Resume a suspended ImageUpdateAutomation
|
||||||
|
|
||||||
|
|||||||
40
docs/cmd/flux_resume_auto_image-update.md
Normal file
40
docs/cmd/flux_resume_auto_image-update.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
## flux resume auto image-update
|
||||||
|
|
||||||
|
Resume a suspended ImageUpdateAutomation
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.
|
||||||
|
|
||||||
|
```
|
||||||
|
flux resume auto image-update [name] [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
# Resume reconciliation for an existing ImageUpdateAutomation
|
||||||
|
flux resume auto image-update latest-images
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for image-update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--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 resume auto](flux_resume_auto.md) - Resume automation objects
|
||||||
|
|
||||||
@@ -26,4 +26,5 @@ The suspend auto sub-commands suspend the reconciliation of an automation object
|
|||||||
|
|
||||||
* [flux suspend](flux_suspend.md) - Suspend resources
|
* [flux suspend](flux_suspend.md) - Suspend resources
|
||||||
* [flux suspend auto image-repository](flux_suspend_auto_image-repository.md) - Suspend reconciliation of an ImageRepository
|
* [flux suspend auto image-repository](flux_suspend_auto_image-repository.md) - Suspend reconciliation of an ImageRepository
|
||||||
|
* [flux suspend auto image-update](flux_suspend_auto_image-update.md) - Suspend reconciliation of an ImageUpdateAutomation
|
||||||
|
|
||||||
|
|||||||
40
docs/cmd/flux_suspend_auto_image-update.md
Normal file
40
docs/cmd/flux_suspend_auto_image-update.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
## flux suspend auto image-update
|
||||||
|
|
||||||
|
Suspend reconciliation of an ImageUpdateAutomation
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
The suspend command disables the reconciliation of a ImageUpdateAutomation resource.
|
||||||
|
|
||||||
|
```
|
||||||
|
flux suspend auto image-update [name] [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
# Suspend reconciliation for an existing ImageUpdateAutomation
|
||||||
|
flux suspend auto image-update latest-images
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for image-update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--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 suspend auto](flux_suspend_auto.md) - Suspend automation objects
|
||||||
|
|
||||||
Reference in New Issue
Block a user