From 67b341d377cb34164a9d7d550d428a178da5e567 Mon Sep 17 00:00:00 2001 From: tuti Date: Fri, 27 Mar 2026 20:19:01 -0700 Subject: [PATCH] feat(release): wire release subcommands through summary and logging middleware - prep, build, publish: wrap actions with middleware.WithSummary (step labels release-prep / release-build / release-publish); refactor action signatures to (version, outputs, error). - Reshape middleware.WithLogging to wrap a cli.BeforeFunc instead of a cli.ActionFunc, so logging is configured before each Before runs (Before funcs themselves emit warnings worth capturing to the rotating log file). - Wrap every leaf command's Before with middleware.WithLogging (cut, validate, prep, build, publish, github, notes, from) and drop the inline middleware.ConfigureLogging calls from each Before body. - Drop the local hack/release/summary.go that pre-dated #4687 and duplicated internal/middleware. Move its WriteSummary tests into hack/release/internal/middleware/summary_test.go. - Add hack/release/_output to .gitignore. --- .gitignore | 2 +- hack/release/.gitignore | 2 + hack/release/branch.go | 10 +- hack/release/build.go | 24 +- hack/release/from.go | 4 +- hack/release/internal/middleware/logging.go | 8 +- hack/release/internal/middleware/summary.go | 29 +- .../internal/middleware/summary_test.go | 299 ++++++++++++++++++ hack/release/prep.go | 37 +-- hack/release/public.go | 4 +- hack/release/publish.go | 24 +- hack/release/releasenotes.go | 4 +- 12 files changed, 376 insertions(+), 71 deletions(-) create mode 100644 hack/release/.gitignore create mode 100644 hack/release/internal/middleware/summary_test.go diff --git a/.gitignore b/.gitignore index e86e77b7e9..85b827d401 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,4 @@ ut/ *.tar # Logs -*.log \ No newline at end of file +*.log diff --git a/hack/release/.gitignore b/hack/release/.gitignore new file mode 100644 index 0000000000..515972fda5 --- /dev/null +++ b/hack/release/.gitignore @@ -0,0 +1,2 @@ +# Release summary output +_output diff --git a/hack/release/branch.go b/hack/release/branch.go index a9631dcd67..e6321f4897 100644 --- a/hack/release/branch.go +++ b/hack/release/branch.go @@ -91,12 +91,12 @@ var branchCutCommand = &cli.Command{ localFlag, gitRemoteFlag, }, - Before: branchCutBefore, - Action: middleware.WithLogging(middleware.WithSummary("branch-cut", func(ctx context.Context, c *cli.Command) (string, map[string]any, error) { + Before: middleware.WithLogging(branchCutBefore), + Action: middleware.WithSummary("branch-cut", func(ctx context.Context, c *cli.Command) (string, map[string]any, error) { stream := c.String(streamFlag.Name) outputs, err := branchCutAction(ctx, c) return stream, outputs, err - })), + }), After: branchCutAfter, } @@ -463,8 +463,8 @@ var branchValidateCommand = &cli.Command{ }, githubTokenFlag, }, - Before: branchValidateBefore, - Action: middleware.WithLogging(middleware.WithSummary("branch-validate", branchValidateAction)), + Before: middleware.WithLogging(branchValidateBefore), + Action: middleware.WithSummary("branch-validate", branchValidateAction), } var branchValidateBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { diff --git a/hack/release/build.go b/hack/release/build.go index 703b798320..4c0410274e 100644 --- a/hack/release/build.go +++ b/hack/release/build.go @@ -74,8 +74,8 @@ var buildCommand = &cli.Command{ versionCheckFlag, extensionTimeoutFlag, }, - Before: buildBefore, - Action: buildAction, + Before: middleware.WithLogging(buildBefore), + Action: middleware.WithSummary("release-build", buildAction), After: buildAfter, } @@ -85,8 +85,6 @@ var buildCleanupFns []func(ctx context.Context) error // Pre-action for release build command. var buildBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { - middleware.ConfigureLogging(c) - // Start with a clean slate for build cleanup functions. buildCleanupFns = nil @@ -183,13 +181,13 @@ var buildBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (cont }) // Action for release build command. -var buildAction = cli.ActionFunc(func(ctx context.Context, c *cli.Command) error { +var buildAction = func(ctx context.Context, c *cli.Command) (string, map[string]any, error) { + version := c.String(versionFlag.Name) repoRootDir, err := command.GitDir() if err != nil { - return fmt.Errorf("getting git directory: %w", err) + return version, nil, fmt.Errorf("getting git directory: %w", err) } - version := c.String(versionFlag.Name) buildLog := logrus.WithField("version", version) // For hashrelease builds, skip if image is already published. @@ -198,7 +196,7 @@ var buildAction = cli.ActionFunc(func(ctx context.Context, c *cli.Command) error buildLog.WithError(err).Warn("Failed to check if image is already published, proceeding with build") } else if published { buildLog.Warn("Image is already published, skipping build") - return nil + return version, nil, nil } } @@ -231,7 +229,7 @@ var buildAction = cli.ActionFunc(func(ctx context.Context, c *cli.Command) error return nil }) if err := setupHashreleaseBuild(ctx, c, repoRootDir); err != nil { - return fmt.Errorf("preparing hashrelease build environment: %w", err) + return version, nil, fmt.Errorf("preparing hashrelease build environment: %w", err) } } else { buildLog = buildLog.WithField("release", true) @@ -242,14 +240,14 @@ var buildAction = cli.ActionFunc(func(ctx context.Context, c *cli.Command) error buildLog.Info("Building Operator") if out, err := command.MakeInDir(repoRootDir, "release-build", buildEnv...); err != nil { buildLog.Error(out) - return fmt.Errorf("building Operator: %w", err) + return version, nil, fmt.Errorf("building Operator: %w", err) } if err := assertOperatorImageVersion(registry, image, version); err != nil { - return fmt.Errorf("asserting operator image version: %w", err) + return version, nil, fmt.Errorf("asserting operator image version: %w", err) } listImages(registry, image, version) - return nil -}) + return version, nil, nil +} // runBuildCleanup runs all registered cleanup functions in reverse order (LIFO), // logging each failure individually. It returns the joined errors and resets the slice. diff --git a/hack/release/from.go b/hack/release/from.go index c0b64c59ba..208a263719 100644 --- a/hack/release/from.go +++ b/hack/release/from.go @@ -44,14 +44,12 @@ var releaseFromCommand = &cli.Command{ devTagSuffixFlag, skipValidationFlag, }, - Before: releaseFromBefore, + Before: middleware.WithLogging(releaseFromBefore), Action: releaseFromAction, } // Pre-action for "release from" command. var releaseFromBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { - middleware.ConfigureLogging(c) - if c.Bool(skipValidationFlag.Name) { logrus.Warnf("Skipping %s validation as requested.", c.Name) return ctx, nil diff --git a/hack/release/internal/middleware/logging.go b/hack/release/internal/middleware/logging.go index a3d557e57d..d29669d8e2 100644 --- a/hack/release/internal/middleware/logging.go +++ b/hack/release/internal/middleware/logging.go @@ -70,11 +70,11 @@ func ConfigureLogging(c *cli.Command) { logrus.AddHook(rotateFileHook) } -// WithLogging wraps a cli.ActionFunc with automatic log file configuration +// WithLogging wraps a cli.BeforeFunc with automatic log file configuration // derived from the command's full name (e.g. "release branch" -> "release-branch.log"). -func WithLogging(action cli.ActionFunc) cli.ActionFunc { - return func(ctx context.Context, c *cli.Command) error { +func WithLogging(before cli.BeforeFunc) cli.BeforeFunc { + return func(ctx context.Context, c *cli.Command) (context.Context, error) { ConfigureLogging(c) - return action(ctx, c) + return before(ctx, c) } } diff --git a/hack/release/internal/middleware/summary.go b/hack/release/internal/middleware/summary.go index 463879a3ba..6516894b4b 100644 --- a/hack/release/internal/middleware/summary.go +++ b/hack/release/internal/middleware/summary.go @@ -31,7 +31,9 @@ import ( const ReleaseDir = "hack/release" // StepSummary represents structured output for a release step. -// Written to /hack/release/_output/summary//.yaml. +// Written to /hack/release/_output/summary//.yaml, +// where is a release version (e.g. v1.36.0) for release-* steps +// or a stream identifier (e.g. v1.36) for branch-* steps. type StepSummary struct { Status string `yaml:"status"` Started time.Time `yaml:"started"` @@ -44,9 +46,10 @@ func SummaryOutputDir(repoRootDir string) string { return filepath.Join(repoRootDir, ReleaseDir, "_output") } -// WriteSummary writes a summary YAML file to /summary//.yaml. -func WriteSummary(baseDir, version, step string, s StepSummary) error { - dir := filepath.Join(baseDir, "summary", version) +// WriteSummary writes a summary YAML file to /summary//.yaml. +// key is a release version or stream identifier; step is the step name. +func WriteSummary(baseDir, key, step string, s StepSummary) error { + dir := filepath.Join(baseDir, "summary", key) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("creating output dir: %w", err) } @@ -57,22 +60,28 @@ func WriteSummary(baseDir, version, step string, s StepSummary) error { return os.WriteFile(filepath.Join(dir, step+".yaml"), data, 0o644) } -// SummaryAction is a command action that returns a version string, +// SummaryAction is a command action that returns a key (version or stream), // structured outputs, and an error. type SummaryAction func(context.Context, *cli.Command) (string, map[string]any, error) // WithSummary wraps a SummaryAction with timing, status, and summary file emission. func WithSummary(step string, action SummaryAction) cli.ActionFunc { + return withSummary(step, action, command.GitDir) +} + +// withSummary is the testable core of WithSummary, parameterized over the +// repo-root resolver so tests can inject a temp dir. +func withSummary(step string, action SummaryAction, repoRootFn func() (string, error)) cli.ActionFunc { return func(ctx context.Context, c *cli.Command) error { started := time.Now() - ver, outputsMap, actionErr := action(ctx, c) + key, outputsMap, actionErr := action(ctx, c) status := "success" if actionErr != nil { status = "failure" } - if ver == "" { - ver = "unknown" + if key == "" { + key = "unknown" } summary := StepSummary{ Status: status, @@ -80,13 +89,13 @@ func WithSummary(step string, action SummaryAction) cli.ActionFunc { Completed: time.Now(), Outputs: outputsMap, } - repoRoot, err := command.GitDir() + repoRoot, err := repoRootFn() if err != nil { logrus.WithError(err).Warn("Failed to determine repo root for summary") return actionErr } outputDir := SummaryOutputDir(repoRoot) - if err := WriteSummary(outputDir, ver, step, summary); err != nil { + if err := WriteSummary(outputDir, key, step, summary); err != nil { logrus.WithError(err).Warn("Failed to write summary file") } return actionErr diff --git a/hack/release/internal/middleware/summary_test.go b/hack/release/internal/middleware/summary_test.go new file mode 100644 index 0000000000..b2d2a98b93 --- /dev/null +++ b/hack/release/internal/middleware/summary_test.go @@ -0,0 +1,299 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package middleware + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" + + "github.com/urfave/cli/v3" + "gopkg.in/yaml.v3" +) + +func TestWriteSummary(t *testing.T) { + baseDir := filepath.Join(t.TempDir(), "hack", "release", "_output") + + started := time.Date(2026, 1, 15, 10, 0, 0, 0, time.UTC) + completed := time.Date(2026, 1, 15, 10, 5, 0, 0, time.UTC) + + s := StepSummary{ + Status: "success", + Started: started, + Completed: completed, + Outputs: map[string]any{ + "branch": "build-v1.36.0", + }, + } + + if err := WriteSummary(baseDir, "v1.36.0", "release-prep", s); err != nil { + t.Fatalf("WriteSummary() error = %v", err) + } + + outPath := filepath.Join(baseDir, "summary", "v1.36.0", "release-prep.yaml") + data, err := os.ReadFile(outPath) + if err != nil { + t.Fatalf("failed to read summary file: %v", err) + } + + var got StepSummary + if err := yaml.Unmarshal(data, &got); err != nil { + t.Fatalf("failed to unmarshal summary: %v", err) + } + + if got.Status != "success" { + t.Errorf("Status = %q, want %q", got.Status, "success") + } + if !got.Started.Equal(started) { + t.Errorf("Started = %v, want %v", got.Started, started) + } + if !got.Completed.Equal(completed) { + t.Errorf("Completed = %v, want %v", got.Completed, completed) + } + if got.Outputs["branch"] != "build-v1.36.0" { + t.Errorf("Outputs[branch] = %v, want %q", got.Outputs["branch"], "build-v1.36.0") + } +} + +func TestWriteSummary_CreatesDirectory(t *testing.T) { + baseDir := filepath.Join(t.TempDir(), "nested", "output") + + s := StepSummary{ + Status: "success", + } + + if err := WriteSummary(baseDir, "v1.36.0", "release-build", s); err != nil { + t.Fatalf("WriteSummary() error = %v", err) + } + + outPath := filepath.Join(baseDir, "summary", "v1.36.0", "release-build.yaml") + if _, err := os.Stat(outPath); os.IsNotExist(err) { + t.Error("expected summary file to be created") + } +} + +func TestWriteSummary_FailureStatus(t *testing.T) { + baseDir := filepath.Join(t.TempDir(), "_output") + + s := StepSummary{ + Status: "failure", + } + + if err := WriteSummary(baseDir, "v1.36.0", "release-publish", s); err != nil { + t.Fatalf("WriteSummary() error = %v", err) + } + + outPath := filepath.Join(baseDir, "summary", "v1.36.0", "release-publish.yaml") + data, err := os.ReadFile(outPath) + if err != nil { + t.Fatalf("failed to read summary file: %v", err) + } + + var got StepSummary + if err := yaml.Unmarshal(data, &got); err != nil { + t.Fatalf("failed to unmarshal summary: %v", err) + } + if got.Status != "failure" { + t.Errorf("Status = %q, want %q", got.Status, "failure") + } +} + +func TestWriteSummary_OmitsEmptyOutputs(t *testing.T) { + baseDir := filepath.Join(t.TempDir(), "_output") + + s := StepSummary{ + Status: "success", + } + + if err := WriteSummary(baseDir, "v1.36.0", "release-build", s); err != nil { + t.Fatalf("WriteSummary() error = %v", err) + } + + outPath := filepath.Join(baseDir, "summary", "v1.36.0", "release-build.yaml") + data, err := os.ReadFile(outPath) + if err != nil { + t.Fatalf("failed to read summary file: %v", err) + } + + var m map[string]any + if err := yaml.Unmarshal(data, &m); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + if _, ok := m["outputs"]; ok { + t.Error("expected 'outputs' to be omitted when empty") + } +} + +func TestWithSummary(t *testing.T) { + tests := []struct { + name string + actionKey string + actionOutputs map[string]any + actionErr error + repoRootErr error + wantStatus string + wantKey string // key used in the on-disk path + wantFileWritten bool // whether a summary file should exist + wantReturnedErr bool // whether WithSummary should return an error + wantHasOutputs bool // whether the YAML contains an outputs key + }{ + { + name: "success with outputs", + actionKey: "v1.36.0", + actionOutputs: map[string]any{"branch": "build-v1.36.0"}, + actionErr: nil, + wantStatus: "success", + wantKey: "v1.36.0", + wantFileWritten: true, + wantReturnedErr: false, + wantHasOutputs: true, + }, + { + name: "failure status when action errors", + actionKey: "v1.36.0", + actionErr: errors.New("boom"), + wantStatus: "failure", + wantKey: "v1.36.0", + wantFileWritten: true, + wantReturnedErr: true, + }, + { + name: "empty key falls back to unknown", + actionKey: "", + actionErr: errors.New("early exit"), + wantStatus: "failure", + wantKey: "unknown", + wantFileWritten: true, + wantReturnedErr: true, + }, + { + name: "repo-root resolver failure does not mask action error", + actionKey: "v1.36.0", + actionErr: errors.New("action failed"), + repoRootErr: errors.New("not a git repo"), + wantFileWritten: false, + wantReturnedErr: true, + }, + { + name: "repo-root resolver failure on success path returns nil", + actionKey: "v1.36.0", + actionErr: nil, + repoRootErr: errors.New("not a git repo"), + wantFileWritten: false, + wantReturnedErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + repoRoot := t.TempDir() + repoRootFn := func() (string, error) { + if tc.repoRootErr != nil { + return "", tc.repoRootErr + } + return repoRoot, nil + } + + action := func(_ context.Context, _ *cli.Command) (string, map[string]any, error) { + return tc.actionKey, tc.actionOutputs, tc.actionErr + } + + err := withSummary("release-prep", action, repoRootFn)(context.Background(), &cli.Command{}) + + if tc.wantReturnedErr && err == nil { + t.Errorf("expected error, got nil") + } + if !tc.wantReturnedErr && err != nil { + t.Errorf("expected nil error, got %v", err) + } + if tc.wantReturnedErr && err != nil && !errors.Is(err, tc.actionErr) { + t.Errorf("expected error to wrap action error %v, got %v", tc.actionErr, err) + } + + outPath := filepath.Join(repoRoot, ReleaseDir, "_output", "summary", tc.wantKey, "release-prep.yaml") + _, statErr := os.Stat(outPath) + + if tc.wantFileWritten && statErr != nil { + t.Fatalf("expected summary file at %s, stat err: %v", outPath, statErr) + } + if !tc.wantFileWritten && statErr == nil { + t.Fatalf("expected no summary file at %s, but it exists", outPath) + } + if !tc.wantFileWritten { + return + } + + data, readErr := os.ReadFile(outPath) + if readErr != nil { + t.Fatalf("read summary: %v", readErr) + } + var got StepSummary + if err := yaml.Unmarshal(data, &got); err != nil { + t.Fatalf("unmarshal summary: %v", err) + } + if got.Status != tc.wantStatus { + t.Errorf("Status = %q, want %q", got.Status, tc.wantStatus) + } + if got.Started.IsZero() { + t.Error("Started is zero time") + } + if got.Completed.Before(got.Started) { + t.Errorf("Completed (%v) is before Started (%v)", got.Completed, got.Started) + } + + var raw map[string]any + if err := yaml.Unmarshal(data, &raw); err != nil { + t.Fatalf("unmarshal raw: %v", err) + } + _, hasOutputs := raw["outputs"] + if hasOutputs != tc.wantHasOutputs { + t.Errorf("outputs key present = %v, want %v", hasOutputs, tc.wantHasOutputs) + } + }) + } +} + +func TestWithSummary_WriteFailureDoesNotMaskActionResult(t *testing.T) { + // Point the repo root at a path where MkdirAll will fail (a regular file). + tmp := t.TempDir() + notADir := filepath.Join(tmp, "blocker") + if err := os.WriteFile(notADir, []byte{}, 0o644); err != nil { + t.Fatalf("setup: %v", err) + } + repoRootFn := func() (string, error) { return notADir, nil } + + t.Run("action success is preserved when summary write fails", func(t *testing.T) { + action := func(_ context.Context, _ *cli.Command) (string, map[string]any, error) { + return "v1.36.0", nil, nil + } + if err := withSummary("release-prep", action, repoRootFn)(context.Background(), &cli.Command{}); err != nil { + t.Errorf("expected nil error from successful action, got %v", err) + } + }) + + t.Run("action error is preserved when summary write fails", func(t *testing.T) { + actionErr := errors.New("action failed") + action := func(_ context.Context, _ *cli.Command) (string, map[string]any, error) { + return "v1.36.0", nil, actionErr + } + err := withSummary("release-prep", action, repoRootFn)(context.Background(), &cli.Command{}) + if !errors.Is(err, actionErr) { + t.Errorf("expected action error to be preserved, got %v", err) + } + }) +} diff --git a/hack/release/prep.go b/hack/release/prep.go index 564aa5f089..10b350cc7a 100644 --- a/hack/release/prep.go +++ b/hack/release/prep.go @@ -53,7 +53,7 @@ to point to local repositories for Calico and Enterprise respectively.`, githubTokenFlag, localFlag, }, - Before: prepBefore, + Before: middleware.WithLogging(prepBefore), Action: prepAction, After: branchCutAfter, } @@ -181,29 +181,27 @@ var prepBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (conte }) // Action executed for release prep command. -var prepAction = middleware.WithLogging(func(ctx context.Context, c *cli.Command) error { +var prepAction = middleware.WithSummary("release-prep", func(ctx context.Context, c *cli.Command) (string, map[string]any, error) { + version := c.String(versionFlag.Name) baseBranch, err := contextString(ctx, baseBranchCtxKey) if err != nil { - return err - } - version, err := contextString(ctx, versionCtxKey) - if err != nil { - return err + return version, nil, err } prepBranch, err := contextString(ctx, branchNameCtxKey) if err != nil { - return err + return version, nil, err } repoRootDir, err := branchCutActionCommon(ctx, c, nil, fmt.Sprintf("build: %s release", version)) if err != nil { - return err + return version, nil, err } + outputs := map[string]any{"branch": prepBranch} // If local flag is set, skip pushing prep branch and creating PR if c.Bool(localFlag.Name) { logrus.WithField("branch", prepBranch).Warn("Local flag set, no remote changes will be made") logrus.Infof("Branch for releasing %s (%s) is ready to be pushed and a PR created", version, prepBranch) - return nil + return version, outputs, nil } // Push branch to remote @@ -211,23 +209,23 @@ var prepAction = middleware.WithLogging(func(ctx context.Context, c *cli.Command logrus.Debugf("Pushing branch %s to %s", prepBranch, gitRemote) if out, err := command.Git("push", "--force", "--set-upstream", gitRemote, prepBranch); err != nil { logrus.Error(out) - return fmt.Errorf("error pushing branch %s to remote %s: %w", prepBranch, gitRemote, err) + return version, outputs, fmt.Errorf("error pushing branch %s to remote %s: %w", prepBranch, gitRemote, err) } // Attempt to create PR for the release prep branch remoteURL, err := command.Git("config", "--get", fmt.Sprintf("remote.%s.url", gitRemote)) if err != nil { - return fmt.Errorf("error getting remote URL for %s: %w", gitRemote, err) + return version, outputs, fmt.Errorf("error getting remote URL for %s: %w", gitRemote, err) } githubUser := strings.Split(remoteURL[strings.Index(remoteURL, "git@github.com:")+len("git@github.com:"):strings.LastIndex(remoteURL, ".git")], "/")[0] githubOrg, err := contextString(ctx, githubOrgCtxKey) if err != nil { - return err + return version, outputs, err } githubRepo, err := contextString(ctx, githubRepoCtxKey) if err != nil { - return err + return version, outputs, err } headBranch := prepBranch if githubUser != githubOrg { @@ -255,7 +253,7 @@ var prepAction = middleware.WithLogging(func(ctx context.Context, c *cli.Command logrus.WithField("args", strings.Join(args, " ")).Debug("Creating PR for release preparation") if pr, err := command.RunInDir(repoRootDir, "hack/bin/gh", args, nil); err != nil { if !strings.Contains(err.Error(), "already exists") { - return fmt.Errorf("failed to create PR: %w", err) + return version, outputs, fmt.Errorf("failed to create PR: %w", err) } logrus.Warnf("PR already exists. Find PR at: https://github.com/%s/%s/pulls?q=is%%3Aopen+head%%3A%s", githubOrg, githubRepo, prepBranch) } else { @@ -264,9 +262,12 @@ var prepAction = middleware.WithLogging(func(ctx context.Context, c *cli.Command // Skip milestone management if requested or if using a forked repo if c.Bool(skipMilestoneFlag.Name) { - return nil + return version, outputs, nil } else if c.String(gitRepoFlag.Name) != mainRepo && !c.Bool(skipRepoCheckFlag.Name) { - return fmt.Errorf("cannot manage milestones when forked repo (%s); either use the main repo (%s) or set flag to skip repo check", c.String(gitRepoFlag.Name), mainRepo) + return version, outputs, fmt.Errorf("cannot manage milestones when forked repo (%s); either use the main repo (%s) or set flag to skip repo check", c.String(gitRepoFlag.Name), mainRepo) + } + if err := manageStreamMilestone(ctx, c.String(githubTokenFlag.Name)); err != nil { + return version, outputs, err } - return manageStreamMilestone(ctx, c.String(githubTokenFlag.Name)) + return version, outputs, nil }) diff --git a/hack/release/public.go b/hack/release/public.go index 0e3215700f..349bad2690 100644 --- a/hack/release/public.go +++ b/hack/release/public.go @@ -35,13 +35,11 @@ var publicCommand = &cli.Command{ githubTokenFlag, skipValidationFlag, }, - Before: publicBefore, + Before: middleware.WithLogging(publicBefore), Action: publicAction, } var publicBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { - middleware.ConfigureLogging(c) - var err error ctx, err = addRepoInfoToCtx(ctx, c.String(gitRepoFlag.Name)) if err != nil { diff --git a/hack/release/publish.go b/hack/release/publish.go index 70a4b6e75c..a4f47c0472 100644 --- a/hack/release/publish.go +++ b/hack/release/publish.go @@ -47,15 +47,13 @@ var publishCommand = &cli.Command{ githubTokenFlag, draftGithubReleaseFlag, }, - Before: publishBefore, - Action: publishAction, + Before: middleware.WithLogging(publishBefore), + Action: middleware.WithSummary("release-publish", publishAction), } // Pre-action for publish command. // It configures logging and performs validations. var publishBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { - middleware.ConfigureLogging(c) - var err error ctx, err = addRepoInfoToCtx(ctx, c.String(gitRepoFlag.Name)) @@ -91,29 +89,33 @@ var publishBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (co }) // Action for publish command. -var publishAction = cli.ActionFunc(func(ctx context.Context, c *cli.Command) error { +var publishAction = func(ctx context.Context, c *cli.Command) (string, map[string]any, error) { + version := c.String(versionFlag.Name) repoRootDir, err := command.GitDir() if err != nil { - return fmt.Errorf("getting git directory: %w", err) + return version, nil, fmt.Errorf("getting git directory: %w", err) } // Publish images if err := publishImages(c, repoRootDir); err != nil { - return err + return version, nil, err } // Only images are published for hashrelease builds. if c.Bool(hashreleaseFlag.Name) { - return nil + return version, nil, nil } // Publish GitHub release if requested if !c.Bool(createGithubReleaseFlag.Name) { logrus.Warnf("Skipping GitHub release creation. Either use %q to create a GitHub release or create manually.", publicCommand.FullName()) - return nil + return version, nil, nil } - return publishGithubRelease(ctx, c, repoRootDir) -}) + if err := publishGithubRelease(ctx, c, repoRootDir); err != nil { + return version, nil, err + } + return version, nil, nil +} // publishImages publishes the operator images to the specified registry. // If the images are already published, it skips publishing. diff --git a/hack/release/releasenotes.go b/hack/release/releasenotes.go index 3f81420970..db61c14e2b 100644 --- a/hack/release/releasenotes.go +++ b/hack/release/releasenotes.go @@ -39,14 +39,12 @@ Otherwise, use --local flag to generate release notes based on local versions fi localFlag, skipValidationFlag, }, - Before: releaseNotesBefore, + Before: middleware.WithLogging(releaseNotesBefore), Action: releaseNotesAction, } // Pre-action for "release notes" command. var releaseNotesBefore = cli.BeforeFunc(func(ctx context.Context, c *cli.Command) (context.Context, error) { - middleware.ConfigureLogging(c) - var err error ctx, err = addRepoInfoToCtx(ctx, c.String(gitRepoFlag.Name)) if err != nil {