diff --git a/api/v1alpha1/controllermanagerconfig_loader.go b/api/v1alpha1/controllermanagerconfig_loader.go index b00799b3..e7f51674 100644 --- a/api/v1alpha1/controllermanagerconfig_loader.go +++ b/api/v1alpha1/controllermanagerconfig_loader.go @@ -12,8 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/cache" ) -func LoadOptionsFromFile(path string, scheme *runtime.Scheme, options *ctrl.Options, config *ControllerManagerConfig) error { - if err := loadFile(path, scheme, config); err != nil { +func LoadOptionsFromFile(path string, scheme *runtime.Scheme, options *ctrl.Options, config *ControllerManagerConfig, expandEnv bool) error { + if err := loadFile(path, scheme, config, expandEnv); err != nil { return err } @@ -22,12 +22,16 @@ func LoadOptionsFromFile(path string, scheme *runtime.Scheme, options *ctrl.Opti return nil } -func loadFile(path string, scheme *runtime.Scheme, config *ControllerManagerConfig) error { +func loadFile(path string, scheme *runtime.Scheme, config *ControllerManagerConfig, expandEnv bool) error { content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("could not read file at %s: %w", path, err) } + if expandEnv { + content = []byte(os.ExpandEnv(string(content))) + } + codecs := serializer.NewCodecFactory(scheme) // Regardless of if the bytes are of any external version, diff --git a/api/v1alpha1/controllermanagerconfig_loader_test.go b/api/v1alpha1/controllermanagerconfig_loader_test.go index bf94c167..0702ac9e 100644 --- a/api/v1alpha1/controllermanagerconfig_loader_test.go +++ b/api/v1alpha1/controllermanagerconfig_loader_test.go @@ -36,6 +36,13 @@ ignoreNamespaces: - kube-public - spire-system - local-path-storage +` + + fileContentExpandEnv = ` +apiVersion: spire.spiffe.io/v1alpha1 +kind: ControllerManagerConfig +clusterName: cluster2 +trustDomain: $TRUST_DOMAIN ` ) @@ -56,7 +63,7 @@ func TestLoadOptionsFromFileReplaceDefaultValues(t *testing.T) { ValidatingWebhookConfigurationName: "foo-webhook", } - err := spirev1alpha1.LoadOptionsFromFile(path, scheme, &options, &ctrlConfig) + err := spirev1alpha1.LoadOptionsFromFile(path, scheme, &options, &ctrlConfig, false) require.NoError(t, err) ok := true @@ -107,10 +114,43 @@ func TestLoadOptionsFromFileInvalidPath(t *testing.T) { ValidatingWebhookConfigurationName: "foo-webhook", } - err := spirev1alpha1.LoadOptionsFromFile("", scheme, &options, &ctrlConfig) + err := spirev1alpha1.LoadOptionsFromFile("", scheme, &options, &ctrlConfig, false) require.EqualError(t, err, "could not read file at : open : no such file or directory") - err = spirev1alpha1.LoadOptionsFromFile("foo.yaml", scheme, &options, &ctrlConfig) + err = spirev1alpha1.LoadOptionsFromFile("foo.yaml", scheme, &options, &ctrlConfig, false) fmt.Printf("err :%v\n", err) require.EqualError(t, err, "could not read file at foo.yaml: open foo.yaml: no such file or directory") } + +func TestLoadOptionsFromFileExpandEnv(t *testing.T) { + t.Setenv("TRUST_DOMAIN", "example.org") + + tempDir := t.TempDir() + path := filepath.Join(tempDir, "config.yaml") + require.NoError(t, os.WriteFile(path, []byte(fileContentExpandEnv), 0600)) + + scheme := runtime.NewScheme() + options := ctrl.Options{Scheme: scheme} + + ctrlConfig := spirev1alpha1.ControllerManagerConfig{} + + tests := []struct { + expandEnv bool + expectedValue string + }{ + { + expandEnv: true, + expectedValue: "example.org", + }, + { + expandEnv: false, + expectedValue: "$TRUST_DOMAIN", + }, + } + + for _, test := range tests { + err := spirev1alpha1.LoadOptionsFromFile(path, scheme, &options, &ctrlConfig, test.expandEnv) + require.NoError(t, err) + require.Equal(t, test.expectedValue, ctrlConfig.TrustDomain) + } +} diff --git a/cmd/main.go b/cmd/main.go index 32cbc80f..ebc0899d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -86,11 +86,13 @@ func main() { func parseConfig() (spirev1alpha1.ControllerManagerConfig, ctrl.Options, []*regexp.Regexp, error) { var configFileFlag string var spireAPISocketFlag string + var expandEnvFlag bool flag.StringVar(&configFileFlag, "config", "", "The controller will load its initial configuration from this file. "+ "Omit this flag to use the default configuration values. "+ "Command-line flags override configuration from this file.") flag.StringVar(&spireAPISocketFlag, "spire-api-socket", "", "The path to the SPIRE API socket (deprecated; use the config file)") + flag.BoolVar(&expandEnvFlag, "expand-env", false, "Expand environment variables in SPIRE Controller Manager config file") // Parse log flags opts := zap.Options{ @@ -112,7 +114,7 @@ func parseConfig() (spirev1alpha1.ControllerManagerConfig, ctrl.Options, []*rege var ignoreNamespacesRegex []*regexp.Regexp if configFileFlag != "" { - if err := spirev1alpha1.LoadOptionsFromFile(configFileFlag, scheme, &options, &ctrlConfig); err != nil { + if err := spirev1alpha1.LoadOptionsFromFile(configFileFlag, scheme, &options, &ctrlConfig, expandEnvFlag); err != nil { return ctrlConfig, options, ignoreNamespacesRegex, fmt.Errorf("unable to load the config file: %w", err) }