diff --git a/src/cmd/internal.go b/src/cmd/internal.go index 2799fbbf..c208ccbb 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -63,6 +63,7 @@ var genCLIDocs = &cobra.Command{ rootCmd.RemoveCommand(scanCmd) rootCmd.RemoveCommand(planCmd) rootCmd.RemoveCommand(applyCmd) + rootCmd.RemoveCommand(initCmd) // Set the default value for the uds-cache flag (otherwise this defaults to the user's home directory) rootCmd.Flag("uds-cache").DefValue = "~/.uds-cache" diff --git a/src/cmd/root.go b/src/cmd/root.go index 635d8691..4e61df85 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -12,6 +12,7 @@ import ( "github.com/defenseunicorns/uds-cli/src/cmd/monitor" "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/uds-cli/src/config/lang" + "github.com/defenseunicorns/uds-cli/src/pkg/featureflags" "github.com/defenseunicorns/uds-cli/src/types" goyaml "github.com/goccy/go-yaml" "github.com/pterm/pterm" @@ -25,6 +26,8 @@ var ( // Default global config for the bundler bundleCfg = types.BundleConfig{} + + cliFeatures string ) var rootCmd = &cobra.Command{ @@ -47,6 +50,12 @@ var rootCmd = &cobra.Command{ return err } } + // Enable features from the --feature flag + if cliFeatures != "" { + for _, feature := range strings.Split(cliFeatures, ",") { + featureflags.EnableFeature(feature) + } + } return nil }, Short: lang.RootCmdShort, @@ -64,6 +73,8 @@ var rootCmd = &cobra.Command{ // Execute is the entrypoint for the CLI. func Execute() { + // initialize feature flags + featureflags.Initialize() err := rootCmd.Execute() if err == nil { return @@ -113,6 +124,11 @@ func init() { rootCmd.PersistentFlags().IntVar(&config.CommonOptions.OCIConcurrency, "oci-concurrency", v.GetInt(V_BNDL_OCI_CONCURRENCY), lang.CmdBundleFlagConcurrency) rootCmd.PersistentFlags().BoolVar(&config.NoColor, "no-color", v.GetBool(V_NO_COLOR), lang.RootCmdFlagNoColor) + // Add a persistent flag for setting features + rootCmd.PersistentFlags().StringVar(&cliFeatures, "feature", "", "Comma-separated list of features to enable via a flag") + // hide the 'feature' flag for now + _ = rootCmd.PersistentFlags().MarkHidden("feature") + rootCmd.AddCommand(monitor.Cmd) } diff --git a/src/cmd/uds.go b/src/cmd/uds.go index 5170338c..0f72160e 100644 --- a/src/cmd/uds.go +++ b/src/cmd/uds.go @@ -36,6 +36,7 @@ var createCmd = &cobra.Command{ if err != nil { return fmt.Errorf("error reading the current working directory") } + if len(args) > 0 { srcDir = args[0] } @@ -267,6 +268,7 @@ func init() { // logs cmd rootCmd.AddCommand(logsCmd) + } // chooseBundle provides a file picker when users don't specify a file diff --git a/src/cmd/vendored.go b/src/cmd/vendored.go index a67573c0..24f6cc2c 100644 --- a/src/cmd/vendored.go +++ b/src/cmd/vendored.go @@ -21,6 +21,7 @@ import ( "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/uds-cli/src/config/lang" + "github.com/defenseunicorns/uds-cli/src/pkg/featureflags" "github.com/spf13/cobra" zarfCLI "github.com/zarf-dev/zarf/src/cmd" zarfConfig "github.com/zarf-dev/zarf/src/config" @@ -121,6 +122,10 @@ var planCmd = &cobra.Command{ Short: lang.CmdBundlePlanShort, // Args: cobra.MaximumNArgs(0), RunE: func(_ *cobra.Command, _ []string) error { + if !featureflags.IsEnabled("tofu") { + message.Warn("The 'plan' command is not enabled. Use the '--feature=tofu' flag or set the FEATURE_FLAG environment variable.") + return nil + } return useEmbeddedTofu() }, DisableFlagParsing: true, @@ -131,6 +136,11 @@ var applyCmd = &cobra.Command{ Short: lang.CmdBundleApplyShort, Args: cobra.MaximumNArgs(0), RunE: func(_ *cobra.Command, _ []string) error { + if !featureflags.IsEnabled("tofu") { + message.Warn("The 'apply' command is not enabled. Use the '--feature=tofu' flag or set the FEATURE_FLAG environment variable.") + return nil + } + return useEmbeddedTofu() }, DisableFlagParsing: true, @@ -148,6 +158,23 @@ var scanCmd = &cobra.Command{ DisableFlagParsing: true, } +// uds init [bundle-name] will initialize tofu in the current directory. If a +// bundle name is provided, it will use the bundle as the source for the Tofu +// providers and uds-bundle.tf file. +var initCmd = &cobra.Command{ + Use: "init [BUNDLE_TARBALL]", + Short: lang.CmdBundleApplyShort, + Args: cobra.MaximumNArgs(0), + RunE: func(_ *cobra.Command, _ []string) error { + if !featureflags.IsEnabled("tofu") { + fmt.Println("The 'init' command is not enabled. Use the '--feature=tofu' flag or set the FEATURE_FLAG environment variable.") + return nil + } + return useEmbeddedTofu() + }, + DisableFlagParsing: true, +} + func init() { // grab Zarf version to make Zarf library checks happy if buildInfo, ok := debug.ReadBuildInfo(); ok { @@ -170,4 +197,5 @@ func init() { rootCmd.AddCommand(scanCmd) // uds-security-hub CLI command rootCmd.AddCommand(planCmd) // tofu plan rootCmd.AddCommand(applyCmd) // tofu apply + rootCmd.AddCommand(initCmd) // tofu init } diff --git a/src/config/lang/lang.go b/src/config/lang/lang.go index 0b935724..77b75171 100644 --- a/src/config/lang/lang.go +++ b/src/config/lang/lang.go @@ -56,6 +56,7 @@ const ( // bundle apply CmdBundleApplyShort = "Create or update infrastructure with Tofu" + CmdBundleInitShort = "Initialize OpenTofu" // bundle inspect CmdBundleInspectShort = "Display the metadata of a bundle" @@ -125,4 +126,4 @@ const ( CmdPeprMonitorTimestampFlag = "Show timestamps in Pepr logs" CmdPeprMonitorSinceFlag = "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs." CmdPeprMonitorJSONFlag = "Return the raw JSON output of the logs" -) +) \ No newline at end of file diff --git a/src/pkg/featureflags/featureflags.go b/src/pkg/featureflags/featureflags.go new file mode 100644 index 00000000..83fe1df0 --- /dev/null +++ b/src/pkg/featureflags/featureflags.go @@ -0,0 +1,34 @@ +// Copyright 2025 Defense Unicorns +// SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + +package featureflags + +import ( + "os" + "strings" +) + +// FeatureFlagMap stores the state of feature flags. +var FeatureFlagMap = map[string]bool{} + +// Initialize initializes the feature flags from an environment variable. +func Initialize() { + // Check the FEATURE_FLAG environment variable. + // example: FEATURE_FLAG=tofu,encrypt-oci + envFeatures := os.Getenv("FEATURE_FLAG") + if envFeatures != "" { + for _, feature := range strings.Split(envFeatures, ",") { + FeatureFlagMap[feature] = true + } + } +} + +// EnableFeature enables a feature flag programmatically. +func EnableFeature(feature string) { + FeatureFlagMap[feature] = true +} + +// IsEnabled checks if a feature flag is enabled. +func IsEnabled(feature string) bool { + return FeatureFlagMap[feature] +} diff --git a/tasks.yaml b/tasks.yaml index d1c6aec8..1b2c8ced 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -129,4 +129,4 @@ tasks: - task: get-versions - task: build-args - task: download-tofu-mac-apple - - cmd: GOOS=darwin GOARCH=arm64 go build -ldflags="${BUILD_ARGS}" -o build/uds-mac-apple main.go + - cmd: GOOS=darwin GOARCH=arm64 go build -ldflags="${BUILD_ARGS}" -o build/uds-mac-apple main.go \ No newline at end of file