diff --git a/pkg/actions/flux/enable.go b/pkg/actions/flux/enable.go index 05389af79b..0a8b0d3d59 100644 --- a/pkg/actions/flux/enable.go +++ b/pkg/actions/flux/enable.go @@ -28,6 +28,23 @@ type Installer struct { fluxClient InstallerClient } +// ValidateFlags checks that the Flux flags configured for bootstrap are +// recognised by the Flux CLI, without performing an actual bootstrap. It is +// intended to be called before cluster creation, so misconfigured flags are +// caught early instead of after a lengthy cluster creation. +func ValidateFlags(opts *api.GitOps) error { + if opts == nil || opts.Flux == nil { + return nil + } + + fluxClient, err := flux.NewClient(opts.Flux) + if err != nil { + return err + } + + return fluxClient.Validate() +} + func New(k8sClientSet kubeclient.Interface, opts *api.GitOps) (*Installer, error) { if opts.Flux == nil { return nil, errors.New("expected gitops.flux in cluster configuration but found nil") diff --git a/pkg/ctl/create/cluster.go b/pkg/ctl/create/cluster.go index b68d0acb3d..d6ae9df67b 100644 --- a/pkg/ctl/create/cluster.go +++ b/pkg/ctl/create/cluster.go @@ -239,6 +239,9 @@ func doCreateCluster(cmd *cmdutils.Cmd, ngFilter *filter.NodeGroupFilter, params if _, err := exec.LookPath("flux"); err != nil { return fmt.Errorf("flux binary is required when gitops configuration is set: %w", err) } + if err := flux.ValidateFlags(cfg.GitOps); err != nil { + return fmt.Errorf("validating Flux configuration: %w", err) + } } if params.InstallWindowsVPCController { diff --git a/pkg/flux/flux.go b/pkg/flux/flux.go index c9b6180f19..7eb511e372 100644 --- a/pkg/flux/flux.go +++ b/pkg/flux/flux.go @@ -15,6 +15,13 @@ import ( const ( fluxBin = "flux" minSupportedVersion = "0.32.0" + + // validationFlag is appended to the args we pass to `flux bootstrap` when + // validating flags. It is never a flag Flux recognises, so pflag will + // fail to parse it. Since pflag stops at the first unrecognised flag, + // an error that doesn't mention validationFlag means a real flag (one + // that appears earlier in the args) is invalid. + validationFlag = "eksctl-internal-validate-only" ) type Client struct { @@ -59,6 +66,25 @@ func (c *Client) Bootstrap() error { return c.runFluxCmd(args...) } +// Validate checks that the Flux flags configured for bootstrap are +// recognised by the Flux CLI, without performing an actual bootstrap. +// Flux has no dry-run mode for bootstrap, so this works around that by +// appending a flag Flux cannot recognise: see validationFlag. +func (c *Client) Validate() error { + args := []string{"bootstrap", c.opts.GitProvider} + + for k, v := range c.opts.Flags { + args = append(args, fmt.Sprintf("--%s", k), v) + } + args = append(args, fmt.Sprintf("--%s", validationFlag)) + + err := c.runFluxCmd(args...) + if err == nil || strings.Contains(err.Error(), validationFlag) { + return nil + } + return fmt.Errorf("invalid flux flags: %w", err) +} + func (c *Client) runFluxCmd(args ...string) error { logger.Debug(fmt.Sprintf("running flux %v ", args)) return c.executor.Exec(fluxBin, args...) diff --git a/pkg/flux/flux_test.go b/pkg/flux/flux_test.go index 7fdce8caa3..208d8cdbeb 100644 --- a/pkg/flux/flux_test.go +++ b/pkg/flux/flux_test.go @@ -2,8 +2,10 @@ package flux_test import ( "errors" + "fmt" "os" "path/filepath" + "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -173,4 +175,51 @@ var _ = Describe("Flux", func() { }) }) }) + + Context("Validate", func() { + // recognisedFlags simulates the real `flux bootstrap` command: + // pflag parses args in order and fails on the first flag it doesn't + // recognise. + recognisedFlags := map[string]bool{ + "owner": true, + "repository": true, + "branch": true, + "path": true, + } + + BeforeEach(func() { + fakeExecutor.ExecCalls(func(_ string, args ...string) error { + for _, arg := range args { + if !strings.HasPrefix(arg, "--") { + continue + } + flagName := strings.TrimPrefix(arg, "--") + if !recognisedFlags[flagName] { + return fmt.Errorf("unknown flag: --%s", flagName) + } + } + return nil + }) + }) + + When("all configured flags are recognised by Flux", func() { + BeforeEach(func() { + opts.Flags = api.FluxFlags{"owner": "me", "repository": "my-repo"} + }) + + It("succeeds, having failed only on the validation sentinel flag", func() { + Expect(fluxClient.Validate()).To(Succeed()) + }) + }) + + When("a configured flag is not recognised by Flux", func() { + BeforeEach(func() { + opts.Flags = api.FluxFlags{"owner": "me", "totally-bogus-flag": "oops"} + }) + + It("returns an error identifying the invalid flag, instead of the validation sentinel", func() { + Expect(fluxClient.Validate()).To(MatchError(ContainSubstring("totally-bogus-flag"))) + }) + }) + }) })