mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
[VAULT-34829] pipeline(backport): add github create backport command (#30713)
Add a new `github create backport` sub-command that can create a
backport of a given pull request. The command has been designed around a
Github Actions workflow where it is triggered on a closed pull request
event with a guard that checks for merges:
```yaml
pull_request_target:
types: closed
jobs:
backport:
if: github.even.pull_request.merged
runs-on: "..."
```
Eventually this sub-command (or another similar one) can be used to
implemente backporting a CE pull request to the corresponding ce/*
branch in vault-enterprise. This functionality will be implemented in
VAULT-34827.
This backport runner has several new behaviors not present in the
existing backport assistant:
- If the source PR was made against an enterprise branch we'll assume
that we want create a CE backport.
- Enterprise only files will be automatically _removed_ from the CE
backport for you. This will not guarantee a working CE pull request
but does quite a bit of the heavy lifting for you.
- If the change only contains enterprise files we'll skip creating a
CE backport.
- If the corresponding CE branch is inactive (as defined in
.release/versions.hcl) then we will skip creating a backport in most
cases. The exceptions are changes that include docs, README, or
pipeline changes as we assume that even active branches will want
those changes.
- Backport labels still work but _only_ to enterprise PR's. It is
assumed that when the subsequent PRs are merged that their
corresponding CE backports will be created.
- Backport labels no longer include editions. They will now use the
same schema as active versions defined .release/verions.hcl. E.g.
`backport/1.19.x`. `main` is always assumed to be active.
- The runner will always try and update the source PR with a Github
comment regarding the status of each individual backport. Even if
one attempt at backporting fails we'll continue until we've
attempted all backports.
Signed-off-by: Ryan Cragun <me@ryan.ec>
This commit is contained in:
parent
689ede2da5
commit
025a6d5071
11 changed files with 2384 additions and 9 deletions
|
|
@ -11,6 +11,7 @@ require (
|
|||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/veqryn/slog-context v0.7.0
|
||||
github.com/zclconf/go-cty v1.16.2
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -56,7 +57,6 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/zclconf/go-cty v1.16.2 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
|
|
|
|||
|
|
@ -31,14 +31,6 @@ func newGithubCmd() *cobra.Command {
|
|||
Long: "Github commands",
|
||||
}
|
||||
github.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if parent := cmd.Parent(); parent != nil {
|
||||
if parent.PersistentPreRunE != nil {
|
||||
err := parent.PersistentPreRunE(parent, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if token, set := os.LookupEnv("GITHUB_TOKEN"); set {
|
||||
githubCmdState.Github = githubCmdState.Github.WithAuthToken(token)
|
||||
} else {
|
||||
|
|
@ -46,6 +38,7 @@ func newGithubCmd() *cobra.Command {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
github.AddCommand(newGithubCreateCmd())
|
||||
github.AddCommand(newGithubListCmd())
|
||||
|
||||
return github
|
||||
|
|
|
|||
19
tools/pipeline/internal/cmd/github_create.go
Normal file
19
tools/pipeline/internal/cmd/github_create.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newGithubCreateCmd() *cobra.Command {
|
||||
create := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Github create commands",
|
||||
Long: "Github create commands",
|
||||
}
|
||||
create.AddCommand(newGithubCreateBackportCmd())
|
||||
|
||||
return create
|
||||
}
|
||||
116
tools/pipeline/internal/cmd/github_create_backport.go
Normal file
116
tools/pipeline/internal/cmd/github_create_backport.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/changed"
|
||||
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/github"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var createGithubBackportState struct {
|
||||
req github.CreateBackportReq
|
||||
ceExclude []string
|
||||
ceAllowInactive []string
|
||||
}
|
||||
|
||||
func newGithubCreateBackportCmd() *cobra.Command {
|
||||
listRuns := &cobra.Command{
|
||||
Use: "backport 1234",
|
||||
Short: "Create a backport pull request from another pull request",
|
||||
Long: "Create a backport pull request from another pull request",
|
||||
RunE: runCreateGithubBackportCmd,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
switch len(args) {
|
||||
case 1:
|
||||
pr, err := strconv.ParseUint(args[0], 10, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pull number: %s: %w", args[0], err)
|
||||
}
|
||||
if pr <= math.MaxUint32 {
|
||||
createGithubBackportState.req.PullNumber = uint(pr)
|
||||
} else {
|
||||
return fmt.Errorf("invalid pull number: %s: number is too large", args[0])
|
||||
}
|
||||
return nil
|
||||
case 0:
|
||||
return errors.New("no pull request number has been provided")
|
||||
default:
|
||||
return fmt.Errorf("invalid arguments: only pull request number is expected, received %d arguments: %v", len(args), args)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
listRuns.PersistentFlags().StringSliceVarP(&createGithubBackportState.ceAllowInactive, "ce-allow-inactive-groups", "a", []string{"docs", "changelog", "pipeline"}, "Change file groups that should be allowed to backport to inactive CE branches")
|
||||
listRuns.PersistentFlags().StringVar(&createGithubBackportState.req.CEBranchPrefix, "ce-branch-prefix", "ce", "The branch name prefix")
|
||||
listRuns.PersistentFlags().StringSliceVarP(&createGithubBackportState.ceExclude, "ce-exclude-groups", "e", []string{"enterprise"}, "Change file groups that should be excluded from the backporting to CE branches")
|
||||
listRuns.PersistentFlags().StringVar(&createGithubBackportState.req.BaseOrigin, "base-origin", "origin", "The name to use for the base remote origin")
|
||||
listRuns.PersistentFlags().StringVarP(&createGithubBackportState.req.Owner, "owner", "o", "hashicorp", "The Github organization")
|
||||
listRuns.PersistentFlags().StringVarP(&createGithubBackportState.req.Repo, "repo", "r", "vault-enterprise", "The Github repository. Private repositories require auth via a GITHUB_TOKEN env var")
|
||||
listRuns.PersistentFlags().StringVarP(&createGithubBackportState.req.RepoDir, "repo-dir", "d", "", "The path to the vault repository dir. If not set a temporary directory will be used")
|
||||
listRuns.PersistentFlags().StringVarP(&createGithubBackportState.req.ReleaseVersionConfigPath, "releases-version-path", "m", "", "The path to .release/versions.hcl")
|
||||
listRuns.PersistentFlags().UintVar(&createGithubBackportState.req.ReleaseRecurseDepth, "recurse", 3, "If no path to a config file is given, recursively search backwards for it and stop at root or until we've his the configured depth.")
|
||||
|
||||
// NOTE: The following are technically flags but they only for testing testing
|
||||
// the command before we cut over to new utility.
|
||||
listRuns.PersistentFlags().StringVar(&createGithubBackportState.req.EntBranchPrefix, "ent-branch-prefix", "", "The ent branch name prefix. Only used for testing before migration to the new workflow")
|
||||
listRuns.PersistentFlags().StringVar(&createGithubBackportState.req.BackportLabelPrefix, "backport-label-prefix", "backport", "The name to use for the base remote origin")
|
||||
|
||||
err := listRuns.PersistentFlags().MarkHidden("ent-branch-prefix")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = listRuns.PersistentFlags().MarkHidden("backport-label-prefix")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return listRuns
|
||||
}
|
||||
|
||||
func runCreateGithubBackportCmd(cmd *cobra.Command, args []string) error {
|
||||
cmd.SilenceUsage = true // Don't spam the usage on failure
|
||||
|
||||
for i, ig := range createGithubBackportState.ceAllowInactive {
|
||||
if i == 0 && createGithubBackportState.req.CEAllowInactiveGroups == nil {
|
||||
createGithubBackportState.req.CEAllowInactiveGroups = changed.FileGroups{}
|
||||
}
|
||||
createGithubBackportState.req.CEAllowInactiveGroups = createGithubBackportState.req.CEAllowInactiveGroups.Add(changed.FileGroup(ig))
|
||||
}
|
||||
|
||||
for i, eg := range createGithubBackportState.ceExclude {
|
||||
if i == 0 && createGithubBackportState.req.CEExclude == nil {
|
||||
createGithubBackportState.req.CEExclude = changed.FileGroups{}
|
||||
}
|
||||
createGithubBackportState.req.CEExclude = createGithubBackportState.req.CEExclude.Add(changed.FileGroup(eg))
|
||||
}
|
||||
|
||||
res := createGithubBackportState.req.Run(context.TODO(), githubCmdState.Github, githubCmdState.Git)
|
||||
if res == nil {
|
||||
res = &github.CreateBackportRes{}
|
||||
}
|
||||
if err := res.Err(); err != nil {
|
||||
res.ErrorMessage = err.Error()
|
||||
}
|
||||
|
||||
switch rootCfg.format {
|
||||
case "json":
|
||||
b, err := res.ToJSON()
|
||||
if err != nil {
|
||||
return errors.Join(res.Err(), err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
default:
|
||||
fmt.Println(res.ToTable().Render())
|
||||
}
|
||||
|
||||
return res.Err()
|
||||
}
|
||||
|
|
@ -64,6 +64,7 @@ func newRootCmd() *cobra.Command {
|
|||
|
||||
// Execute executes the root pipeline command.
|
||||
func Execute() {
|
||||
cobra.EnableTraverseRunHooks = true // Automatically chain run hooks
|
||||
rootCmd := newRootCmd()
|
||||
rootCmd.SilenceErrors = true // We handle this below
|
||||
|
||||
|
|
|
|||
135
tools/pipeline/internal/pkg/git/am.go
Normal file
135
tools/pipeline/internal/pkg/git/am.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AmOpts are the git am flags and arguments
|
||||
// See: https://git-scm.com/docs/git-am
|
||||
type AmOpts struct {
|
||||
// Options
|
||||
CommitterDateIsAuthorDate bool // --committer-date-is-author-date
|
||||
Empty EmptyCommit // --empty=<mode>
|
||||
Keep bool // --keep
|
||||
KeepNonPatch bool // --keep-non-patch
|
||||
MessageID bool // --message-id
|
||||
NoMessageID bool // --no-message-id
|
||||
NoReReReAutoupdate bool // --no-rerere-autoupdate
|
||||
NoVerify bool // --no-verify
|
||||
Quiet bool // --quiet
|
||||
ReReReAutoupdate bool // --rerere-autoupdate
|
||||
Signoff bool // --signoff
|
||||
ThreeWayMerge bool // --3way
|
||||
Whitespace ApplyWhitespaceAction // --whitespace=<action>
|
||||
|
||||
// Targets, depending on which combination of options you're setting
|
||||
Mbox []string // <mbox|Maildir>
|
||||
|
||||
// Sequences
|
||||
Abort bool // --abort
|
||||
Continue bool // --continue
|
||||
Quit bool // --quit
|
||||
Resolved bool // --resolved
|
||||
Retry bool // --retry
|
||||
|
||||
// Options that are allowed on sequences
|
||||
AllowEmpty bool // --allow-empty
|
||||
}
|
||||
|
||||
// Am runs the git am command
|
||||
func (c *Client) Am(ctx context.Context, opts *AmOpts) (*ExecResponse, error) {
|
||||
return c.Exec(ctx, "am", opts)
|
||||
}
|
||||
|
||||
// String returns the options as a string
|
||||
func (o *AmOpts) String() string {
|
||||
return strings.Join(o.Strings(), " ")
|
||||
}
|
||||
|
||||
// Strings returns the options as a string slice
|
||||
func (o *AmOpts) Strings() []string {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := []string{}
|
||||
|
||||
switch {
|
||||
case o.Abort:
|
||||
return append(opts, "--abort")
|
||||
case o.Continue:
|
||||
return append(opts, "--continue")
|
||||
case o.Quit:
|
||||
return append(opts, "--quit")
|
||||
case o.Resolved:
|
||||
if o.AllowEmpty {
|
||||
opts = append(opts, "--allow-empty")
|
||||
}
|
||||
return append(opts, "--resolved")
|
||||
case o.Retry:
|
||||
return append(opts, "--retry")
|
||||
}
|
||||
|
||||
if o.CommitterDateIsAuthorDate {
|
||||
opts = append(opts, "--committer-date-is-author-date")
|
||||
}
|
||||
|
||||
if o.Empty != "" {
|
||||
opts = append(opts, fmt.Sprintf("--empty=%s", string(o.Empty)))
|
||||
}
|
||||
|
||||
if o.Keep {
|
||||
opts = append(opts, "--keep")
|
||||
}
|
||||
|
||||
if o.KeepNonPatch {
|
||||
opts = append(opts, "--keep-non-patch")
|
||||
}
|
||||
|
||||
if o.MessageID {
|
||||
opts = append(opts, "--message-id")
|
||||
}
|
||||
|
||||
if o.NoMessageID {
|
||||
opts = append(opts, "--no-message-id")
|
||||
}
|
||||
|
||||
if o.NoReReReAutoupdate {
|
||||
opts = append(opts, "--no-rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.NoVerify {
|
||||
opts = append(opts, "--no-verify")
|
||||
}
|
||||
|
||||
if o.Quiet {
|
||||
opts = append(opts, "--quiet")
|
||||
}
|
||||
|
||||
if o.ReReReAutoupdate {
|
||||
opts = append(opts, "--rerere-autoupdate")
|
||||
}
|
||||
|
||||
if o.Signoff {
|
||||
opts = append(opts, "--signoff")
|
||||
}
|
||||
|
||||
if o.ThreeWayMerge {
|
||||
opts = append(opts, "--3way")
|
||||
}
|
||||
|
||||
if o.Whitespace != "" {
|
||||
opts = append(opts, fmt.Sprintf("--whitespace=%s", string(o.Whitespace)))
|
||||
}
|
||||
|
||||
if len(o.Mbox) > 0 {
|
||||
opts = append(opts, o.Mbox...)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
|
@ -22,6 +22,77 @@ func TestOptsStringers(t *testing.T) {
|
|||
opts OptStringer
|
||||
expected string
|
||||
}{
|
||||
"am": {
|
||||
&AmOpts{
|
||||
AllowEmpty: true, // Only supported for --resolved
|
||||
CommitterDateIsAuthorDate: true,
|
||||
Empty: EmptyCommitKeep,
|
||||
Keep: true,
|
||||
KeepNonPatch: true,
|
||||
MessageID: true,
|
||||
NoMessageID: true,
|
||||
NoReReReAutoupdate: true,
|
||||
NoVerify: true,
|
||||
Quiet: true,
|
||||
ReReReAutoupdate: true,
|
||||
Signoff: true,
|
||||
ThreeWayMerge: true,
|
||||
Whitespace: ApplyWhitespaceActionFix,
|
||||
Mbox: []string{"/path/to/my.patch"},
|
||||
},
|
||||
"--committer-date-is-author-date --empty=keep --keep --keep-non-patch --message-id --no-message-id --no-rerere-autoupdate --no-verify --quiet --rerere-autoupdate --signoff --3way --whitespace=fix /path/to/my.patch",
|
||||
},
|
||||
"am --continue": {
|
||||
&AmOpts{
|
||||
// Unallowed options are ignored
|
||||
Empty: EmptyCommitKeep,
|
||||
AllowEmpty: true,
|
||||
// Sequence
|
||||
Continue: true,
|
||||
},
|
||||
"--continue",
|
||||
},
|
||||
"am --abort": {
|
||||
&AmOpts{
|
||||
// Unallowed options are ignored
|
||||
Empty: EmptyCommitKeep,
|
||||
AllowEmpty: true,
|
||||
// Sequence
|
||||
Abort: true,
|
||||
},
|
||||
"--abort",
|
||||
},
|
||||
"am --quit": {
|
||||
&AmOpts{
|
||||
// Unallowed options are ignored
|
||||
Empty: EmptyCommitKeep,
|
||||
AllowEmpty: true,
|
||||
// Sequence
|
||||
Quit: true,
|
||||
},
|
||||
"--quit",
|
||||
},
|
||||
"am --allow-empty --resolved": {
|
||||
&AmOpts{
|
||||
// Unallowed options are ignored
|
||||
Empty: EmptyCommitKeep,
|
||||
// Allowed options are kept
|
||||
AllowEmpty: true,
|
||||
// Sequence
|
||||
Resolved: true,
|
||||
},
|
||||
"--allow-empty --resolved",
|
||||
},
|
||||
"am --retry": {
|
||||
&AmOpts{
|
||||
// Unallowed options are ignored
|
||||
Empty: EmptyCommitKeep,
|
||||
AllowEmpty: true,
|
||||
// Sequence
|
||||
Retry: true,
|
||||
},
|
||||
"--retry",
|
||||
},
|
||||
"apply": {
|
||||
&ApplyOpts{
|
||||
AllowEmpty: true,
|
||||
|
|
|
|||
1256
tools/pipeline/internal/pkg/github/create_backport.go
Normal file
1256
tools/pipeline/internal/pkg/github/create_backport.go
Normal file
File diff suppressed because it is too large
Load diff
741
tools/pipeline/internal/pkg/github/create_backport_test.go
Normal file
741
tools/pipeline/internal/pkg/github/create_backport_test.go
Normal file
|
|
@ -0,0 +1,741 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
libgithub "github.com/google/go-github/v68/github"
|
||||
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/changed"
|
||||
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCreateBackportReq_Validate tests validation of the request
|
||||
func TestCreateBackportReq_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
req *CreateBackportReq
|
||||
valid bool
|
||||
}{
|
||||
"empty": {nil, false},
|
||||
"valid": {NewCreateBackportReq(WithCreateBrackportReqPullNumber(1234)), true},
|
||||
"no owner": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBackportReqOwner(""),
|
||||
), false,
|
||||
},
|
||||
"no repo": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBrackportReqRepo(""),
|
||||
), false,
|
||||
},
|
||||
"no pull number": {NewCreateBackportReq(), false},
|
||||
"no ce branch prefix": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBrackportReqCEBranchPrefix(""),
|
||||
), false,
|
||||
},
|
||||
"no base origin": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBrackportReqBaseOrigin(""),
|
||||
), false,
|
||||
},
|
||||
"uninitialized exclude groups": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBrackportReqCEExclude(nil),
|
||||
), false,
|
||||
},
|
||||
"uninitialized inactive groups": {
|
||||
NewCreateBackportReq(
|
||||
WithCreateBrackportReqPullNumber(1234),
|
||||
WithCreateBrackportReqAllowInactiveGroups(nil),
|
||||
), false,
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if test.valid {
|
||||
require.NoError(t, test.req.Validate(context.Background()))
|
||||
} else {
|
||||
require.Error(t, test.req.Validate(context.Background()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBackportReq_backportNameForRef tests generating the backport
|
||||
// branch name from branch name ref and the original PR branch name.
|
||||
func TestCreateBackportReq_backportNameForRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
ref string // These should be full branch names
|
||||
prBranch string
|
||||
expected string
|
||||
}{
|
||||
// backporting to ent main should never really happen but we'll test the
|
||||
// logic anyway
|
||||
"ent main": {
|
||||
"main",
|
||||
"my-pr",
|
||||
"backport/main/my-pr",
|
||||
},
|
||||
"ent release branch": {
|
||||
"release/1.19.x+ent",
|
||||
"my-pr",
|
||||
"backport/release/1.19.x+ent/my-pr",
|
||||
},
|
||||
"ce main": {
|
||||
"ce/main",
|
||||
"my-pr",
|
||||
"backport/ce/main/my-pr",
|
||||
},
|
||||
"ce release branch": {
|
||||
"ce/release/1.19.x",
|
||||
"my-pr",
|
||||
"backport/ce/release/1.19.x/my-pr",
|
||||
},
|
||||
"truncates super long branch name": {
|
||||
"main",
|
||||
"my-really-really-long-pr-name-that-must-exceed-two-hundred-and-fifty-characters-when-it-is-appended-to-the-backport-and-base-ref-prefixes-ought-to-be-truncated-so-as-to-not-exceed-the-github-pr-branch-requirements-otherwise-bad-things-happen",
|
||||
"backport/main/my-really-really-long-pr-name-that-must-exceed-two-hundred-and-fifty-characters-when-it-is-appended-to-the-backport-and-base-ref-prefixes-ought-to-be-truncated-so-as-to-not-exceed-the-github-pr-branch-requirements-otherwise-bad-things-h",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := NewCreateBackportReq()
|
||||
require.Equal(t, test.expected, req.backportBranchNameForRef(test.ref, test.prBranch))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBackportReq_baseRefVersion tests generating the base ref version
|
||||
// from the backport branch reference. The base ref version matches the schema
|
||||
// used in .release/versions.hcl.
|
||||
func TestCreateBackportReq_baseRefVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for ref, test := range map[string]struct {
|
||||
req *CreateBackportReq
|
||||
expectedRef string
|
||||
}{
|
||||
// backporting to ent main should never really happen but we'll test the
|
||||
// logic anyway
|
||||
"main": {req: NewCreateBackportReq(), expectedRef: "main"},
|
||||
"ce/main": {req: NewCreateBackportReq(), expectedRef: "main"},
|
||||
"ent/main": {req: NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")), expectedRef: "main"},
|
||||
"release/1.19.x+ent": {req: NewCreateBackportReq(), expectedRef: "release/1.19.x"},
|
||||
"ce/release/1.19.x": {req: NewCreateBackportReq(), expectedRef: "release/1.19.x"},
|
||||
"ent/release/1.19.x": {req: NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")), expectedRef: "release/1.19.x"},
|
||||
} {
|
||||
t.Run(ref, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, test.expectedRef, test.req.baseRefVersion(ref))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBackportReq_determineBackportRefs tests generating a list
|
||||
// of backport refs when considering the base ref of the PR and any labels
|
||||
// that have been applied to it.
|
||||
func TestCreateBackportReq_determineBackportRefs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
req *CreateBackportReq
|
||||
baseRef string
|
||||
labels Labels
|
||||
expected []string
|
||||
}{
|
||||
"ent main no labels": {
|
||||
NewCreateBackportReq(),
|
||||
"main",
|
||||
nil,
|
||||
[]string{"ce/main"},
|
||||
},
|
||||
"ent main no labels with ent prefix": {
|
||||
NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")),
|
||||
"ent/main",
|
||||
nil,
|
||||
[]string{"ce/main"},
|
||||
},
|
||||
"ent main with labels": {
|
||||
NewCreateBackportReq(),
|
||||
"main",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.19.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
},
|
||||
[]string{"ce/main", "release/1.19.x+ent", "release/1.18.x+ent"},
|
||||
},
|
||||
"ent main with labels with ent prefix": {
|
||||
NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")),
|
||||
"ent/main",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.19.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
},
|
||||
[]string{"ce/main", "ent/release/1.19.x+ent", "ent/release/1.18.x+ent"},
|
||||
},
|
||||
"ent release no labels": {
|
||||
NewCreateBackportReq(),
|
||||
"release/1.19.x+ent",
|
||||
nil,
|
||||
[]string{"ce/release/1.19.x"},
|
||||
},
|
||||
"ent release no labels with ent prefix": {
|
||||
NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")),
|
||||
"ent/release/1.19.x+ent",
|
||||
nil,
|
||||
[]string{"ce/release/1.19.x"},
|
||||
},
|
||||
"ent release with labels": {
|
||||
NewCreateBackportReq(),
|
||||
"release/1.19.x+ent",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.17.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.16.x")},
|
||||
},
|
||||
[]string{
|
||||
"ce/release/1.19.x",
|
||||
"release/1.18.x+ent",
|
||||
"release/1.17.x+ent",
|
||||
"release/1.16.x+ent",
|
||||
},
|
||||
},
|
||||
"ent release with labels with ent prefix": {
|
||||
NewCreateBackportReq(WithCreateBrackportReqEntBranchPrefix("ent")),
|
||||
"ent/release/1.19.x+ent",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.17.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.16.x")},
|
||||
},
|
||||
[]string{
|
||||
"ce/release/1.19.x",
|
||||
"ent/release/1.18.x+ent",
|
||||
"ent/release/1.17.x+ent",
|
||||
"ent/release/1.16.x+ent",
|
||||
},
|
||||
},
|
||||
"ce main no labels": {
|
||||
NewCreateBackportReq(),
|
||||
"ce/main",
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
"ce main with labels": {
|
||||
NewCreateBackportReq(),
|
||||
"ce/main",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.19.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
},
|
||||
[]string{"ce/release/1.19.x", "ce/release/1.18.x"},
|
||||
},
|
||||
"ce release no labels": {
|
||||
NewCreateBackportReq(),
|
||||
"ce/release/1.19.x",
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
"ce release with labels": {
|
||||
NewCreateBackportReq(),
|
||||
"ce/release/1.19.x",
|
||||
Labels{
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.18.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.17.x")},
|
||||
&libgithub.Label{Name: libgithub.Ptr("backport/1.16.x")},
|
||||
},
|
||||
[]string{"ce/release/1.18.x", "ce/release/1.17.x", "ce/release/1.16.x"},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.EqualValues(t, test.expected, test.req.determineBackportRefs(context.Background(), test.baseRef, test.labels))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBackportReq_shouldSkipRef tests whether various combinations of
|
||||
// base refs, backport refs, changed files, and active CE versions are
|
||||
// backportable references or should be skipped.
|
||||
func TestCreateBackportReq_shouldSkipRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultActiveVersions := map[string]*releases.Version{
|
||||
// main is never going to be in here as it's assumed it's always active
|
||||
"1.19.x": {CEActive: true, LTS: true},
|
||||
"1.18.x": {CEActive: false},
|
||||
"1.17.x": {CEActive: false},
|
||||
"1.16.x": {CEActive: true, LTS: true},
|
||||
}
|
||||
|
||||
defaultChangedFiles := &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr(".github/workflows/build.yml"),
|
||||
},
|
||||
Groups: changed.FileGroups{"pipeline"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "gotoolchain", "pipeline",
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
baseRefVersion string
|
||||
ref string
|
||||
activeVersions map[string]*releases.Version
|
||||
changedFiles *ListChangedFilesRes
|
||||
skip bool
|
||||
}{
|
||||
"main to ce/main": {
|
||||
baseRefVersion: "main",
|
||||
ref: "ce/main",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: false,
|
||||
},
|
||||
"main to ce/main with ent and ce only files": {
|
||||
baseRefVersion: "main",
|
||||
ref: "ce/main",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("e1c10eae02e13f5a090b9c29b0b1a3003e8ca7f6"),
|
||||
Filename: libgithub.Ptr("go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("a6397662ea1d5fdde744ff3e4246377cf369197a"),
|
||||
Filename: libgithub.Ptr("vault_ent/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "enterprise", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "enterprise", "gotoolchain",
|
||||
},
|
||||
},
|
||||
skip: false,
|
||||
},
|
||||
"main to active release/1.19.x+ent": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "release/1.19.x+ent",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: false,
|
||||
},
|
||||
"main to release/1.18.x+ent (inactive CE)": {
|
||||
baseRefVersion: "1.18.x",
|
||||
ref: "release/1.18.x+ent",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: false,
|
||||
},
|
||||
"active release branch with app changes": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: false,
|
||||
},
|
||||
"active release branch to CE with only ent changes": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr(".github/workflows/build-artifacts-ent.yml"),
|
||||
},
|
||||
Groups: changed.FileGroups{"enterprise", "pipeline"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/vault_ent/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "enterprise", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "enterprise", "gotoolchain", "pipeline",
|
||||
},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
"inactive ce branch with no allowed group changes": {
|
||||
baseRefVersion: "1.18.x",
|
||||
ref: "ce/release/1.18.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/vault_ent/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "enterprise", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "enterprise", "gotoolchain",
|
||||
},
|
||||
},
|
||||
|
||||
skip: true,
|
||||
},
|
||||
"inactive ce with with pipeline changes": {
|
||||
baseRefVersion: "1.18.x",
|
||||
ref: "ce/release/1.18.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr(".github/workflows/build.yml"),
|
||||
},
|
||||
Groups: changed.FileGroups{"pipeline"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "gotoolchain", "pipeline",
|
||||
},
|
||||
},
|
||||
|
||||
skip: false,
|
||||
},
|
||||
"inactive ce with with docs changes": {
|
||||
baseRefVersion: "1.17.x",
|
||||
ref: "ce/release/1.17.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("website/content/docs/index.mdx"),
|
||||
},
|
||||
Groups: changed.FileGroups{"docs"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "gotoolchain", "pipeline",
|
||||
},
|
||||
},
|
||||
|
||||
skip: false,
|
||||
},
|
||||
"inactive ce with with changelog changes": {
|
||||
baseRefVersion: "1.17.x",
|
||||
ref: "ce/release/1.17.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("changelog/1234.txt"),
|
||||
},
|
||||
Groups: changed.FileGroups{"changelog"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "gotoolchain", "pipeline",
|
||||
},
|
||||
},
|
||||
|
||||
skip: false,
|
||||
},
|
||||
"empty changed files list is skipped": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{},
|
||||
Groups: changed.FileGroups{},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
"nil changed files list is skipped": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: nil,
|
||||
skip: true,
|
||||
},
|
||||
"release branch with no active versions": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: map[string]*releases.Version{},
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/vault_ent/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "enterprise", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
Groups: changed.FileGroups{
|
||||
"app", "enterprise", "gotoolchain",
|
||||
},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
"release branch with nil active versions": {
|
||||
baseRefVersion: "1.19.x",
|
||||
ref: "ce/release/1.19.x",
|
||||
activeVersions: nil,
|
||||
changedFiles: &ListChangedFilesRes{
|
||||
Files: changed.Files{
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "gotoolchain"},
|
||||
},
|
||||
{
|
||||
File: &libgithub.CommitFile{
|
||||
SHA: libgithub.Ptr("84e0b544965861a7c6373e639cb13755512f84f4"),
|
||||
Filename: libgithub.Ptr("vault/vault_ent/go.mod"),
|
||||
},
|
||||
Groups: changed.FileGroups{"app", "enterprise", "gotoolchain"},
|
||||
},
|
||||
},
|
||||
},
|
||||
skip: true,
|
||||
},
|
||||
"missing base ref version": {
|
||||
baseRefVersion: "",
|
||||
ref: "ce/main",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: true,
|
||||
},
|
||||
"missing ref version": {
|
||||
baseRefVersion: "main",
|
||||
ref: "",
|
||||
activeVersions: defaultActiveVersions,
|
||||
changedFiles: defaultChangedFiles,
|
||||
skip: true,
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := NewCreateBackportReq()
|
||||
msg, skip := req.shouldSkipRef(
|
||||
context.Background(),
|
||||
test.baseRefVersion,
|
||||
test.ref,
|
||||
test.activeVersions,
|
||||
test.changedFiles,
|
||||
)
|
||||
require.Equalf(
|
||||
t, test.skip, skip, "should have %t but got %t with %s", test.skip, skip, msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBackportReq_pullRequestBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
expectContains []string
|
||||
expectNotContains []string
|
||||
origin *libgithub.PullRequest
|
||||
attempt *CreateBackportAttempt
|
||||
}{
|
||||
"no error": {
|
||||
expectContains: []string{"original body"},
|
||||
expectNotContains: []string{"error body"},
|
||||
origin: &libgithub.PullRequest{
|
||||
Body: libgithub.Ptr("original body"),
|
||||
Number: libgithub.Ptr(1234),
|
||||
HTMLURL: libgithub.Ptr("https://github.com/hashicorp/vault-enterprise/pull/1234"),
|
||||
MergedBy: &libgithub.User{Login: libgithub.Ptr("my-login")},
|
||||
},
|
||||
attempt: &CreateBackportAttempt{
|
||||
TargetRef: "release/1.19.x",
|
||||
},
|
||||
},
|
||||
"error": {
|
||||
expectContains: []string{"original body", "error body"},
|
||||
origin: &libgithub.PullRequest{
|
||||
Body: libgithub.Ptr("original body"),
|
||||
Number: libgithub.Ptr(1234),
|
||||
HTMLURL: libgithub.Ptr("https://github.com/hashicorp/vault-enterprise/pull/1234"),
|
||||
MergedBy: &libgithub.User{Login: libgithub.Ptr("my-login")},
|
||||
},
|
||||
attempt: &CreateBackportAttempt{
|
||||
TargetRef: "release/1.19.x",
|
||||
Error: errors.New("error body"),
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := NewCreateBackportReq()
|
||||
got, err := req.pullRequestBody(test.origin, test.attempt)
|
||||
require.NoError(t, err)
|
||||
for _, c := range test.expectContains {
|
||||
require.Containsf(t, got, c, got)
|
||||
}
|
||||
for _, nc := range test.expectNotContains {
|
||||
require.NotContainsf(t, got, nc, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBackportRes_Err(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, test := range map[string]struct {
|
||||
in *CreateBackportRes
|
||||
failed error
|
||||
}{
|
||||
"nil": {
|
||||
nil,
|
||||
errors.New("uninitialized"),
|
||||
},
|
||||
"no errors": {
|
||||
&CreateBackportRes{
|
||||
Attempts: map[string]*CreateBackportAttempt{
|
||||
"ce/main": {},
|
||||
"release/1.18.x": {},
|
||||
"release/1.19.x": {},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
"top level error no attempt errors": {
|
||||
&CreateBackportRes{
|
||||
Error: errors.New("top-failed"),
|
||||
Attempts: map[string]*CreateBackportAttempt{
|
||||
"ce/main": {},
|
||||
"release/1.18.x": {},
|
||||
"release/1.19.x": {},
|
||||
},
|
||||
},
|
||||
errors.New("top-failed"),
|
||||
},
|
||||
"no top level error attempt errors": {
|
||||
&CreateBackportRes{
|
||||
Attempts: map[string]*CreateBackportAttempt{
|
||||
"ce/main": {
|
||||
Error: errors.New("child-failed"),
|
||||
},
|
||||
"release/1.18.x": {},
|
||||
"release/1.19.x": {},
|
||||
},
|
||||
},
|
||||
errors.New("child-failed"),
|
||||
},
|
||||
"top level and attempt errors": {
|
||||
&CreateBackportRes{
|
||||
Error: errors.New("top-failed"),
|
||||
Attempts: map[string]*CreateBackportAttempt{
|
||||
"ce/main": {},
|
||||
"release/1.18.x": {},
|
||||
"release/1.19.x": {
|
||||
Error: errors.New("child-failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
errors.New("top-failed\nchild-failed"),
|
||||
},
|
||||
"multiple attempt errors": {
|
||||
&CreateBackportRes{
|
||||
Error: errors.New("top-failed"),
|
||||
Attempts: map[string]*CreateBackportAttempt{
|
||||
"ce/main": {},
|
||||
"release/1.18.x": {
|
||||
Error: errors.New("child-2-failed"),
|
||||
},
|
||||
"release/1.19.x": {
|
||||
Error: errors.New("child-3-failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
// When multiple attempts fail the errros should be stable
|
||||
errors.New("top-failed\nchild-2-failed\nchild-3-failed"),
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if test.failed == nil {
|
||||
require.Nil(t, test.in.Err())
|
||||
} else {
|
||||
require.Equal(t, test.failed.Error(), test.in.Err().Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
9
tools/pipeline/internal/pkg/github/embeds.go
Normal file
9
tools/pipeline/internal/pkg/github/embeds.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package github
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed embeds/*
|
||||
var embeds embed.FS
|
||||
34
tools/pipeline/internal/pkg/github/embeds/backport-pr.tmpl
Normal file
34
tools/pipeline/internal/pkg/github/embeds/backport-pr.tmpl
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
## Backport [#{{ .OriginPullRequest.Number }}]({{ .OriginPullRequest.HTMLURL }}) {{ if .Attempt.TargetRef }} into {{ .Attempt.TargetRef }} {{ end }}
|
||||
|
||||
{{ if .Attempt.Error }}
|
||||
:rotating_light:
|
||||
>**Warning** automatic backport of commits failed. If the first commit failed,
|
||||
you will see a blank no-op commit below. If at least one commit succeeded, you
|
||||
will see the backported commits up to, _but not including_, the commit where
|
||||
the merge conflict occurred.
|
||||
|
||||
The person who merged in the original PR is: @{{ .OriginPullRequest.GetMergedBy.GetLogin }}
|
||||
This person should resolve the merge-conflict(s) by either:
|
||||
* Manually completing the backports into this branch
|
||||
* Creating a new branch and manually backporting all commits
|
||||
|
||||
Error(s) encountered while attempting the backport:
|
||||
> {{ .Attempt.Error }}
|
||||
|
||||
To continue the backport process, please follow the instructions below:
|
||||
|
||||
1. Checkout the branch in this PR locally (or optionally create a new branch off {{ .OriginPullRequest.GetBase.GetRef }})
|
||||
1. Manually cherry-pick the missing commits from the original PR into this branch:
|
||||
1. Cherry-pick the commits from the original PR into this branch `git cherry-pick <commit-hash>` (see Overview of unprocessed commits below for the list of commits to cherry-pick)
|
||||
1. Resolve any conflicts that arise
|
||||
1. Remove any CE only files if the backport target branch is to ce
|
||||
1. Push the changes to this branch
|
||||
1. Update the PR description to reflect the new commit(s)
|
||||
|
||||
{{ end }}
|
||||
|
||||
The following text was copied from the body of the original pull request
|
||||
|
||||
---
|
||||
|
||||
{{ .OriginPullRequest.Body }}
|
||||
Loading…
Reference in a new issue