From 448ecaeb28fb4c87b40cc7171786c2e639013d2d Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Tue, 12 Nov 2024 12:38:24 -0600 Subject: [PATCH 1/8] add image pull policy Signed-off-by: Filinto Duran --- pkg/kubernetes/run.go | 2 +- pkg/runfileconfig/run_file_config.go | 5 +++-- pkg/runfileconfig/run_file_config_parser.go | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/kubernetes/run.go b/pkg/kubernetes/run.go index 6a7199d64..b8869916e 100644 --- a/pkg/kubernetes/run.go +++ b/pkg/kubernetes/run.go @@ -297,7 +297,7 @@ func createDeploymentConfig(client versioned.Interface, app runfileconfig.App) d Name: app.AppID, Image: app.ContainerImage, Env: getEnv(app), - ImagePullPolicy: corev1.PullAlways, + ImagePullPolicy: corev1.PullPolicy(app.ContainerImagePullPolicy), }, }, }, diff --git a/pkg/runfileconfig/run_file_config.go b/pkg/runfileconfig/run_file_config.go index 50f17a977..575237600 100644 --- a/pkg/runfileconfig/run_file_config.go +++ b/pkg/runfileconfig/run_file_config.go @@ -41,8 +41,9 @@ type RunFileConfig struct { // ContainerConfiguration represents the application container configuration parameters. type ContainerConfiguration struct { - ContainerImage string `yaml:"containerImage"` - CreateService bool `yaml:"createService"` + ContainerImage string `yaml:"containerImage"` + ContainerImagePullPolicy string `yaml:"containerImagePullPolicy"` + CreateService bool `yaml:"createService"` } // App represents the configuration options for the apps in the run file. diff --git a/pkg/runfileconfig/run_file_config_parser.go b/pkg/runfileconfig/run_file_config_parser.go index 207bfd2fa..8a2c0a941 100644 --- a/pkg/runfileconfig/run_file_config_parser.go +++ b/pkg/runfileconfig/run_file_config_parser.go @@ -97,6 +97,15 @@ func (a *RunFileConfig) validateRunConfig(runFilePath string) error { if len(strings.TrimSpace(a.Apps[i].ResourcesPath)) > 0 { a.Apps[i].ResourcesPaths = append(a.Apps[i].ResourcesPaths, a.Apps[i].ResourcesPath) } + + // Check containerImagePullPolicy is valid + if a.Apps[i].ContainerImagePullPolicy != "" { + if a.Apps[i].ContainerImagePullPolicy != "Always" && a.Apps[i].ContainerImagePullPolicy != "Never" && a.Apps[i].ContainerImagePullPolicy != "IfNotPresent" { + return fmt.Errorf("invalid containerImagePullPolicy: %s", a.Apps[i].ContainerImagePullPolicy) + } + } else { + a.Apps[i].ContainerImagePullPolicy = "Always" + } } return nil } From e2f366e23fd9a9c4576826a53f3f0577556fce31 Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Tue, 12 Nov 2024 12:47:03 -0600 Subject: [PATCH 2/8] add allowed values Signed-off-by: Filinto Duran --- pkg/runfileconfig/run_file_config_parser.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser.go b/pkg/runfileconfig/run_file_config_parser.go index 8a2c0a941..0a1291ad9 100644 --- a/pkg/runfileconfig/run_file_config_parser.go +++ b/pkg/runfileconfig/run_file_config_parser.go @@ -100,8 +100,9 @@ func (a *RunFileConfig) validateRunConfig(runFilePath string) error { // Check containerImagePullPolicy is valid if a.Apps[i].ContainerImagePullPolicy != "" { - if a.Apps[i].ContainerImagePullPolicy != "Always" && a.Apps[i].ContainerImagePullPolicy != "Never" && a.Apps[i].ContainerImagePullPolicy != "IfNotPresent" { - return fmt.Errorf("invalid containerImagePullPolicy: %s", a.Apps[i].ContainerImagePullPolicy) + allowedValues := []string{"Always", "Never", "IfNotPresent"} + if !utils.Contains(allowedValues, a.Apps[i].ContainerImagePullPolicy) { + return fmt.Errorf("invalid containerImagePullPolicy: %s, allowed values: %v", a.Apps[i].ContainerImagePullPolicy, allowedValues) } } else { a.Apps[i].ContainerImagePullPolicy = "Always" From 9783d1b81670d14394902a0d3b33e4fba42bc5b5 Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 08:38:05 -0600 Subject: [PATCH 3/8] feedback refactor allowed values name Signed-off-by: Filinto Duran --- pkg/runfileconfig/run_file_config_parser.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser.go b/pkg/runfileconfig/run_file_config_parser.go index 0a1291ad9..30d401f57 100644 --- a/pkg/runfileconfig/run_file_config_parser.go +++ b/pkg/runfileconfig/run_file_config_parser.go @@ -26,6 +26,8 @@ import ( "gopkg.in/yaml.v2" ) +var imagePullPolicyValuesAllowed = []string{"Always", "Never", "IfNotPresent"} + // Parse the provided run file into a RunFileConfig struct. func (a *RunFileConfig) parseAppsConfig(runFilePath string) error { var err error @@ -100,9 +102,8 @@ func (a *RunFileConfig) validateRunConfig(runFilePath string) error { // Check containerImagePullPolicy is valid if a.Apps[i].ContainerImagePullPolicy != "" { - allowedValues := []string{"Always", "Never", "IfNotPresent"} - if !utils.Contains(allowedValues, a.Apps[i].ContainerImagePullPolicy) { - return fmt.Errorf("invalid containerImagePullPolicy: %s, allowed values: %v", a.Apps[i].ContainerImagePullPolicy, allowedValues) + if !utils.Contains(imagePullPolicyValuesAllowed, a.Apps[i].ContainerImagePullPolicy) { + return fmt.Errorf("invalid containerImagePullPolicy: %s, allowed values: %s", a.Apps[i].ContainerImagePullPolicy, strings.Join(imagePullPolicyValuesAllowed, ", ")) } } else { a.Apps[i].ContainerImagePullPolicy = "Always" From 4f9a6c254b5bbe7d2d3e1edf08fb7113268bb2ad Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 10:02:48 -0600 Subject: [PATCH 4/8] add unit tests Signed-off-by: Filinto Duran --- .../run_file_config_parser_test.go | 31 +++++++++++++++++++ ...un_config_container_image_pull_policy.yaml | 24 ++++++++++++++ ...g_container_image_pull_policy_invalid.yaml | 24 ++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml create mode 100644 pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml diff --git a/pkg/runfileconfig/run_file_config_parser_test.go b/pkg/runfileconfig/run_file_config_parser_test.go index 8fd009e73..f3acb6ea9 100644 --- a/pkg/runfileconfig/run_file_config_parser_test.go +++ b/pkg/runfileconfig/run_file_config_parser_test.go @@ -32,6 +32,9 @@ var ( runFileForPrecedenceRuleDaprDir = filepath.Join(".", "testdata", "test_run_config_precedence_rule_dapr_dir.yaml") runFileForLogDestination = filepath.Join(".", "testdata", "test_run_config_log_destination.yaml") runFileForMultiResourcePaths = filepath.Join(".", "testdata", "test_run_config_multiple_resources_paths.yaml") + + runFileForContainerImagePullPolicy = filepath.Join(".", "testdata", "test_run_config_container_image_pull_policy.yaml") + runFileForContainerImagePullPolicyInvalid = filepath.Join(".", "testdata", "test_run_config_container_image_pull_policy_invalid.yaml") ) func TestRunConfigFile(t *testing.T) { @@ -144,6 +147,34 @@ func TestRunConfigFile(t *testing.T) { } }) + t.Run("test containerImagePullPolicy", func(t *testing.T) { + t.Run("default value is Always", func(t *testing.T) { + config := RunFileConfig{} + config.parseAppsConfig(validRunFilePath) + err := config.validateRunConfig(runFileForPrecedenceRuleDaprDir) + assert.NoError(t, err) + assert.Equal(t, "Always", config.Apps[0].ContainerImagePullPolicy) + assert.Equal(t, "Always", config.Apps[1].ContainerImagePullPolicy) + }) + + t.Run("custom value is respected", func(t *testing.T) { + config := RunFileConfig{} + config.parseAppsConfig(runFileForContainerImagePullPolicy) + err := config.validateRunConfig(runFileForContainerImagePullPolicy) + assert.NoError(t, err) + assert.Equal(t, "IfNotPresent", config.Apps[0].ContainerImagePullPolicy) + assert.Equal(t, "Always", config.Apps[1].ContainerImagePullPolicy) + }) + + t.Run("invalid value is rejected", func(t *testing.T) { + config := RunFileConfig{} + config.parseAppsConfig(runFileForContainerImagePullPolicyInvalid) + err := config.validateRunConfig(runFileForContainerImagePullPolicyInvalid) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid containerImagePullPolicy: Invalid, allowed values: Always, Never, IfNotPresent") + }) + }) + t.Run("test precedence logic with daprInstallDir for resources-path and dapr config file", func(t *testing.T) { config := RunFileConfig{} diff --git a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml new file mode 100644 index 000000000..a2b19f112 --- /dev/null +++ b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml @@ -0,0 +1,24 @@ +version: 1 +common: + resourcesPath: ./app/resources + appProtocol: HTTP + appHealthProbeTimeout: 10 + env: + DEBUG: false + tty: sts +apps: + - appDirPath: ./webapp/ + resourcesPath: ./resources + configFilePath: ./config.yaml + appPort: 8080 + appHealthProbeTimeout: 1 + containerImagePullPolicy: IfNotPresent + containerImage: ghcr.io/dapr/dapr-workflows-python-sdk:latest + - appID: backend + appDirPath: ./backend/ + appProtocol: GRPC + appPort: 3000 + unixDomainSocket: /tmp/test-socket + env: + DEBUG: true + containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest \ No newline at end of file diff --git a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml new file mode 100644 index 000000000..d1aaf7030 --- /dev/null +++ b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml @@ -0,0 +1,24 @@ +version: 1 +common: + resourcesPath: ./app/resources + appProtocol: HTTP + appHealthProbeTimeout: 10 + env: + DEBUG: false + tty: sts +apps: + - appDirPath: ./webapp/ + resourcesPath: ./resources + configFilePath: ./config.yaml + appPort: 8080 + appHealthProbeTimeout: 1 + containerImagePullPolicy: Invalid + containerImage: ghcr.io/dapr/dapr-workflows-python-sdk:latest + - appID: backend + appDirPath: ./backend/ + appProtocol: GRPC + appPort: 3000 + unixDomainSocket: /tmp/test-socket + env: + DEBUG: true + containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest \ No newline at end of file From 2224c1a82806f907824dba9dfd8e2b149199f3ab Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 10:48:42 -0600 Subject: [PATCH 5/8] lint Signed-off-by: Filinto Duran --- .../run_file_config_parser_test.go | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser_test.go b/pkg/runfileconfig/run_file_config_parser_test.go index f3acb6ea9..7b564a646 100644 --- a/pkg/runfileconfig/run_file_config_parser_test.go +++ b/pkg/runfileconfig/run_file_config_parser_test.go @@ -148,31 +148,46 @@ func TestRunConfigFile(t *testing.T) { }) t.Run("test containerImagePullPolicy", func(t *testing.T) { - t.Run("default value is Always", func(t *testing.T) { - config := RunFileConfig{} - config.parseAppsConfig(validRunFilePath) - err := config.validateRunConfig(runFileForPrecedenceRuleDaprDir) - assert.NoError(t, err) - assert.Equal(t, "Always", config.Apps[0].ContainerImagePullPolicy) - assert.Equal(t, "Always", config.Apps[1].ContainerImagePullPolicy) - }) - - t.Run("custom value is respected", func(t *testing.T) { - config := RunFileConfig{} - config.parseAppsConfig(runFileForContainerImagePullPolicy) - err := config.validateRunConfig(runFileForContainerImagePullPolicy) - assert.NoError(t, err) - assert.Equal(t, "IfNotPresent", config.Apps[0].ContainerImagePullPolicy) - assert.Equal(t, "Always", config.Apps[1].ContainerImagePullPolicy) - }) + testcases := []struct { + name string + runfFile string + expectedPullPolicies []string + expectedErr bool + }{ + { + name: "default value is Always", + runfFile: validRunFilePath, + expectedPullPolicies: []string{"Always", "Always"}, + expectedErr: false, + }, + { + name: "custom value is respected", + runfFile: runFileForContainerImagePullPolicy, + expectedPullPolicies: []string{"IfNotPresent", "Always"}, + expectedErr: false, + }, + { + name: "invalid value is rejected", + runfFile: runFileForContainerImagePullPolicyInvalid, + expectedPullPolicies: []string{"Always", "Always"}, + expectedErr: true, + }, + } - t.Run("invalid value is rejected", func(t *testing.T) { - config := RunFileConfig{} - config.parseAppsConfig(runFileForContainerImagePullPolicyInvalid) - err := config.validateRunConfig(runFileForContainerImagePullPolicyInvalid) - assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid containerImagePullPolicy: Invalid, allowed values: Always, Never, IfNotPresent") - }) + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + config := RunFileConfig{} + config.parseAppsConfig(tc.runfFile) + err := config.validateRunConfig(tc.runfFile) + if tc.expectedErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid containerImagePullPolicy: Invalid, allowed values: Always, Never, IfNotPresent") + return + } + assert.Equal(t, tc.expectedPullPolicies[0], config.Apps[0].ContainerImagePullPolicy) + assert.Equal(t, tc.expectedPullPolicies[1], config.Apps[1].ContainerImagePullPolicy) + }) + } }) t.Run("test precedence logic with daprInstallDir for resources-path and dapr config file", func(t *testing.T) { From f591e74582fda5a2487fba671ecbc0cf8118e42a Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 10:51:10 -0600 Subject: [PATCH 6/8] lint Signed-off-by: Filinto Duran --- .../run_file_config_parser_test.go | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser_test.go b/pkg/runfileconfig/run_file_config_parser_test.go index 7b564a646..c39427bc6 100644 --- a/pkg/runfileconfig/run_file_config_parser_test.go +++ b/pkg/runfileconfig/run_file_config_parser_test.go @@ -14,6 +14,7 @@ limitations under the License. package runfileconfig import ( + "fmt" "os" "path/filepath" "strings" @@ -149,10 +150,11 @@ func TestRunConfigFile(t *testing.T) { t.Run("test containerImagePullPolicy", func(t *testing.T) { testcases := []struct { - name string - runfFile string - expectedPullPolicies []string - expectedErr bool + name string + runfFile string + expectedPullPolicies []string + expectedBadPolicyValue string + expectedErr bool }{ { name: "default value is Always", @@ -167,10 +169,11 @@ func TestRunConfigFile(t *testing.T) { expectedErr: false, }, { - name: "invalid value is rejected", - runfFile: runFileForContainerImagePullPolicyInvalid, - expectedPullPolicies: []string{"Always", "Always"}, - expectedErr: true, + name: "invalid value is rejected", + runfFile: runFileForContainerImagePullPolicyInvalid, + expectedPullPolicies: []string{"Always", "Always"}, + expectedBadPolicyValue: "Invalid", + expectedErr: true, }, } @@ -181,7 +184,7 @@ func TestRunConfigFile(t *testing.T) { err := config.validateRunConfig(tc.runfFile) if tc.expectedErr { assert.Error(t, err) - assert.Contains(t, err.Error(), "invalid containerImagePullPolicy: Invalid, allowed values: Always, Never, IfNotPresent") + assert.Contains(t, err.Error(), fmt.Sprintf("invalid containerImagePullPolicy: %s, allowed values: Always, Never, IfNotPresent", tc.expectedBadPolicyValue)) return } assert.Equal(t, tc.expectedPullPolicies[0], config.Apps[0].ContainerImagePullPolicy) From 6a422870aca2b63bbcb9c4eb5add9825a82da681 Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 11:03:37 -0600 Subject: [PATCH 7/8] more lint Signed-off-by: Filinto Duran --- .../run_file_config_parser_test.go | 90 +++++++++---------- ...un_config_container_image_pull_policy.yaml | 2 +- ...g_container_image_pull_policy_invalid.yaml | 2 +- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser_test.go b/pkg/runfileconfig/run_file_config_parser_test.go index c39427bc6..b2df4dd17 100644 --- a/pkg/runfileconfig/run_file_config_parser_test.go +++ b/pkg/runfileconfig/run_file_config_parser_test.go @@ -148,51 +148,6 @@ func TestRunConfigFile(t *testing.T) { } }) - t.Run("test containerImagePullPolicy", func(t *testing.T) { - testcases := []struct { - name string - runfFile string - expectedPullPolicies []string - expectedBadPolicyValue string - expectedErr bool - }{ - { - name: "default value is Always", - runfFile: validRunFilePath, - expectedPullPolicies: []string{"Always", "Always"}, - expectedErr: false, - }, - { - name: "custom value is respected", - runfFile: runFileForContainerImagePullPolicy, - expectedPullPolicies: []string{"IfNotPresent", "Always"}, - expectedErr: false, - }, - { - name: "invalid value is rejected", - runfFile: runFileForContainerImagePullPolicyInvalid, - expectedPullPolicies: []string{"Always", "Always"}, - expectedBadPolicyValue: "Invalid", - expectedErr: true, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - config := RunFileConfig{} - config.parseAppsConfig(tc.runfFile) - err := config.validateRunConfig(tc.runfFile) - if tc.expectedErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), fmt.Sprintf("invalid containerImagePullPolicy: %s, allowed values: Always, Never, IfNotPresent", tc.expectedBadPolicyValue)) - return - } - assert.Equal(t, tc.expectedPullPolicies[0], config.Apps[0].ContainerImagePullPolicy) - assert.Equal(t, tc.expectedPullPolicies[1], config.Apps[1].ContainerImagePullPolicy) - }) - } - }) - t.Run("test precedence logic with daprInstallDir for resources-path and dapr config file", func(t *testing.T) { config := RunFileConfig{} @@ -300,6 +255,51 @@ func TestRunConfigFile(t *testing.T) { }) } +func TestContainerImagePullPolicy(t *testing.T) { + testcases := []struct { + name string + runfFile string + expectedPullPolicies []string + expectedBadPolicyValue string + expectedErr bool + }{ + { + name: "default value is Always", + runfFile: validRunFilePath, + expectedPullPolicies: []string{"Always", "Always"}, + expectedErr: false, + }, + { + name: "custom value is respected", + runfFile: runFileForContainerImagePullPolicy, + expectedPullPolicies: []string{"IfNotPresent", "Always"}, + expectedErr: false, + }, + { + name: "invalid value is rejected", + runfFile: runFileForContainerImagePullPolicyInvalid, + expectedPullPolicies: []string{"Always", "Always"}, + expectedBadPolicyValue: "Invalid", + expectedErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + config := RunFileConfig{} + config.parseAppsConfig(tc.runfFile) + err := config.validateRunConfig(tc.runfFile) + if tc.expectedErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("invalid containerImagePullPolicy: %s, allowed values: Always, Never, IfNotPresent", tc.expectedBadPolicyValue)) + return + } + assert.Equal(t, tc.expectedPullPolicies[0], config.Apps[0].ContainerImagePullPolicy) + assert.Equal(t, tc.expectedPullPolicies[1], config.Apps[1].ContainerImagePullPolicy) + }) + } +} + func TestMultiResourcePathsResolution(t *testing.T) { config := RunFileConfig{} diff --git a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml index a2b19f112..606296e2f 100644 --- a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml +++ b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy.yaml @@ -21,4 +21,4 @@ apps: unixDomainSocket: /tmp/test-socket env: DEBUG: true - containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest \ No newline at end of file + containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest diff --git a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml index d1aaf7030..26c5c6b4c 100644 --- a/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml +++ b/pkg/runfileconfig/testdata/test_run_config_container_image_pull_policy_invalid.yaml @@ -21,4 +21,4 @@ apps: unixDomainSocket: /tmp/test-socket env: DEBUG: true - containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest \ No newline at end of file + containerImage: ghcr.io/dapr/dapr-workflows-csharp-sdk:latest From b39be8f0eea228ea8fbdd935ac6192ef1dc15458 Mon Sep 17 00:00:00 2001 From: Filinto Duran Date: Thu, 14 Nov 2024 11:04:25 -0600 Subject: [PATCH 8/8] more lint Signed-off-by: Filinto Duran --- pkg/runfileconfig/run_file_config_parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/runfileconfig/run_file_config_parser.go b/pkg/runfileconfig/run_file_config_parser.go index 30d401f57..c5a857b3e 100644 --- a/pkg/runfileconfig/run_file_config_parser.go +++ b/pkg/runfileconfig/run_file_config_parser.go @@ -100,7 +100,7 @@ func (a *RunFileConfig) validateRunConfig(runFilePath string) error { a.Apps[i].ResourcesPaths = append(a.Apps[i].ResourcesPaths, a.Apps[i].ResourcesPath) } - // Check containerImagePullPolicy is valid + // Check containerImagePullPolicy is valid. if a.Apps[i].ContainerImagePullPolicy != "" { if !utils.Contains(imagePullPolicyValuesAllowed, a.Apps[i].ContainerImagePullPolicy) { return fmt.Errorf("invalid containerImagePullPolicy: %s, allowed values: %s", a.Apps[i].ContainerImagePullPolicy, strings.Join(imagePullPolicyValuesAllowed, ", "))