mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-13 23:01:11 +01:00
* [VAULT-39424] pipeline(close-origin-pr): add support for closing the origin of copied PRs
When we copy a community contributed Pull Request to Enterprise the
source PR is effectively orphaned, leaving the original PR still
opened, the author unsure of what state the copied PR is in, and any
issues associated with it open.
When the copied PR is closed we ought to close the origin PR if it's
still open, and any other issues that might be associated with either
the origin PR or the copied PR.
We can also add comments to both PRs that include links to each other
and the squash commit to make discovery of the work visible to those
with access to both repos. Unfortunately there is no way to know what
the SHA will be when it's synced so we have to rely on the
'Co-Authored-By:' trailers in commit message.
There are some challenges to this:
- The automation should only execute when copied PRs are closed
- How to determine the origin PR from only the copied PR
- How to determine the PR's linked issues (which the v3 REST API does not expose)
We solved them by:
- Requiring the PR HEAD ref to start with `copy/`
- Encoding the origin PR information in the PR HEAD ref.
e.g. `copy/hashicorp/vault/31580/ryan/VAULT-39424-test-ce`
- Using the V4 GraphQL API to determine "closed issue references"
The result is a new `pipeline` CLI command that can close the origin PR,
all of the issues, and write status comments on each PR with links to
everything to establish omnidirectional linking in the Github UI.
```bash
pipeline github close origin-pull-request 9903
```
* fix feedback
---------
Signed-off-by: Ryan Cragun <me@ryan.ec>
Co-authored-by: Ryan Cragun <me@ryan.ec>
117 lines
5.0 KiB
Go
117 lines
5.0 KiB
Go
// 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 newCreateGithubBackportCmd() *cobra.Command {
|
|
backportCmd := &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)
|
|
}
|
|
},
|
|
}
|
|
|
|
backportCmd.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")
|
|
backportCmd.PersistentFlags().StringVar(&createGithubBackportState.req.CEBranchPrefix, "ce-branch-prefix", "ce", "The branch name prefix")
|
|
backportCmd.PersistentFlags().StringSliceVarP(&createGithubBackportState.ceExclude, "ce-exclude-groups", "e", []string{"enterprise"}, "Change file groups that should be excluded from the backporting to CE branches")
|
|
backportCmd.PersistentFlags().StringVar(&createGithubBackportState.req.BaseOrigin, "base-origin", "origin", "The name to use for the base remote origin")
|
|
backportCmd.PersistentFlags().StringVarP(&createGithubBackportState.req.Owner, "owner", "o", "hashicorp", "The Github organization")
|
|
backportCmd.PersistentFlags().StringVarP(&createGithubBackportState.req.Repo, "repo", "r", "vault-enterprise", "The Github repository. Private repositories require auth via a GITHUB_TOKEN env var")
|
|
backportCmd.PersistentFlags().StringVarP(&createGithubBackportState.req.RepoDir, "repo-dir", "d", "", "The path to the vault repository dir. If not set a temporary directory will be used")
|
|
backportCmd.PersistentFlags().StringVarP(&createGithubBackportState.req.ReleaseVersionConfigPath, "releases-version-path", "m", "", "The path to .release/versions.hcl")
|
|
backportCmd.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.
|
|
backportCmd.PersistentFlags().StringVar(&createGithubBackportState.req.EntBranchPrefix, "ent-branch-prefix", "", "The ent branch name prefix. Only used for testing before migration to the new workflow")
|
|
backportCmd.PersistentFlags().StringVar(&createGithubBackportState.req.BackportLabelPrefix, "backport-label-prefix", "backport", "The name to use for the base remote origin")
|
|
|
|
err := backportCmd.PersistentFlags().MarkHidden("ent-branch-prefix")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = backportCmd.PersistentFlags().MarkHidden("backport-label-prefix")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return backportCmd
|
|
}
|
|
|
|
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.GithubV3, 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()
|
|
}
|