|
|
|
|
@ -1722,7 +1722,7 @@ metadata:
|
|
|
|
|
data:
|
|
|
|
|
key: value`,
|
|
|
|
|
expectedFiles: map[string]string{
|
|
|
|
|
"generated-by-postrender-0.yaml": `apiVersion: v1
|
|
|
|
|
"generated-by-postrender-test-0.yaml": `apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: test-cm
|
|
|
|
|
@ -1735,7 +1735,7 @@ data:
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
files, err := splitAndDeannotate(tt.input)
|
|
|
|
|
files, err := splitAndDeannotate(tt.input, "test")
|
|
|
|
|
|
|
|
|
|
if tt.expectedError != "" {
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
@ -1789,7 +1789,7 @@ data:
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Split and deannotate
|
|
|
|
|
reconstructed, err := splitAndDeannotate(merged)
|
|
|
|
|
reconstructed, err := splitAndDeannotate(merged, "test")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Compare the results
|
|
|
|
|
@ -1824,7 +1824,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
hooks, buf, notes, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
@ -1871,7 +1871,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
@ -1899,7 +1899,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
@ -1921,7 +1921,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
@ -1942,7 +1942,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
hooks, buf, notes, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
@ -1981,7 +1981,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
hooks, buf, notes, err := cfg.renderResources(
|
|
|
|
|
ch, values, "test-release", "", false, false, false,
|
|
|
|
|
nil, false, false, false,
|
|
|
|
|
nil, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
@ -1990,6 +1990,305 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
|
|
|
|
|
assert.Equal(t, "", notes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_DuplicateResourceInHookAndTemplate(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
// Simulate a chart where the same ServiceAccount appears both as a
|
|
|
|
|
// pre-install hook and as a regular template. This is a valid Helm pattern
|
|
|
|
|
// but previously caused post-renderers like Kustomize to fail with
|
|
|
|
|
// "may not add resource with an already registered id" because hooks and
|
|
|
|
|
// templates were merged into a single stream before post-rendering.
|
|
|
|
|
saHook := `apiVersion: v1
|
|
|
|
|
kind: ServiceAccount
|
|
|
|
|
metadata:
|
|
|
|
|
name: my-app
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install
|
|
|
|
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded`
|
|
|
|
|
|
|
|
|
|
saTemplate := `apiVersion: v1
|
|
|
|
|
kind: ServiceAccount
|
|
|
|
|
metadata:
|
|
|
|
|
name: my-app`
|
|
|
|
|
|
|
|
|
|
deployment := `apiVersion: apps/v1
|
|
|
|
|
kind: Deployment
|
|
|
|
|
metadata:
|
|
|
|
|
name: my-app
|
|
|
|
|
spec:
|
|
|
|
|
template:
|
|
|
|
|
spec:
|
|
|
|
|
serviceAccountName: my-app`
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/sa-hook.yaml", ModTime: modTime, Data: []byte(saHook)},
|
|
|
|
|
{Name: "templates/sa.yaml", ModTime: modTime, Data: []byte(saTemplate)},
|
|
|
|
|
{Name: "templates/deployment.yaml", ModTime: modTime, Data: []byte(deployment)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Use a post-renderer that rejects duplicate resource IDs, similar to
|
|
|
|
|
// how Kustomize behaves. We verify that no single post-render call
|
|
|
|
|
// receives the ServiceAccount twice.
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
count := strings.Count(content, "kind: ServiceAccount")
|
|
|
|
|
if count > 1 {
|
|
|
|
|
t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count)
|
|
|
|
|
}
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hooks, buf, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategySeparate,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Len(t, hooks, 1)
|
|
|
|
|
assert.Equal(t, "my-app", hooks[0].Name)
|
|
|
|
|
assert.Contains(t, buf.String(), "kind: Deployment")
|
|
|
|
|
assert.Contains(t, buf.String(), "kind: ServiceAccount")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_CombinedInvokesOnceWithEverything(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
hookManifest := `apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: hook-cm
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install`
|
|
|
|
|
templateManifest := `apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(hookManifest)},
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(templateManifest)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var calls int
|
|
|
|
|
var lastInput string
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
calls++
|
|
|
|
|
lastInput = content
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyCombined,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, 1, calls, "combined strategy should invoke the post-renderer exactly once")
|
|
|
|
|
assert.Contains(t, lastInput, "hook-cm")
|
|
|
|
|
assert.Contains(t, lastInput, "template-cm")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_ZeroValueStrategyActsAsCombined(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`)},
|
|
|
|
|
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: hook-cm
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var calls int
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
calls++
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategy(""),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, 1, calls, "unset strategy must preserve backwards-compatible combined behavior")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_SeparateSplitsHooksAndTemplates(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: hook-cm
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install`)},
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var inputs []string
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
inputs = append(inputs, content)
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategySeparate,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Len(t, inputs, 2, "separate strategy should invoke the post-renderer twice when both hooks and templates exist")
|
|
|
|
|
for _, in := range inputs {
|
|
|
|
|
hasHook := strings.Contains(in, "hook-cm")
|
|
|
|
|
hasTemplate := strings.Contains(in, "template-cm")
|
|
|
|
|
assert.False(t, hasHook && hasTemplate, "a single post-render invocation must not contain both hook and template resources")
|
|
|
|
|
assert.True(t, hasHook || hasTemplate, "each post-render invocation must contain either a hook or a template")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_SeparateWithOnlyTemplates(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var calls int
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
calls++
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategySeparate,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, 1, calls, "separate strategy should skip the empty hook group and invoke the post-renderer only once")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_NoHooksSkipsHooks(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: hook-cm
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install`)},
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var inputs []string
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
inputs = append(inputs, content)
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hooks, manifestDoc, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyNoHooks,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Len(t, inputs, 1, "nohooks strategy should invoke the post-renderer exactly once (for templates only)")
|
|
|
|
|
assert.NotContains(t, inputs[0], "hook-cm", "hooks must not be sent to the post-renderer")
|
|
|
|
|
assert.Contains(t, inputs[0], "template-cm", "templates must be sent to the post-renderer")
|
|
|
|
|
|
|
|
|
|
// Hooks still round-trip through the release so they can execute.
|
|
|
|
|
require.Len(t, hooks, 1)
|
|
|
|
|
assert.Contains(t, hooks[0].Manifest, "hook-cm")
|
|
|
|
|
assert.Contains(t, manifestDoc.String(), "template-cm")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_NoHooksWithOnlyHooks(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: hook-cm
|
|
|
|
|
annotations:
|
|
|
|
|
"helm.sh/hook": pre-install`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var calls int
|
|
|
|
|
mockPR := &mockPostRenderer{
|
|
|
|
|
transform: func(content string) string {
|
|
|
|
|
calls++
|
|
|
|
|
return content
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategyNoHooks,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, 0, calls, "nohooks strategy should not invoke the post-renderer when the chart only has hooks")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenderResources_PostRenderer_UnknownStrategyErrors(t *testing.T) {
|
|
|
|
|
cfg := actionConfigFixture(t)
|
|
|
|
|
|
|
|
|
|
modTime := time.Now()
|
|
|
|
|
ch := buildChartWithTemplates([]*common.File{
|
|
|
|
|
{Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1
|
|
|
|
|
kind: ConfigMap
|
|
|
|
|
metadata:
|
|
|
|
|
name: template-cm`)},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
mockPR := &mockPostRenderer{}
|
|
|
|
|
|
|
|
|
|
_, _, _, err := cfg.renderResources(
|
|
|
|
|
ch, nil, "test-release", "", false, false, false,
|
|
|
|
|
mockPR, false, false, false, PostRenderStrategy("bogus"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "unknown post-render strategy")
|
|
|
|
|
assert.Contains(t, err.Error(), "bogus")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDetermineReleaseSSAApplyMethod(t *testing.T) {
|
|
|
|
|
assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false))
|
|
|
|
|
assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true))
|
|
|
|
|
|