diff --git a/internal/api/dashboard/client.go b/internal/api/dashboard/client.go index ae41b67..72f29b0 100644 --- a/internal/api/dashboard/client.go +++ b/internal/api/dashboard/client.go @@ -50,12 +50,6 @@ type RunParameters struct { Guardrails []json.RawMessage `json:"guardrails"` Budgets []json.RawMessage `json:"budgets"` ConfigTemplate string `json:"configTemplate"` - - // FeatureFlags is the JSON-serialized event.FeatureFlags proto describing - // which per-org feature flags are enabled for this run (e.g. whether the - // Kubernetes plugins should be used). Unmarshaled with protojson where it's - // consumed. May be empty against older dashboards that don't return it. - FeatureFlags json.RawMessage `json:"featureFlags"` } type Client interface { @@ -157,7 +151,6 @@ func (c *client) RunParameters(ctx context.Context, repoURL, branchName string) guardrails budgets configTemplate - featureFlags } }` diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 0a809a5..6636a55 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -59,28 +59,7 @@ type TaggingPolicy struct { *event.TagPolicy } -// applyFeatureFlags reads the per-org feature flags returned in the run -// parameters and applies the ones the CLI acts on to the plugin config. -// Currently this gates the Kubernetes plugins on enableK8sPlugins. It must run -// before any plugin is loaded so the manager knows whether to download and load -// them (EnsurePlugins builds the Manager once and memoizes it). -func (s *Scanner) applyFeatureFlags(runParameters *dashboard.RunParameters) error { - if runParameters == nil || len(runParameters.FeatureFlags) == 0 { - return nil - } - flags := new(event.FeatureFlags) - if err := pj.Unmarshal(runParameters.FeatureFlags, flags); err != nil { - return fmt.Errorf("failed to unmarshal feature flags: %w", err) - } - s.Plugins.EnableK8sPlugins = flags.GetEnableK8SPlugins() - return nil -} - func (s *Scanner) ListPolicies(ctx context.Context, runParameters *dashboard.RunParameters, providerFilter []string) ([]FinOpsPolicy, []TaggingPolicy, error) { - if err := s.applyFeatureFlags(runParameters); err != nil { - return nil, nil, err - } - var tagPolicies []*event.TagPolicy var finopsPolicySettings []*event.FinopsPolicySettings var hasRunParameters bool @@ -190,12 +169,6 @@ func (s *Scanner) ListPolicies(ctx context.Context, runParameters *dashboard.Run func (s *Scanner) Scan(ctx context.Context, runParameters dashboard.RunParameters, absolutePath, branchName string, tokenSource oauth2.TokenSource, pluginOpts pkgscanner.PluginOpts) (*format.Result, error) { var result format.Result - // Apply run-parameter feature flags (e.g. the Kubernetes plugin gate) before - // EnsurePlugins loads any plugins below. - if err := s.applyFeatureFlags(&runParameters); err != nil { - return nil, err - } - repositoryName := runParameters.RepositoryName // UsageDefaults is empty when the scan runs without Infracost Cloud @@ -362,12 +335,6 @@ func (s *Scanner) Scan(ctx context.Context, runParameters dashboard.RunParameter if err != nil { return nil, fmt.Errorf("failed to scan project %q: %w", project.Name, err) } - // A nil result means the project was intentionally skipped (e.g. a - // Kubernetes project while the k8s plugins are feature-gated off). - if projectResult == nil { - continue - } - result.Projects = append(result.Projects, &format.ProjectResult{ Config: projectResult.Config, Diagnostics: projectResult.Diagnostics, diff --git a/pkg/plugins/config.go b/pkg/plugins/config.go index 632352e..60d94d6 100644 --- a/pkg/plugins/config.go +++ b/pkg/plugins/config.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/infracost/cli/pkg/config/process" - "github.com/infracost/cli/pkg/logging" ) var _ process.Processor = (*Config)(nil) @@ -30,17 +29,6 @@ type Config struct { // available. AutoUpdate bool `env:"INFRACOST_CLI_PLUGIN_AUTO_UPDATE" default:"true"` - // EnableK8sPlugins gates the Kubernetes parser/provider plugins. It mirrors - // the enableK8sPlugins run-parameter feature flag (a per-org LaunchDarkly - // rollout). The plugins are always downloaded (they're publicly available - // regardless); this flag only controls whether they're executed. When false - // the Kubernetes plugins are excluded from provider processing and their - // projects are skipped. Set from the run parameters before scanning. - // - // TODO: remove this flag (and the RequiresK8sPlugins gate) once the - // Kubernetes plugins are fully launched and no longer feature-gated. - EnableK8sPlugins bool - managerMu sync.Mutex ensureOnce sync.Once ensureErr error @@ -134,45 +122,17 @@ func (c *Config) ParserPluginForProject(ctx context.Context, projectTypeOrPlugin return manager.LoadParserPluginForProject(ctx, projectTypeOrPluginName) } -// ProviderPlugins returns every loaded provider plugin that should run, with -// feature-gated plugins (currently the Kubernetes provider when -// EnableK8sPlugins is off) filtered out. +// ProviderPlugins returns every loaded provider plugin. func (c *Config) ProviderPlugins(ctx context.Context) ([]*ProviderPlugin, error) { - var providers []*ProviderPlugin if c.LoadProviderPlugins != nil { - var err error - if providers, err = c.LoadProviderPlugins(ctx); err != nil { - return nil, err - } - } else { - manager, err := c.EnsurePlugins(ctx) - if err != nil { - return nil, err - } - if providers, err = manager.LoadProviderPlugins(ctx); err != nil { - return nil, err - } - } - - filtered := make([]*ProviderPlugin, 0, len(providers)) - for _, p := range providers { - if c.SkipPluginExecution(p.Info.GetName()) { - logging.Debugf("skipping provider plugin %q (feature flag disabled)", p.Info.GetName()) - continue - } - filtered = append(filtered, p) + return c.LoadProviderPlugins(ctx) } - return filtered, nil -} -// SkipPluginExecution reports whether a plugin reporting the given name should -// be excluded from execution given the current feature-flag gates. Today this -// is the Kubernetes plugins when EnableK8sPlugins is off. -func (c *Config) SkipPluginExecution(reportedName string) bool { - if c.EnableK8sPlugins { - return false + manager, err := c.EnsurePlugins(ctx) + if err != nil { + return nil, err } - return gatedPluginNames()[reportedName] + return manager.LoadProviderPlugins(ctx) } // Close releases all plugin subprocess resources. diff --git a/pkg/plugins/config_test.go b/pkg/plugins/config_test.go index e59bf8f..044c292 100644 --- a/pkg/plugins/config_test.go +++ b/pkg/plugins/config_test.go @@ -561,8 +561,7 @@ func TestEnsureInstalled(t *testing.T) { defer srv.Close() cacheDir := t.TempDir() - // Every required plugin is downloaded unconditionally, including the - // feature-gated Kubernetes ones — the gate only affects execution. + // Every required plugin is downloaded unconditionally. c := &Config{Cache: cacheDir, BaseURL: srv.URL, AutoUpdate: true} _, err := c.EnsurePlugins(context.Background()) require.NoError(t, err) @@ -637,52 +636,21 @@ func TestUpdatePlugins(t *testing.T) { }) } -func TestSkipPluginExecution(t *testing.T) { - // The Kubernetes plugins are the gated ones; sanity check the source of - // truth so this test can't silently pass if that changes. - require.Contains(t, gatedPluginNames(), "infracost/kubernetes") - - t.Run("skips gated plugins when the flag is off", func(t *testing.T) { - c := &Config{EnableK8sPlugins: false} - assert.True(t, c.SkipPluginExecution("infracost/kubernetes")) - assert.False(t, c.SkipPluginExecution("infracost/aws")) - }) - - t.Run("runs gated plugins when the flag is on", func(t *testing.T) { - c := &Config{EnableK8sPlugins: true} - assert.False(t, c.SkipPluginExecution("infracost/kubernetes")) - assert.False(t, c.SkipPluginExecution("infracost/aws")) - }) -} - -func TestProviderPluginsFiltersGated(t *testing.T) { +func TestProviderPluginsReturnsAll(t *testing.T) { providers := []*ProviderPlugin{ {Info: &pb.GetPluginInfoResponse{Name: "infracost/aws"}}, {Info: &pb.GetPluginInfoResponse{Name: "infracost/kubernetes"}}, } - loader := func(context.Context) ([]*ProviderPlugin, error) { return providers, nil } + c := &Config{LoadProviderPlugins: func(context.Context) ([]*ProviderPlugin, error) { return providers, nil }} - names := func(ps []*ProviderPlugin) []string { - out := make([]string, len(ps)) - for i, p := range ps { - out[i] = p.Info.GetName() - } - return out - } - - t.Run("excludes the kubernetes provider when the flag is off", func(t *testing.T) { - c := &Config{EnableK8sPlugins: false, LoadProviderPlugins: loader} - got, err := c.ProviderPlugins(context.Background()) - require.NoError(t, err) - assert.Equal(t, []string{"infracost/aws"}, names(got)) - }) + got, err := c.ProviderPlugins(context.Background()) + require.NoError(t, err) - t.Run("includes the kubernetes provider when the flag is on", func(t *testing.T) { - c := &Config{EnableK8sPlugins: true, LoadProviderPlugins: loader} - got, err := c.ProviderPlugins(context.Background()) - require.NoError(t, err) - assert.ElementsMatch(t, []string{"infracost/aws", "infracost/kubernetes"}, names(got)) - }) + names := make([]string, len(got)) + for i, p := range got { + names[i] = p.Info.GetName() + } + assert.ElementsMatch(t, []string{"infracost/aws", "infracost/kubernetes"}, names) } func TestRequiredPluginVersionEnv(t *testing.T) { diff --git a/pkg/plugins/required.go b/pkg/plugins/required.go index c9f7695..290e982 100644 --- a/pkg/plugins/required.go +++ b/pkg/plugins/required.go @@ -31,10 +31,6 @@ type requiredPlugin struct { // we can't ask it directly. DisplayName string Type string - // RequiresK8sPlugins gates this plugin on the enableK8sPlugins run-parameter - // feature flag (a per-org LaunchDarkly rollout). When the flag is off the - // plugin is neither downloaded nor loaded. - RequiresK8sPlugins bool } // requiredPlugins is the set of plugins the CLI manages automatically. @@ -53,12 +49,11 @@ var requiredPlugins = []requiredPlugin{ {Key: "aws", Name: "infracost-provider-aws", LegacyName: "infracost-plugin-aws", DisplayName: "infracost/aws", Type: pluginTypeProvider}, {Key: "google", Name: "infracost-provider-google", LegacyName: "infracost-plugin-google", DisplayName: "infracost/google", Type: pluginTypeProvider}, {Key: "azure", Name: "infracost-provider-azure", LegacyName: "infracost-plugin-azure", DisplayName: "infracost/azure", Type: pluginTypeProvider}, - // Kubernetes parser and provider are gated on the enableK8sPlugins run - // parameter. They share the "kubernetes" key (and so the same version pin - // env var) — namespacing by type keeps their asset names and on-disk - // binaries distinct. - {Key: "kubernetes", Name: "infracost-parser-kubernetes", DisplayName: "infracost/kubernetes", Type: pluginTypeParser, RequiresK8sPlugins: true}, - {Key: "kubernetes", Name: "infracost-provider-kubernetes", DisplayName: "infracost/kubernetes", Type: pluginTypeProvider, RequiresK8sPlugins: true}, + // The Kubernetes parser and provider share the "kubernetes" key (and so the + // same version pin env var) — namespacing by type keeps their asset names + // and on-disk binaries distinct. + {Key: "kubernetes", Name: "infracost-parser-kubernetes", DisplayName: "infracost/kubernetes", Type: pluginTypeParser}, + {Key: "kubernetes", Name: "infracost-provider-kubernetes", DisplayName: "infracost/kubernetes", Type: pluginTypeProvider}, } // requiredPluginVersion returns the user-pinned version for the required @@ -66,16 +61,3 @@ var requiredPlugins = []requiredPlugin{ func requiredPluginVersion(key string) string { return os.Getenv("INFRACOST_CLI_PLUGIN_" + strings.ToUpper(key) + "_VERSION") } - -// gatedPluginNames returns the set of reported plugin names (as surfaced by -// GetPluginInfo) that are gated behind the enableK8sPlugins feature flag. Used -// to skip their execution when the flag is off. -func gatedPluginNames() map[string]bool { - names := make(map[string]bool) - for _, required := range requiredPlugins { - if required.RequiresK8sPlugins { - names[required.DisplayName] = true - } - } - return names -} diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index 5133ada..f6a811a 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -120,15 +120,6 @@ func ScanProject(ctx context.Context, opts *ScanProjectOptions) (*ProjectResult, return nil, fmt.Errorf("failed to load parser plugin for project type %q: %w", projectType, err) } - // Skip projects handled by a feature-gated parser (currently the Kubernetes - // parser when enableK8sPlugins is off). The plugin is still installed and - // autodetection still recognizes its projects; we just don't execute it. A - // nil result signals the caller to drop the project. - if opts.Plugins.SkipPluginExecution(parserPlugin.Info.GetName()) { - logging.Debugf("skipping project %q handled by gated plugin %q (feature flag disabled)", opts.Project.Name, parserPlugin.Info.GetName()) - return nil, nil - } - overrides := make(map[string]any) if opts.PluginOptions != nil { for _, key := range []string{string(projectType), parserPlugin.Info.GetName(), strings.TrimPrefix(parserPlugin.Info.GetName(), "infracost/")} {