From f06fc0d9827ee591e4db4d7a8b8215111e51cdfe Mon Sep 17 00:00:00 2001 From: Chetan Banavikalmutt Date: Tue, 6 Apr 2021 21:54:57 +0530 Subject: [PATCH] feat: allow per-version kustomize options (#5967) Allow adding build options that are specific to a kustomize version instead of using the same default options for each version. Signed-off-by: Chetan Banavikalmutt --- docs/operator-manual/argocd-cm.yaml | 6 +- docs/user-guide/kustomize.md | 9 +-- util/settings/settings.go | 68 +++++++++++++++++---- util/settings/settings_test.go | 91 +++++++++++++++++++++++++++-- 4 files changed, 154 insertions(+), 20 deletions(-) diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml index ca5f945040da3..5b949aee62b27 100644 --- a/docs/operator-manual/argocd-cm.yaml +++ b/docs/operator-manual/argocd-cm.yaml @@ -224,7 +224,11 @@ data: # Build options/parameters to use with `kustomize build` (optional) kustomize.buildOptions: --load_restrictor none - # Additional Kustomize versions and corresponding binary paths + # Per-version build options and binary paths + kustomize.path.v3.9.1: /custom-tools/kustomize_3_9 + kustomize.buildOptions.v3.9.1: --enable_kyaml true + + # Additional Kustomize versions and corresponding binary paths (deprecated) kustomize.version.v3.5.1: /custom-tools/kustomize_3_5_1 kustomize.version.v3.5.4: /custom-tools/kustomize_3_5_4 diff --git a/docs/user-guide/kustomize.md b/docs/user-guide/kustomize.md index 6e027468453f2..77cc64358cd2a 100644 --- a/docs/user-guide/kustomize.md +++ b/docs/user-guide/kustomize.md @@ -23,7 +23,7 @@ Read more about [private repos](private-repositories.md). ## `kustomize build` Options/Parameters -To provide build options to `kustomize build` add a property to the ArgoCD CM under data: +To provide build options to `kustomize build` of default kustomize version, use `kustomize.buildOptions` field of `argocd-cm` ConfigMap. Use `kustomize.buildOptions.` to register version specific build options. ```yaml apiVersion: v1 @@ -36,12 +36,13 @@ metadata: app.kubernetes.io/part-of: argocd data: kustomize.buildOptions: --load_restrictor none + kustomize.buildOptions.v3.9.1: --output /tmp ``` ## Custom Kustomize versions Argo CD supports using multiple kustomize versions simultaneously and specifies required version per application. To add additional versions make sure required versions are [bundled](../operator-manual/custom_tools.md) and then -use `kustomize.version.` fields of `argocd-cm` ConfigMap to register bundled additional versions. +use `kustomize.path.` fields of `argocd-cm` ConfigMap to register bundled additional versions. ```yaml apiVersion: v1 @@ -53,8 +54,8 @@ metadata: app.kubernetes.io/name: argocd-cm app.kubernetes.io/part-of: argocd data: - kustomize.version.v3.5.1: /custom-tools/kustomize_3_5_1 - kustomize.version.v3.5.4: /custom-tools/kustomize_3_5_4 + kustomize.path.v3.5.1: /custom-tools/kustomize_3_5_1 + kustomize.path.v3.5.4: /custom-tools/kustomize_3_5_4 ``` Once a new version is configured you can reference it in Application spec as following: diff --git a/util/settings/settings.go b/util/settings/settings.go index c0fdbcfe7f236..b6695d0231e40 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -122,8 +122,10 @@ type HelmRepoCredentials struct { type KustomizeVersion struct { // Name holds Kustomize version name Name string - // Name holds corresponding binary path + // Path holds corresponding binary path Path string + // BuildOptions that are specific to Kustomize version + BuildOptions string } // KustomizeSettings holds kustomize settings @@ -134,19 +136,25 @@ type KustomizeSettings struct { func (ks *KustomizeSettings) GetOptions(source v1alpha1.ApplicationSource) (*v1alpha1.KustomizeOptions, error) { binaryPath := "" + buildOptions := "" if source.Kustomize != nil && source.Kustomize.Version != "" { for _, ver := range ks.Versions { if ver.Name == source.Kustomize.Version { + // add version specific path and build options binaryPath = ver.Path + buildOptions = ver.BuildOptions break } } if binaryPath == "" { return nil, fmt.Errorf("kustomize version %s is not registered", source.Kustomize.Version) } + } else { + // add build options for the default version + buildOptions = ks.BuildOptions } return &v1alpha1.KustomizeOptions{ - BuildOptions: ks.BuildOptions, + BuildOptions: buildOptions, BinaryPath: binaryPath, }, nil } @@ -264,6 +272,8 @@ const ( kustomizeBuildOptionsKey = "kustomize.buildOptions" // kustomizeVersionKeyPrefix is a kustomize version key prefix kustomizeVersionKeyPrefix = "kustomize.version" + // kustomizePathPrefixKey is a kustomize path for a specific version + kustomizePathPrefixKey = "kustomize.path" // anonymousUserEnabledKey is the key which enables or disables anonymous user anonymousUserEnabledKey = "users.anonymous.enabled" // anonymousUserEnabledKey is the key which specifies token expiration duration @@ -586,22 +596,60 @@ func (mgr *SettingsManager) GetKustomizeSettings() (*KustomizeSettings, error) { if err != nil { return nil, err } + kustomizeVersionsMap := map[string]KustomizeVersion{} + buildOptions := map[string]string{} settings := &KustomizeSettings{} - if value, ok := argoCDCM.Data[kustomizeBuildOptionsKey]; ok { - settings.BuildOptions = value + + // extract build options for the default version + if options, ok := argoCDCM.Data[kustomizeBuildOptionsKey]; ok { + settings.BuildOptions = options } + + // extract per-version binary paths and build options for k, v := range argoCDCM.Data { - if !strings.HasPrefix(k, kustomizeVersionKeyPrefix) { - continue + // extract version and path from kustomize.version. + if strings.HasPrefix(k, kustomizeVersionKeyPrefix) { + err = addKustomizeVersion(kustomizeVersionKeyPrefix, k, v, kustomizeVersionsMap) + if err != nil { + return nil, err + } + } + + // extract version and path from kustomize.path. + if strings.HasPrefix(k, kustomizePathPrefixKey) { + err = addKustomizeVersion(kustomizePathPrefixKey, k, v, kustomizeVersionsMap) + if err != nil { + return nil, err + } + } + + // extract version and build options from kustomize.buildOptions. + if strings.HasPrefix(k, kustomizeBuildOptionsKey) && k != kustomizeBuildOptionsKey { + buildOptions[k[len(kustomizeBuildOptionsKey)+1:]] = v + } + } + + for _, v := range kustomizeVersionsMap { + if _, ok := buildOptions[v.Name]; ok { + v.BuildOptions = buildOptions[v.Name] } - settings.Versions = append(settings.Versions, KustomizeVersion{ - Name: k[len(kustomizeVersionKeyPrefix)+1:], - Path: v, - }) + settings.Versions = append(settings.Versions, v) } return settings, nil } +func addKustomizeVersion(prefix, name, path string, kvMap map[string]KustomizeVersion) error { + version := name[len(prefix)+1:] + if _, ok := kvMap[version]; ok { + return fmt.Errorf("found duplicate kustomize version: %s", version) + } + kvMap[version] = KustomizeVersion{ + Name: version, + Path: path, + } + return nil +} + // DEPRECATED. Helm repository credentials are now managed using RepoCredentials func (mgr *SettingsManager) GetHelmRepositories() ([]HelmRepoCredentials, error) { argoCDCM, err := mgr.getConfigMap() diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go index 82b179a3c92a2..ed2c92fbf8d85 100644 --- a/util/settings/settings_test.go +++ b/util/settings/settings_test.go @@ -2,6 +2,7 @@ package settings import ( "context" + "sort" "testing" "github.com/argoproj/argo-cd/v2/common" @@ -296,14 +297,74 @@ func TestSettingsManager_GetKustomizeBuildOptions(t *testing.T) { assert.Equal(t, "foo", options.BuildOptions) assert.Equal(t, []KustomizeVersion{{Name: "v3.2.1", Path: "somePath"}}, options.Versions) }) + + t.Run("Kustomize settings per-version", func(t *testing.T) { + _, settingsManager := fixtures(map[string]string{ + "kustomize.buildOptions": "--global true", + "kustomize.version.v3.2.1": "/path_3.2.1", + "kustomize.buildOptions.v3.2.3": "--options v3.2.3", + "kustomize.path.v3.2.3": "/path_3.2.3", + "kustomize.path.v3.2.4": "/path_3.2.4", + "kustomize.buildOptions.v3.2.4": "--options v3.2.4", + "kustomize.buildOptions.v3.2.5": "--options v3.2.5", + }) + + got, err := settingsManager.GetKustomizeSettings() + + assert.NoError(t, err) + assert.Equal(t, "--global true", got.BuildOptions) + want := &KustomizeSettings{ + BuildOptions: "--global true", + Versions: []KustomizeVersion{ + {Name: "v3.2.1", Path: "/path_3.2.1"}, + {Name: "v3.2.3", Path: "/path_3.2.3", BuildOptions: "--options v3.2.3"}, + {Name: "v3.2.4", Path: "/path_3.2.4", BuildOptions: "--options v3.2.4"}, + }, + } + sortVersionsByName := func(versions []KustomizeVersion) { + sort.Slice(versions, func(i, j int) bool { + return versions[i].Name > versions[j].Name + }) + } + sortVersionsByName(want.Versions) + sortVersionsByName(got.Versions) + assert.EqualValues(t, want, got) + }) + + t.Run("Kustomize settings per-version with duplicate versions", func(t *testing.T) { + _, settingsManager := fixtures(map[string]string{ + "kustomize.buildOptions": "--global true", + "kustomize.version.v3.2.1": "/path_3.2.1", + "kustomize.buildOptions.v3.2.1": "--options v3.2.3", + "kustomize.path.v3.2.2": "/other_path_3.2.2", + "kustomize.path.v3.2.1": "/other_path_3.2.1", + }) + + got, err := settingsManager.GetKustomizeSettings() + assert.EqualError(t, err, "found duplicate kustomize version: v3.2.1") + assert.Empty(t, got) + }) + + t.Run("Config map with no Kustomize settings", func(t *testing.T) { + _, settingsManager := fixtures(map[string]string{ + "other.options": "--global true", + }) + + got, err := settingsManager.GetKustomizeSettings() + assert.NoError(t, err) + assert.Empty(t, got) + }) } func TestKustomizeSettings_GetOptions(t *testing.T) { - settings := KustomizeSettings{Versions: []KustomizeVersion{ - {Name: "v1", Path: "path_v1"}, - {Name: "v2", Path: "path_v2"}, - {Name: "v3", Path: "path_v3"}, - }} + settings := KustomizeSettings{ + BuildOptions: "--opt1 val1", + Versions: []KustomizeVersion{ + {Name: "v1", Path: "path_v1"}, + {Name: "v2", Path: "path_v2"}, + {Name: "v3", Path: "path_v3", BuildOptions: "--opt2 val2"}, + }, + } t.Run("VersionDoesNotExist", func(t *testing.T) { _, err := settings.GetOptions(v1alpha1.ApplicationSource{ @@ -311,6 +372,15 @@ func TestKustomizeSettings_GetOptions(t *testing.T) { assert.Error(t, err) }) + t.Run("DefaultBuildOptions", func(t *testing.T) { + ver, err := settings.GetOptions(v1alpha1.ApplicationSource{}) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, "", ver.BinaryPath) + assert.Equal(t, "--opt1 val1", ver.BuildOptions) + }) + t.Run("VersionExists", func(t *testing.T) { ver, err := settings.GetOptions(v1alpha1.ApplicationSource{ Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v2"}}) @@ -318,6 +388,17 @@ func TestKustomizeSettings_GetOptions(t *testing.T) { return } assert.Equal(t, "path_v2", ver.BinaryPath) + assert.Equal(t, "", ver.BuildOptions) + }) + + t.Run("VersionExistsWithBuildOption", func(t *testing.T) { + ver, err := settings.GetOptions(v1alpha1.ApplicationSource{ + Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v3"}}) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, "path_v3", ver.BinaryPath) + assert.Equal(t, "--opt2 val2", ver.BuildOptions) }) }