diff --git a/integration/render_test.go b/integration/render_test.go index 7427e055820..95ac5776f7e 100644 --- a/integration/render_test.go +++ b/integration/render_test.go @@ -2050,6 +2050,7 @@ func TestRenderWithPostRenderHook(t *testing.T) { description string projectDir string expectedOutput string + expectedStderr string args []string }{ { @@ -2160,12 +2161,38 @@ spec: name: module2 `, }, + { + description: "single module project, one hook with changes, other without changes and with output", + projectDir: "testdata/post-render-hooks", + args: []string{"-m", "m1", "-p", "one-change-two-without-change-but-with-output", "-t", "customtag"}, + expectedOutput: `apiVersion: v1 +kind: Pod +metadata: + labels: + app1: before-change-1 + app2: after-change-2 + name: module1 +spec: + containers: + - image: us-central1-docker.pkg.dev/k8s-skaffold/testing/multi-config-module1:customtag + name: module1 +`, + expectedStderr: `Starting post-render hooks... +running post-render hook 1 +running post-render hook 2 +Completed post-render hooks`, + }, } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { - output := skaffold.Render(test.args...).InDir(test.projectDir).RunOrFailOutput(t.T) - t.CheckDeepEqual(test.expectedOutput, string(output), testutil.YamlObj(t.T)) + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + skaffold.Render(test.args...).InDir(test.projectDir).RunWithStdoutAndStderrOrFail(t.T, stdout, stderr) + t.CheckDeepEqual(test.expectedOutput, stdout.String(), testutil.YamlObj(t.T)) + if test.expectedStderr != "" { + t.CheckMatches(test.expectedStderr, stderr.String()) + } }) } } diff --git a/integration/skaffold/helper.go b/integration/skaffold/helper.go index 7340fbb6ffb..e816533f1fc 100644 --- a/integration/skaffold/helper.go +++ b/integration/skaffold/helper.go @@ -325,6 +325,28 @@ func (b *RunBuilder) RunOrFailOutput(t *testing.T) []byte { return out } +// RunWithStdoutAndStderrOrFail runs the Skaffold command copying the stdout and stderr to the given writers. +// Fails if there is an error. +func (b *RunBuilder) RunWithStdoutAndStderrOrFail(t *testing.T, stdout, stderr io.Writer) { + t.Helper() + + cmd := b.cmd(context.Background()) + cmd.Stdout = stdout + cmd.Stderr = stderr + t.Logf("Running %s in %s", cmd.Args, cmd.Dir) + + start := time.Now() + err := cmd.Run() + if err != nil { + if ee, ok := err.(*exec.ExitError); ok { + defer t.Errorf(string(ee.Stderr)) + } + t.Fatalf("skaffold %s: %v, %s", b.command, err, stderr) + } + + t.Logf("Ran %s in %v", cmd.Args, timeutil.Humanize(time.Since(start))) +} + func (b *RunBuilder) cmd(ctx context.Context) *exec.Cmd { args := []string{b.command} command := b.getCobraCommand() diff --git a/integration/testdata/post-render-hooks/module1/skaffold.yaml b/integration/testdata/post-render-hooks/module1/skaffold.yaml index 03817ee3d85..2138d6b8227 100644 --- a/integration/testdata/post-render-hooks/module1/skaffold.yaml +++ b/integration/testdata/post-render-hooks/module1/skaffold.yaml @@ -47,4 +47,18 @@ profiles: - host: command: - "sed" - - "s/before-change-1/after-change-1/g" \ No newline at end of file + - "s/before-change-1/after-change-1/g" + + - name: one-change-two-without-change-but-with-output + manifests: + hooks: + after: + - host: + command: ["sh", "-c", "echo running post-render hook 1"] + - host: + command: + - "sed" + - "s/before-change-2/after-change-2/g" + withChange: true + - host: + command: ["sh", "-c", "echo running post-render hook 2"] \ No newline at end of file diff --git a/pkg/skaffold/hooks/render.go b/pkg/skaffold/hooks/render.go index 986a686044b..85308eb727c 100644 --- a/pkg/skaffold/hooks/render.go +++ b/pkg/skaffold/hooks/render.go @@ -26,6 +26,7 @@ import ( "sync" "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/manifest" + "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output" "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log" "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest" ) @@ -69,8 +70,10 @@ func (r renderRunner) RunPreHooks(ctx context.Context, out io.Writer) error { } func (r renderRunner) RunPostHooks(ctx context.Context, list manifest.ManifestList, out io.Writer) (manifest.ManifestList, error) { + logWriter := log.GetWriter() + if len(r.PostHooks) > 0 { - log.Entry(context.TODO()).Errorf("Starting %s hooks...", phases.PostRender) + output.Default.Fprintln(logWriter, fmt.Sprintf("Starting %s hooks...", phases.PostRender)) } updated, err := manifest.Load(list.Reader()) if err != nil { @@ -100,14 +103,14 @@ func (r renderRunner) RunPostHooks(ctx context.Context, list manifest.ManifestLi return manifest.ManifestList{}, fmt.Errorf("failed to load manifest") } } else { - if err := hook.run(ctx, updated.Reader(), &b); err != nil && !errors.Is(err, &Skip{}) { + if err := hook.run(ctx, updated.Reader(), logWriter); err != nil && !errors.Is(err, &Skip{}) { return manifest.ManifestList{}, err } } } } if len(r.PostHooks) > 0 { - log.Entry(context.TODO()).Errorf("Completed %s hooks", phases.PostRender) + output.Default.Fprintln(logWriter, fmt.Sprintf("Completed %s hooks", phases.PostRender)) } return updated, nil } @@ -119,8 +122,10 @@ func (r renderRunner) getEnv() []string { } func (r renderRunner) run(ctx context.Context, out io.Writer, hooks []latest.RenderHookItem, phase phase) error { + logWriter := log.GetWriter() + if len(hooks) > 0 { - log.Entry(context.TODO()).Errorf("Starting %s hooks...", phase) + output.Default.Fprintln(logWriter, fmt.Sprintf("Starting %s hooks...", phase)) } env := r.getEnv() for _, h := range hooks { @@ -132,7 +137,7 @@ func (r renderRunner) run(ctx context.Context, out io.Writer, hooks []latest.Ren } } if len(hooks) > 0 { - log.Entry(context.TODO()).Errorf("Completed %s hooks...", phase) + output.Default.Fprintln(logWriter, fmt.Sprintf("Completed %s hooks...", phase)) } return nil } diff --git a/pkg/skaffold/output/log/log.go b/pkg/skaffold/output/log/log.go index e7fb9697015..9cb23fa9abd 100644 --- a/pkg/skaffold/output/log/log.go +++ b/pkg/skaffold/output/log/log.go @@ -122,6 +122,11 @@ func Entry(ctx context.Context) *logrus.Entry { }) } +// Returns the output used by the logger to write its content. +func GetWriter() io.Writer { + return logger.Out +} + // IsDebugLevelEnabled returns true if debug level log is enabled. func IsDebugLevelEnabled() bool { return logger.IsLevelEnabled(logrus.DebugLevel)