diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index fc70b552..09f83899 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -126,7 +126,7 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes opts := install.Options{ BaseURL: localManifests, Version: bootstrapArgs.version, - Namespace: rootArgs.namespace, + Namespace: namespace, Components: bootstrapComponents(), Registry: bootstrapArgs.registry, ImagePullSecret: bootstrapArgs.imagePullSecret, @@ -162,14 +162,14 @@ func applyInstallManifests(ctx context.Context, manifestPath string, components return fmt.Errorf("install failed") } - statusChecker := StatusChecker{} - err := statusChecker.New(time.Second, rootArgs.timeout) + statusChecker, err := NewStatusChecker(time.Second, time.Minute) if err != nil { - return fmt.Errorf("install failed with: %v", err) + return fmt.Errorf("install failed: %w", err) } - err = statusChecker.Assess(components...) - if err != nil { - return fmt.Errorf("install timed out waiting for rollout") + + logger.Waitingf("verifying installation") + if err := statusChecker.Assess(components...); err != nil { + return fmt.Errorf("install failed") } return nil diff --git a/cmd/flux/check.go b/cmd/flux/check.go index b6a46bfc..3404c396 100644 --- a/cmd/flux/check.go +++ b/cmd/flux/check.go @@ -46,8 +46,9 @@ the local environment is configured correctly and if the installed components ar } type checkFlags struct { - pre bool - components []string + pre bool + components []string + extraComponents []string } type kubectlVersion struct { @@ -61,6 +62,8 @@ func init() { "only run pre-installation checks") checkCmd.Flags().StringSliceVar(&checkArgs.components, "components", rootArgs.defaults.Components, "list of components, accepts comma-separated values") + checkCmd.Flags().StringSliceVar(&checkArgs.extraComponents, "components-extra", nil, + "list of components in addition to those supplied or defaulted, accepts comma-separated values") rootCmd.AddCommand(checkCmd) } @@ -173,21 +176,20 @@ func componentsCheck() bool { ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() - statusChecker := StatusChecker{} - err := statusChecker.New(time.Second, rootArgs.timeout) + statusChecker, err := NewStatusChecker(time.Second, 30*time.Second) if err != nil { return false } ok := true - for _, deployment := range checkArgs.components { - err = statusChecker.Assess(deployment) - if err != nil { - logger.Failuref("%s: timed out waiting for rollout", deployment) + deployments := append(checkArgs.components, checkArgs.extraComponents...) + for _, deployment := range deployments { + if err := statusChecker.Assess(deployment); err != nil { ok = false } else { - logger.Successf("%s: successfully rolled out", deployment) + logger.Successf("%s: healthy", deployment) } + kubectlArgs := []string{"-n", rootArgs.namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""} if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err == nil { logger.Actionf(strings.TrimPrefix(strings.TrimSuffix(output, "\""), "\"")) diff --git a/cmd/flux/install.go b/cmd/flux/install.go index 21952d04..b0d301eb 100644 --- a/cmd/flux/install.go +++ b/cmd/flux/install.go @@ -175,23 +175,16 @@ func installCmdRun(cmd *cobra.Command, args []string) error { if installDryRun { logger.Successf("install dry-run finished") return nil - } else { - logger.Successf("install completed") } - statusChecker := StatusChecker{} - err = statusChecker.New(time.Second, rootArgs.timeout) + statusChecker, err := NewStatusChecker(time.Second, time.Minute) if err != nil { - return fmt.Errorf("install failed with: %v", err) + return fmt.Errorf("install failed: %w", err) } logger.Waitingf("verifying installation") - for _, deployment := range components { - err := statusChecker.Assess(deployment) - if err != nil { - return fmt.Errorf("%s: install failed while rolling out deployment", deployment) - } - logger.Successf("%s ready", deployment) + if err := statusChecker.Assess(components...); err != nil { + return fmt.Errorf("install failed") } logger.Successf("install finished") diff --git a/cmd/flux/status.go b/cmd/flux/status.go index a200cba6..11e5b98f 100644 --- a/cmd/flux/status.go +++ b/cmd/flux/status.go @@ -19,9 +19,9 @@ package main import ( "context" "fmt" - "strings" "time" + appsv1 "k8s.io/api/apps/v1" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -53,6 +53,7 @@ type statusable interface { type StatusChecker struct { pollInterval time.Duration timeout time.Duration + client client.Client statusPoller *polling.StatusPoller } @@ -81,24 +82,26 @@ func isReady(ctx context.Context, kubeClient client.Client, } } -func (sc *StatusChecker) New(pollInterval time.Duration, timeout time.Duration) error { +func NewStatusChecker(pollInterval time.Duration, timeout time.Duration) (*StatusChecker, error) { kubeConfig, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext) if err != nil { - return err + return nil, err } restMapper, err := apiutil.NewDynamicRESTMapper(kubeConfig) if err != nil { - return err + return nil, err } client, err := client.New(kubeConfig, client.Options{Mapper: restMapper}) if err != nil { - return err + return nil, err } - statusPoller := polling.NewStatusPoller(client, restMapper) - sc.statusPoller = statusPoller - sc.pollInterval = pollInterval - sc.timeout = timeout - return err + + return &StatusChecker{ + pollInterval: pollInterval, + timeout: timeout, + client: client, + statusPoller: polling.NewStatusPoller(client, restMapper), + }, nil } func (sc *StatusChecker) Assess(components ...string) error { @@ -130,20 +133,19 @@ func (sc *StatusChecker) Assess(components ...string) error { ) <-done - if coll.Error != nil { - return coll.Error - } - - if ctx.Err() == context.DeadlineExceeded { - ids := []string{} + if coll.Error != nil || ctx.Err() == context.DeadlineExceeded { for _, rs := range coll.ResourceStatuses { if rs.Status != status.CurrentStatus { - id := sc.objMetadataToString(rs.Identifier) - ids = append(ids, id) + if !sc.deploymentExists(rs.Identifier) { + logger.Failuref("%s: deployment not found", rs.Identifier.Name) + } else { + logger.Failuref("%s: unhealthy (timed out waiting for rollout)", rs.Identifier.Name) + } } } - return fmt.Errorf("Status check timed out for component(s): [%v]", strings.Join(ids, ", ")) + return fmt.Errorf("timed out waiting for condition") } + return nil } @@ -162,3 +164,16 @@ func (sc *StatusChecker) getObjectRefs(components []string) ([]object.ObjMetadat func (sc *StatusChecker) objMetadataToString(om object.ObjMetadata) string { return fmt.Sprintf("%s '%s/%s'", om.GroupKind.Kind, om.Namespace, om.Name) } + +func (sc *StatusChecker) deploymentExists(om object.ObjMetadata) bool { + ctx, cancel := context.WithTimeout(context.Background(), sc.timeout) + defer cancel() + + namespacedName := types.NamespacedName{ + Namespace: om.Namespace, + Name: om.Name, + } + var existing appsv1.Deployment + err := sc.client.Get(ctx, namespacedName, &existing) + return err == nil +} diff --git a/docs/cmd/flux_check.md b/docs/cmd/flux_check.md index dd397326..63dd1960 100644 --- a/docs/cmd/flux_check.md +++ b/docs/cmd/flux_check.md @@ -25,9 +25,10 @@ flux check [flags] ### Options ``` - --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) - -h, --help help for check - --pre only run pre-installation checks + --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller]) + --components-extra strings list of components in addition to those supplied or defaulted, accepts comma-separated values + -h, --help help for check + --pre only run pre-installation checks ``` ### Options inherited from parent commands