diff --git a/kustomize/commands/edit/add/addconfiguration.go b/kustomize/commands/edit/add/addconfiguration.go new file mode 100644 index 0000000000..80bab507f1 --- /dev/null +++ b/kustomize/commands/edit/add/addconfiguration.go @@ -0,0 +1,79 @@ +// Copyright 2025 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "errors" + "log" + "slices" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/kustfile" + "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/util" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type addConfigurationOptions struct { + configurationFilePaths []string +} + +// newCmdAddConfiguration adds the name of a file containing a configuration +// to the kustomization file. +func newCmdAddConfiguration(fSys filesys.FileSystem) *cobra.Command { + var o addConfigurationOptions + cmd := &cobra.Command{ + Use: "configuration", + Short: "Add the name of a file containing a configuration to the kustomization file", + Long: `Add the name of a file containing a configuration (e.g., a Kubernetes configuration resource) +to the kustomization file. Configurations are used to define custom transformer specifications +for CRDs and other resource types.`, + Example: ` + # Adds a configuration file to the kustomization + kustomize edit add configuration + + # Adds multiple configuration files + kustomize edit add configuration ,`, + RunE: func(cmd *cobra.Command, args []string) error { + err := o.Validate(fSys, args) + if err != nil { + return err + } + return o.RunAddConfiguration(fSys) + }, + } + return cmd +} + +// Validate validates add configuration command. +func (o *addConfigurationOptions) Validate(fSys filesys.FileSystem, args []string) error { + if len(args) == 0 { + return errors.New("must specify a yaml file which contains a configuration resource") + } + var err error + o.configurationFilePaths, err = util.GlobPatterns(fSys, args) + return err +} + +// RunAddConfiguration runs add configuration command (do real work). +func (o *addConfigurationOptions) RunAddConfiguration(fSys filesys.FileSystem) error { + if len(o.configurationFilePaths) == 0 { + return nil + } + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + m, err := mf.Read() + if err != nil { + return err + } + for _, c := range o.configurationFilePaths { + if slices.Contains(m.Configurations, c) { + log.Printf("configuration %s already in kustomization file", c) + continue + } + m.Configurations = append(m.Configurations, c) + } + return mf.Write(m) +} diff --git a/kustomize/commands/edit/add/addconfiguration_test.go b/kustomize/commands/edit/add/addconfiguration_test.go new file mode 100644 index 0000000000..0144aa5c96 --- /dev/null +++ b/kustomize/commands/edit/add/addconfiguration_test.go @@ -0,0 +1,92 @@ +// Copyright 2025 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + testutils_test "sigs.k8s.io/kustomize/kustomize/v5/commands/internal/testutils" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +func TestAddConfiguration(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + testutils_test.WriteTestKustomization(fSys) + + cmd := newCmdAddConfiguration(fSys) + + if cmd == nil { + t.Fatal("Expected cmd to not be nil") + } + + if cmd.Use != "configuration" { + t.Fatalf("Expected Use to be 'configuration', got '%s'", cmd.Use) + } + + if cmd.Short == "" { + t.Fatal("Expected Short to not be empty") + } +} + +func TestAddConfigurationHappyPath(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + err := fSys.WriteFile("config1.yaml", []byte("apiVersion: v1\nkind: Config")) + require.NoError(t, err) + err = fSys.WriteFile("config2.yaml", []byte("apiVersion: v1\nkind: Config")) + require.NoError(t, err) + testutils_test.WriteTestKustomization(fSys) + + cmd := newCmdAddConfiguration(fSys) + args := []string{"config1.yaml", "config2.yaml"} + require.NoError(t, cmd.RunE(cmd, args)) + + content, err := testutils_test.ReadTestKustomization(fSys) + require.NoError(t, err) + assert.Contains(t, string(content), "config1.yaml") + assert.Contains(t, string(content), "config2.yaml") +} + +func TestAddConfigurationDuplicate(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + err := fSys.WriteFile("config.yaml", []byte("apiVersion: v1\nkind: Config")) + require.NoError(t, err) + testutils_test.WriteTestKustomization(fSys) + + cmd := newCmdAddConfiguration(fSys) + + // First addition + args := []string{"config.yaml"} + require.NoError(t, cmd.RunE(cmd, args)) + + content, err := testutils_test.ReadTestKustomization(fSys) + require.NoError(t, err) + assert.Contains(t, string(content), "config.yaml") + + // Second addition (should skip duplicate) + require.NoError(t, cmd.RunE(cmd, args)) + + content, err = testutils_test.ReadTestKustomization(fSys) + require.NoError(t, err) + + // Count occurrences - should only appear once + count := 0 + data := string(content) + for i := 0; i < len(data); { + if idx := len(data[i:]); idx > 0 { + if len(data) >= i+11 { + if data[i:i+11] == "config.yaml" { + count++ + i += 11 + } else { + i++ + } + } else { + break + } + } + } + assert.Equal(t, 1, count, "config.yaml should appear exactly once") +} diff --git a/kustomize/commands/edit/add/all.go b/kustomize/commands/edit/add/all.go index 01dded5089..31886de9b3 100644 --- a/kustomize/commands/edit/add/all.go +++ b/kustomize/commands/edit/add/all.go @@ -47,6 +47,12 @@ func NewCmdAdd( # Adds a transformer configuration to the kustomization kustomize edit add transformer + + # Adds a configuration file to the kustomization + kustomize edit add configuration + + # Adds a generator configuration to the kustomization + kustomize edit add generator `, Args: cobra.MinimumNArgs(1), } @@ -61,6 +67,7 @@ func NewCmdAdd( newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()), newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()), newCmdAddTransformer(fSys), + newCmdAddConfiguration(fSys), newCmdAddGenerator(fSys), ) return c