diff --git a/release/buildspecs/eks-a-releaser-buildspec/create-branch.yml b/release/buildspecs/eks-a-releaser-buildspec/create-branch.yml new file mode 100644 index 0000000000000..ac9af898f2d7c --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/create-branch.yml @@ -0,0 +1,22 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli create-branch + - # Run any additional deployment scripts or commands diff --git a/release/buildspecs/eks-a-releaser-buildspec/create-release.yml b/release/buildspecs/eks-a-releaser-buildspec/create-release.yml new file mode 100644 index 0000000000000..e8175f11f3378 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/create-release.yml @@ -0,0 +1,22 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli create-release + - # Run any additional deployment scripts or commands diff --git a/release/buildspecs/eks-a-releaser-buildspec/go-build-binary.yml b/release/buildspecs/eks-a-releaser-buildspec/go-build-binary.yml new file mode 100644 index 0000000000000..d758f3d49f5de --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/go-build-binary.yml @@ -0,0 +1,19 @@ +version: 0.2 + +phases: + install: + runtime-versions: + golang: latest + pre_build: + commands: + - echo "Navigating to Go CLI directory" + - cd cli-releaser + - echo "Go Dependency Installation" + - go get ./... + build: + commands: + - echo "Go Build" + - go build -o eks-a-releaser-cli ./eks-a-releaser/main.go +artifacts: + files: + - cli-releaser/eks-a-releaser-cli diff --git a/release/buildspecs/eks-a-releaser-buildspec/prod-bundle.yml b/release/buildspecs/eks-a-releaser-buildspec/prod-bundle.yml new file mode 100644 index 0000000000000..be0d4ffc142f2 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/prod-bundle.yml @@ -0,0 +1,23 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - echo "PAT..." + - echo $SECRET_PAT | base64 + - ./eks-a-releaser-cli prod-bundle diff --git a/release/buildspecs/eks-a-releaser-buildspec/prod-cli.yml b/release/buildspecs/eks-a-releaser-buildspec/prod-cli.yml new file mode 100644 index 0000000000000..0cc8aa7f8fc78 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/prod-cli.yml @@ -0,0 +1,21 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli prod-cli diff --git a/release/buildspecs/eks-a-releaser-buildspec/stage-bundle.yml b/release/buildspecs/eks-a-releaser-buildspec/stage-bundle.yml new file mode 100644 index 0000000000000..8eb49872ec3e3 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/stage-bundle.yml @@ -0,0 +1,22 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli stage-bundle + - # Run any additional deployment scripts or commands diff --git a/release/buildspecs/eks-a-releaser-buildspec/stage-cli.yml b/release/buildspecs/eks-a-releaser-buildspec/stage-cli.yml new file mode 100644 index 0000000000000..eedf248161753 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/stage-cli.yml @@ -0,0 +1,21 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli stage-cli diff --git a/release/buildspecs/eks-a-releaser-buildspec/update-homebrew.yml b/release/buildspecs/eks-a-releaser-buildspec/update-homebrew.yml new file mode 100644 index 0000000000000..283a32df3535c --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/update-homebrew.yml @@ -0,0 +1,21 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli update-homebrew diff --git a/release/buildspecs/eks-a-releaser-buildspec/update-makefile.yml b/release/buildspecs/eks-a-releaser-buildspec/update-makefile.yml new file mode 100644 index 0000000000000..5a249e6cb016c --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/update-makefile.yml @@ -0,0 +1,21 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli update-makefile diff --git a/release/buildspecs/eks-a-releaser-buildspec/update-prow.yml b/release/buildspecs/eks-a-releaser-buildspec/update-prow.yml new file mode 100644 index 0000000000000..f8f519d9678d6 --- /dev/null +++ b/release/buildspecs/eks-a-releaser-buildspec/update-prow.yml @@ -0,0 +1,21 @@ +version: 0.2 +env: + secrets-manager: + SECRET_PAT: "Secret:PAT" +phases: + install: + commands: + - echo "Installing dependencies..." + - # Install any required dependencies for deployment + + pre_build: + commands: + - echo "Downloading compiled binary..." + - aws s3 cp s3://eka-a-releaser-build-output/eks-a-releaser-build . + - unzip eks-a-releaser-build + - cd cli-releaser + + build: + commands: + - echo "Deploying Go CLI binary..." + - ./eks-a-releaser-cli update-prow diff --git a/release/cli/cmd/create-branch.go b/release/cli/cmd/create-branch.go new file mode 100644 index 0000000000000..76c295caeea0e --- /dev/null +++ b/release/cli/cmd/create-branch.go @@ -0,0 +1,182 @@ +package cmd + +/* + what does this command do? + + if release type is "minor" then : + creates a new release branch in upstream eks-a repo based off "main" & build tooling repo + + creates a new release branch in forked repo based off newly created release branch in upstream repo + + else : + creates a new patch branch in users forked repo based off latest release branch upstream + +*/ + +import ( + "context" + "fmt" + "os" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + buildToolingRepoName = "eks-anywhere-build-tooling" + upStreamRepoOwner = "testerIbix" // will eventually be replaced by actual upstream owner, aws +) + +// createBranchCmd represents the createBranch command +var createBranchCmd = &cobra.Command{ + Use: "create-branch", + Short: "Creates new release branch from updated trigger file", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command.`, + + Run: func(cmd *cobra.Command, args []string) { + + err := releaseDecision() + if err != nil { + fmt.Printf("error creating branch %s", err) + } + }, +} + +func releaseDecision() error { + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + if RELEASE_TYPE == "minor" { + err := createMinorBranches() + if err != nil { + fmt.Printf("error calling createMinorBranches %s", err) + } + return nil + } + // else + err := createPatchBranch() + if err != nil { + fmt.Printf("error calling createPatchBranch %s", err) + } + return nil +} + +func createMinorBranches() error { + + latestRelease := os.Getenv("LATEST_RELEASE") + + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // create branch in upstream repo based off main branch + ref := "refs/heads/" + latestRelease + baseRef := "main" + + // Get the reference for the base branch + baseRefObj, _, err := client.Git.GetRef(ctx, upStreamRepoOwner, EKSAnyrepoName, "heads/"+baseRef) + if err != nil { + return fmt.Errorf("error getting base branch reference one: %v", err) + } + + // Create a new branch + newBranchRef, _, err := client.Git.CreateRef(ctx, upStreamRepoOwner, EKSAnyrepoName, &github.Reference{ + Ref: &ref, + Object: &github.GitObject{ + SHA: baseRefObj.Object.SHA, + }, + }) + if err != nil { + return fmt.Errorf("error creating branch one: %v", err) + } + + // branch created upstream + fmt.Printf("New release branch '%s' created upstream successfully\n", *newBranchRef.Ref) + + // create branch in forked repo based off upstream + ref = "refs/heads/" + latestRelease + baseRef = latestRelease + + // Get the reference for the base branch from the upstream repository + baseRefObj, _, err = client.Git.GetRef(ctx, upStreamRepoOwner, EKSAnyrepoName, "heads/"+baseRef) + if err != nil { + return fmt.Errorf("error getting base branch reference two: %v", err) + } + + // Create a new branch + newBranchRef, _, err = client.Git.CreateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, &github.Reference{ + Ref: &ref, + Object: &github.GitObject{ + SHA: baseRefObj.Object.SHA, + }, + }) + if err != nil { + return fmt.Errorf("error creating branch two: %v", err) + } + + // branch created upstream + fmt.Printf("New user fork branch '%s' created successfully\n", *newBranchRef.Ref) + + // create branch in upstream build tooling repo based off main branch + ref = "refs/heads/" + latestRelease + baseRef = "main" + + // Get the reference for the base branch + baseRefObj, _, err = client.Git.GetRef(ctx, upStreamRepoOwner, buildToolingRepoName, "heads/"+baseRef) + if err != nil { + return fmt.Errorf("error getting base branch reference three: %v", err) + } + + // Create a new branch + newBranchRef, _, err = client.Git.CreateRef(ctx, upStreamRepoOwner, buildToolingRepoName, &github.Reference{ + Ref: &ref, + Object: &github.GitObject{ + SHA: baseRefObj.Object.SHA, + }, + }) + if err != nil { + return fmt.Errorf("error creating branch three: %v", err) + } + + // branch created upstream + fmt.Printf("New build tooling branch '%s' created successfully\n", *newBranchRef.Ref) + + return nil +} + +func createPatchBranch() error { + + latestRelease := os.Getenv("LATEST_RELEASE") + + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // create branch in forked repo based off upstream + ref := "refs/heads/" + latestRelease + "-releaser-patch" + baseRef := latestRelease + + // Get the reference for the base branch from upstream + baseRefObj, _, err := client.Git.GetRef(ctx, upStreamRepoOwner, EKSAnyrepoName, "heads/"+baseRef) + if err != nil { + return fmt.Errorf("error getting base branch reference: %v", err) + } + + // Create a new branch in fork + newBranchRef, _, err := client.Git.CreateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, &github.Reference{ + Ref: &ref, + Object: &github.GitObject{ + SHA: baseRefObj.Object.SHA, + }, + }) + if err != nil { + return fmt.Errorf("error creating branch: %v", err) + } + + // branch created upstream + fmt.Printf("New branch '%s' created successfully\n", *newBranchRef.Ref) + + return nil +} diff --git a/release/cli/cmd/create-release.go b/release/cli/cmd/create-release.go new file mode 100644 index 0000000000000..e02c88afd8428 --- /dev/null +++ b/release/cli/cmd/create-release.go @@ -0,0 +1,174 @@ +package cmd + +/* + what does this command do? + + this command is responsible for creating a release tag with the commit hash that triggered the prod CLI release + + depending on release type, either minor or patch branch will be checked to retrieve commit hash +*/ + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" + "golang.org/x/oauth2" +) + +// createReleaseCmd represents the createRelease command +var createReleaseCmd = &cobra.Command{ + Use: "create-release", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command.`, + + Run: func(cmd *cobra.Command, args []string) { + runBothTag() + }, +} + +func runBothTag() { + + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + //retrieve commit hash + commitHash := retrieveLatestProdCLIHash(RELEASE_TYPE) + + //create tag with commit hash + tag, errOne := createTag(commitHash) + if errOne != nil { + log.Panic(errOne) + } + + rel, errTwo := createGitHubRelease(tag) + if errTwo != nil { + log.Panic(errTwo) + } + + //print release object + fmt.Print(rel) +} + +// creates tag using retrieved commit hash +func createTag(commitHash string) (*github.RepositoryRelease, error) { + + // retrieve tag name "v0.0.00" + latestVersionValue := os.Getenv("LATEST_VERSION") + + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + + // Create a new GitHub client instance with the token type set to "Bearer" + ts := oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: accessToken, + TokenType: "Bearer", + }) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + releaseName := latestVersionValue + releaseDesc := latestVersionValue //"EKS-Anywhere " + latestVersionValue + " release" + commitSHA := commitHash + release := &github.RepositoryRelease{ + TagName: github.String(releaseName), + Name: github.String(releaseName), + Body: github.String(releaseDesc), + TargetCommitish: github.String(commitSHA), + } + + rel, _, err := client.Repositories.CreateRelease(ctx, upStreamRepoOwner, EKSAnyrepoName, release) + if err != nil { + fmt.Printf("error creating release: %v", err) + } + + fmt.Printf("Release tag %s created successfully!\n", rel.GetTagName()) + return rel, nil +} + +func retrieveLatestProdCLIHash(releaseType string) string { + latestRelease := os.Getenv("LATEST_RELEASE") + + if releaseType == "minor" { + + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + opts := &github.CommitsListOptions{ + Path: prodCliReleaseVerPath, // file to check + SHA: latestRelease, // branch to check - release-0.xx + } + + commits, _, err := client.Repositories.ListCommits(ctx, usersForkedRepoAccount, EKSAnyrepoName, opts) + if err != nil { + return "error fetching commits list" + } + + if len(commits) > 0 { + latestCommit := commits[0] + return latestCommit.GetSHA() + } + + return "no commits found for file" + + } + + // else + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + opts := &github.CommitsListOptions{ + Path: prodCliReleaseVerPath, // file to check + SHA: latestRelease + "-releaser-patch", // branch to check - release-0.xx-releaser-patch + } + + commits, _, err := client.Repositories.ListCommits(ctx, usersForkedRepoAccount, EKSAnyrepoName, opts) + if err != nil { + return "error fetching commits list" + } + + if len(commits) > 0 { + latestCommit := commits[0] + return latestCommit.GetSHA() + } + + return "no commits found for file" + +} + +func createGitHubRelease(releaseTag *github.RepositoryRelease) (*github.RepositoryRelease, error) { + + latestVersionValue := os.Getenv("LATEST_VERSION") + + //create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + release, _, err := client.Repositories.GetReleaseByTag(ctx, upStreamRepoOwner, EKSAnyrepoName, latestVersionValue) + if err == nil { + fmt.Printf("Release %s already exists!\n", latestVersionValue) + return release, nil + } + + release = &github.RepositoryRelease{ + TagName: releaseTag.TagName, + Name: &latestVersionValue, + Body: releaseTag.Body, + } + + rel, _, err := client.Repositories.CreateRelease(ctx, upStreamRepoOwner, EKSAnyrepoName, release) + if err != nil { + return nil, err + } + + return rel, nil +} diff --git a/release/cli/cmd/prod-bundle.go b/release/cli/cmd/prod-bundle.go new file mode 100644 index 0000000000000..4498a6d7cc0ed --- /dev/null +++ b/release/cli/cmd/prod-bundle.go @@ -0,0 +1,145 @@ +package cmd + +/* + what does this command do? + + this command is responsible for staging prod bundle release + + A PR is then created originating from the forked repo targeting the upstream repo latest release branch + + changes are committed into branch depending on release type +*/ +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + prodBundleNumPath = "release/triggers/bundle-release/production/BUNDLE_NUMBER" + prodCliMaxVersionPath = "release/triggers/bundle-release/production/CLI_MAX_VERSION" + prodCliMinVersionPath = "release/triggers/bundle-release/production/CLI_MIN_VERSION" +) + +// prodBundleCmd represents the prodBundle command +var prodBundleCmd = &cobra.Command{ + Use: "prod-bundle", + Short: "creates a PR containing a single commit updating the contents of 3 files intended for prod bundle release", + Long: `Retrieves updated content for production : bundle number, cli max version, and cli min version. + Writes the updated changes to the 3 files and raises a PR with a single commit.`, + + Run: func(cmd *cobra.Command, args []string) { + err := updateAllProdBundleFiles() + if err != nil { + log.Panic(err) + } + }, +} + +func updateAllProdBundleFiles() error { + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + _, err := updateProdBundleFiles(RELEASE_TYPE) + if err != nil { + return err + } + + err = createProdBundlePullRequest(RELEASE_TYPE) + if err != nil { + return err + } + + return nil +} + +func updateProdBundleFiles(releaseType string) (string, error) { + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + bundleNumber := os.Getenv("RELEASE_NUMBER") + latestVersion := os.Getenv("LATEST_VERSION") + latestRelease := os.Getenv("LATEST_RELEASE") + + // Get the latest commit SHA from the appropriate branch + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+getBranchName(releaseType, latestRelease)) + if err != nil { + return "", fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(prodBundleNumPath, "/")), Type: github.String("blob"), Content: github.String(string(bundleNumber)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(prodCliMaxVersionPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(prodCliMinVersionPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return "", fmt.Errorf("error creating tree %s", err) + } + + newTreeSHA := tree.GetSHA() + + // Create a new commit with all the changes + author := &github.CommitAuthor{ + Name: github.String("eks-a-releaser"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update version files for production bundle release"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return "", fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // Update the branch reference + ref.Object.SHA = github.String(newCommitSHA) + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return "", fmt.Errorf("error updating ref %s", err) + } + + return newCommitSHA, nil +} + +func createProdBundlePullRequest(releaseType string) error { + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + base := latestRelease // Target branch for upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, getBranchName(releaseType, latestRelease)) + title := "Update version files to stage production bundle release" + body := "This pull request is responsible for updating the contents of 3 separate files in order to trigger the production bundle release pipeline" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +} diff --git a/release/cli/cmd/prod-cli.go b/release/cli/cmd/prod-cli.go new file mode 100644 index 0000000000000..c512af0378d07 --- /dev/null +++ b/release/cli/cmd/prod-cli.go @@ -0,0 +1,139 @@ +package cmd + +/* + what does this command do? + + this command is responsible for staging prod cli release + + A PR is then created originating from the forked repo targeting the upstream repo latest release branch + + changes are committed into branch depending on release type +*/ +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + prodCliReleaseNumPath = "release/triggers/eks-a-release/production/RELEASE_NUMBER" + prodCliReleaseVerPath = "release/triggers/eks-a-release/production/RELEASE_VERSION" +) + +// prodCliCmd represents the prodCli command +var prodCliCmd = &cobra.Command{ + Use: "prod-cli", + Short: "creates a PR containing a single commit updating the contents of 2 files intended for prod cli release", + Long: `Retrieves updated content for production : release_number and release_version. + Writes the updated changes to the two files and raises a PR with a single commit.`, + + Run: func(cmd *cobra.Command, args []string) { + updateAllProdCliFiles() + }, +} + +// runs both updates functions +func updateAllProdCliFiles() { + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + _, err := updateProdCliFiles(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } + + err = createProdCliPullRequest(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } +} + +func updateProdCliFiles(releaseType string) (string, error) { + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + releaseNumber := os.Getenv("RELEASE_NUMBER") + latestVersion := os.Getenv("LATEST_VERSION") + latestRelease := os.Getenv("LATEST_RELEASE") + + // Get the latest commit SHA from the appropriate branch + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+getBranchName(releaseType, latestRelease)) + if err != nil { + return "", fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(prodCliReleaseNumPath, "/")), Type: github.String("blob"), Content: github.String(string(releaseNumber)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(prodCliReleaseVerPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return "", fmt.Errorf("error creating tree %s", err) + } + + newTreeSHA := tree.GetSHA() + + // Create a new commit with all the changes + author := &github.CommitAuthor{ + Name: github.String("ibix16"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update version files for prod cli release"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return "", fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // Update the branch reference + ref.Object.SHA = github.String(newCommitSHA) + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return "", fmt.Errorf("error updating ref %s", err) + } + + return newCommitSHA, nil +} + +func createProdCliPullRequest(releaseType string) error { + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + base := latestRelease // Target branch for upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, getBranchName(releaseType, latestRelease)) + title := "Update version files to stage prod cli release" + body := "This pull request is responsible for updating the contents of 2 separate files in order to trigger the prod cli release pipeline" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +} diff --git a/release/cli/cmd/stage-bundle.go b/release/cli/cmd/stage-bundle.go new file mode 100644 index 0000000000000..e7b7663a36ff9 --- /dev/null +++ b/release/cli/cmd/stage-bundle.go @@ -0,0 +1,248 @@ +package cmd + +/* + what does this command do? + + this command is responsible for staging bundle release + + A PR is created originating from the forked repo targeting the upstream repo latest release branch + + changes are committed into branch depending on release type +*/ + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + bundleNumPath = "release/triggers/bundle-release/development/BUNDLE_NUMBER" + cliMaxVersionPath = "release/triggers/bundle-release/development/CLI_MAX_VERSION" + cliMinVersionPath = "release/triggers/bundle-release/development/CLI_MIN_VERSION" + //triggerFilePath = "release/triggers/eks-a-releaser-trigger" + usersForkedRepoAccount = getAuthenticatedUsername() +) + +// stageBundleCmd represents the stageBundle command +var stageBundleCmd = &cobra.Command{ + Use: "stage-bundle", + Short: "creates a PR containing 3 commits, each updating the contents of a singular file intended for staging bundle release", + Long: `Retrieves updated content for development : bundle number, cli max version, and cli min version. + Writes the updated changes to the 3 files and raises a PR with the 3 commits.`, + + Run: func(cmd *cobra.Command, args []string) { + + err := runAllStagebundle() + if err != nil { + log.Fatal(err) + } + }, +} + +func runAllStagebundle() error { + + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + commitSHA, err := updateFilesStageBundle(RELEASE_TYPE) + if err != nil { + return err + } + fmt.Print(commitSHA) + + err = createPullRequestStageBundleTwo(RELEASE_TYPE) + if err != nil { + return err + } + + return nil +} + +func updateFilesStageBundle(releaseType string) (string, error) { + + // create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // env variables + bundleNumber := os.Getenv("RELEASE_NUMBER") + latestVersion := os.Getenv("LATEST_VERSION") + latestRelease := os.Getenv("LATEST_RELEASE") + + // Get the latest commit SHA from the appropriate branch, patch vs minor + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+getBranchName(releaseType, latestRelease)) + if err != nil { + return "", fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(bundleNumPath, "/")), Type: github.String("blob"), Content: github.String(string(bundleNumber)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(cliMaxVersionPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(cliMinVersionPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return "", fmt.Errorf("error creating tree %s", err) + } + + newTreeSHA := tree.GetSHA() + + // Create a new commit with all the changes + author := &github.CommitAuthor{ + Name: github.String("ibix16"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update version files for bundle release"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return "", fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // Update the branch reference + ref.Object.SHA = github.String(newCommitSHA) + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return "", fmt.Errorf("error updating ref %s", err) + } + + return newCommitSHA, nil +} + +func createPullRequestStageBundleTwo(releaseType string) error { + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + base := latestRelease // Target branch for upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, getBranchName(releaseType, latestRelease)) + title := "Update version files to stage bundle release" + body := "This pull request is responsible for updating the contents of 3 separate files in order to trigger the staging bundle release pipeline" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +} + +func getBranchName(releaseType, latestRelease string) string { + if releaseType == "minor" { + return latestRelease + } + return latestRelease + "-releaser-patch" +} + +// non related to staging bundle release +// User represents the user's GitHub account information. +type User struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + Name string `json:"name"` + Company string `json:"company"` + Blog string `json:"blog"` + Location string `json:"location"` + Email string `json:"email"` + Hireable bool `json:"hireable"` + Bio string `json:"bio"` + TwitterUsername string `json:"twitter_username"` + PublicRepos int `json:"public_repos"` + PublicGists int `json:"public_gists"` + Followers int `json:"followers"` + Following int `json:"following"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +func getAuthenticatedUsername() string { + + // username is fetched using gh PAT + accessToken := os.Getenv("SECRET_PAT") + // github PAT is retrieved from secrets manager / buildspec file + + // Create a new HTTP client + client := &http.Client{} + + // Create a new HTTP request + req, err := http.NewRequest("GET", "https://api.github.com/user", nil) + if err != nil { + return "error creating HTTP request" + } + + // Set the authorization header with the personal access token + req.Header.Set("Authorization", "token "+accessToken) + + // Send the HTTP request + resp, err := client.Do(req) + if err != nil { + return "error sending HTTP request" + } + defer resp.Body.Close() + + // Read the response body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "error reading response body" + } + + // Check if the request was successful + if resp.StatusCode != http.StatusOK { + return "failed to retrieve user information" + } + + // Unmarshal the response body into a User struct + var user User + err = json.Unmarshal(body, &user) + if err != nil { + return "error unmarshalling" + } + + stringUser := user.Login + return stringUser +} diff --git a/release/cli/cmd/stage-cli.go b/release/cli/cmd/stage-cli.go new file mode 100644 index 0000000000000..b7ef7ec276817 --- /dev/null +++ b/release/cli/cmd/stage-cli.go @@ -0,0 +1,140 @@ +package cmd + +/* + what does this command do? + + this command is responsible for staging cli release + + A PR is then created originating from the forked repo targeting the upstream repo latest release branch + + changes are committed into branch depending on release type +*/ +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + cliReleaseNumPath = "release/triggers/eks-a-release/development/RELEASE_NUMBER" + cliReleaseVerPath = "release/triggers/eks-a-release/development/RELEASE_VERSION" +) + +// stageCliCmd represents the stageCli command +var stageCliCmd = &cobra.Command{ + Use: "stage-cli", + Short: "creates a PR containing a single commit updating the contents of 2 files intended for staging cli release", + Long: `Retrieves updated content for development : release_number and release_version. + Writes the updated changes to the two files and raises a PR with a single commit.`, + + Run: func(cmd *cobra.Command, args []string) { + updateAllStageCliFiles() + }, +} + +// runs both update functions +func updateAllStageCliFiles() { + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + commitSHA, err := updateFilesStageCli(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } + fmt.Print(commitSHA) + + err = createPullRequestStageCli(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } +} + +func updateFilesStageCli(releaseType string) (string, error) { + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + releaseNumber := os.Getenv("RELEASE_NUMBER") + latestVersion := os.Getenv("LATEST_VERSION") + latestRelease := os.Getenv("LATEST_RELEASE") + + // Get the latest commit SHA from the appropriate branch + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+getBranchName(releaseType, latestRelease)) + if err != nil { + return "", fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(cliReleaseNumPath, "/")), Type: github.String("blob"), Content: github.String(string(releaseNumber)), Mode: github.String("100644")}) + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(cliReleaseVerPath, "/")), Type: github.String("blob"), Content: github.String(string(latestVersion)), Mode: github.String("100644")}) + + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return "", fmt.Errorf("error creating tree %s", err) + } + + newTreeSHA := tree.GetSHA() + + // Create a new commit with all the changes + author := &github.CommitAuthor{ + Name: github.String("ibix16"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update version files for cli release"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return "", fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // Update the branch reference + ref.Object.SHA = github.String(newCommitSHA) + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return "", fmt.Errorf("error updating ref %s", err) + } + + return newCommitSHA, nil +} + +func createPullRequestStageCli(releaseType string) error { + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + base := latestRelease // Target branch for upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, getBranchName(releaseType, latestRelease)) + title := "Update version files to stage cli release" + body := "This pull request is responsible for updating the contents of 2 separate files in order to trigger the staging cli release pipeline" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +} diff --git a/release/cli/cmd/trigger.go b/release/cli/cmd/trigger.go new file mode 100644 index 0000000000000..eb840dd05e766 --- /dev/null +++ b/release/cli/cmd/trigger.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var triggerCmd = &cobra.Command{ + Use: "trigger", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, +} + +func init() { + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.AddCommand(triggerCmd) + triggerCmd.AddCommand(updateMakefileCmd) + triggerCmd.AddCommand(updateProwCmd) + triggerCmd.AddCommand(stageBundleCmd) + triggerCmd.AddCommand(stageCliCmd) + triggerCmd.AddCommand(prodBundleCmd) + triggerCmd.AddCommand(prodCliCmd) + triggerCmd.AddCommand(createBranchCmd) + triggerCmd.AddCommand(updateHomebrewCmd) + triggerCmd.AddCommand(createReleaseCmd) +} diff --git a/release/cli/cmd/update-homebrew.go b/release/cli/cmd/update-homebrew.go new file mode 100644 index 0000000000000..c9d1bcaf36c14 --- /dev/null +++ b/release/cli/cmd/update-homebrew.go @@ -0,0 +1,156 @@ +package cmd + +/* + what does this command do? + + this command is responsible for updating the homebrew file + + A PR is then created originating from the forked repo targeting the upstream repo latest release branch + + changes are committed into branch depending on release type +*/ +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + homebrewPath = "release/triggers/brew-version-release/CLI_RELEASE_VERSION" +) + +// updateHomebrewCmd represents the updateHomebrew command +var updateHomebrewCmd = &cobra.Command{ + Use: "update-homebrew", + Short: "Updates homebrew with latest version in eks-a-releaser branch, PR targets release branch", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command.`, + + Run: func(cmd *cobra.Command, args []string) { + runAllHomebrew() + }, +} + +func runAllHomebrew() { + RELEASE_TYPE := os.Getenv("RELEASE_TYPE") + + _, err := updateHomebrew(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } + + err = createPullRequestHomebrew(RELEASE_TYPE) + if err != nil { + log.Panic(err) + } +} + +func updateHomebrew(releaseType string) (string, error) { + + // env variables + latestVersionValue := os.Getenv("LATEST_VERSION") + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + opts := &github.RepositoryContentGetOptions{ + Ref: "main", // Specific branch to check for homebrew file + } + + // Access homebrew file + FileContentBundleNumber, _, _, err := client.Repositories.GetContents(ctx, usersForkedRepoAccount, EKSAnyrepoName, homebrewPath, opts) + if err != nil { + fmt.Print("first breakpoint", err) + } + + // Holds content of homebrew cli version file + content, err := FileContentBundleNumber.GetContent() + if err != nil { + fmt.Print("second breakpoint", err) + } + + // Update instances of previous release with new + updatedFile := strings.ReplaceAll(content, content, latestVersionValue) + + // Get the latest commit SHA from the appropriate branch + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+getBranchName(releaseType, latestRelease)) + if err != nil { + return "", fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(homebrewPath, "/")), Type: github.String("blob"), Content: github.String(string(updatedFile)), Mode: github.String("100644")}) + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return "", fmt.Errorf("error creating tree %s", err) + } + + newTreeSHA := tree.GetSHA() + + // Create a new commit + author := &github.CommitAuthor{ + Name: github.String("ibix16"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update brew-version value to point to new release"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return "", fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // Update the branch reference + ref.Object.SHA = github.String(newCommitSHA) + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return "", fmt.Errorf("error updating ref %s", err) + } + + return newCommitSHA, nil +} + +func createPullRequestHomebrew(releaseType string) error { + latestRelease := os.Getenv("LATEST_RELEASE") + + // Create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + base := latestRelease // Target branch for upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, getBranchName(releaseType, latestRelease)) + title := "Update homebrew cli version value to point to new release" + body := "This pull request is responsible for updating the contents of the home brew cli version file" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +} diff --git a/release/cli/cmd/update-makefile.go b/release/cli/cmd/update-makefile.go new file mode 100644 index 0000000000000..4641076879a12 --- /dev/null +++ b/release/cli/cmd/update-makefile.go @@ -0,0 +1,156 @@ +package cmd + +/* + what does this command do? + + this command is responsible for accessing and updating the Makefile with the latest release value + + the updated makefile is committed to the latest release branch, forked repo + + and a pull request is raised targeting the upstream repo latest release branch +*/ + +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + EKSAnyrepoName = "eks-anywhere" + makeFilePath = "/Makefile" +) + +// upMakeFileCmd represents the upMakeFile command +var updateMakefileCmd = &cobra.Command{ + Use: "update-makefile", + Short: "Updates BRANCH_NAME?= variable to match new release branch within the Makefile", + Long: `A longer description.`, + + Run: func(cmd *cobra.Command, args []string) { + content := updateMakefile() + fmt.Print(content) + }, +} + +func updateMakefile() error { + + // create client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // string variable holding latest release from env "release-0.xx" + latestRelease := os.Getenv("LATEST_RELEASE") + + opts := &github.RepositoryContentGetOptions{ + Ref: "main", // branch that will be accessed + } + + // access makefile in forked repo and retrieve entire file contents + triggerFileContentBundleNumber, _, _, err := client.Repositories.GetContents(ctx, usersForkedRepoAccount, EKSAnyrepoName, makeFilePath, opts) + if err != nil { + fmt.Print("first breakpoint", err) + } + // holds makefile + content, err := triggerFileContentBundleNumber.GetContent() + if err != nil { + fmt.Print("second breakpoint", err) + } + + // stores entire updated Makefile as a string + updatedContent := returnUpdatedMakeFile(content, latestRelease) + + // get latest commit sha from latest release branch + ref, _, err := client.Git.GetRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, "heads/"+latestRelease) + if err != nil { + return fmt.Errorf("error getting ref %s", err) + } + latestCommitSha := ref.Object.GetSHA() + + entries := []*github.TreeEntry{} + entries = append(entries, &github.TreeEntry{Path: github.String(strings.TrimPrefix(makeFilePath, "/")), Type: github.String("blob"), Content: github.String(string(updatedContent)), Mode: github.String("100644")}) + tree, _, err := client.Git.CreateTree(ctx, usersForkedRepoAccount, EKSAnyrepoName, *ref.Object.SHA, entries) + if err != nil { + return fmt.Errorf("error creating tree %s", err) + } + + //validate tree sha + newTreeSHA := tree.GetSHA() + + // create new commit, update email address + author := &github.CommitAuthor{ + Name: github.String("ibix16"), + Email: github.String("fake@wtv.com"), + } + + commit := &github.Commit{ + Message: github.String("Update Makefile"), + Tree: &github.Tree{SHA: github.String(newTreeSHA)}, + Author: author, + Parents: []*github.Commit{{SHA: github.String(latestCommitSha)}}, + } + + commitOP := &github.CreateCommitOptions{} + newCommit, _, err := client.Git.CreateCommit(ctx, usersForkedRepoAccount, EKSAnyrepoName, commit, commitOP) + if err != nil { + return fmt.Errorf("creating commit %s", err) + } + newCommitSHA := newCommit.GetSHA() + + // update branch reference + ref.Object.SHA = github.String(newCommitSHA) + + _, _, err = client.Git.UpdateRef(ctx, usersForkedRepoAccount, EKSAnyrepoName, ref, false) + if err != nil { + return fmt.Errorf("error updating ref %s", err) + } + + // create pull request + base := latestRelease // branch PR will be merged into + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, latestRelease) + title := "Updates Makefile to point to new release" + body := "This pull request is responsible for updating the contents of the Makefile" + + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, EKSAnyrepoName, newPR) + if err != nil { + return fmt.Errorf("error creating PR %s", err) + } + + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil + +} + +// updates Makefile with new release, returns entire file updated +func returnUpdatedMakeFile(fileContent, newRelease string) string { + snippetStartIdentifierB := "BRANCH_NAME?=" + lines := strings.Split(fileContent, "\n") + var updatedLines []string + + for _, line := range lines { + if strings.Contains(line, snippetStartIdentifierB) { + parts := strings.Split(line, "=") + varNamePart := parts[0] // holds "BRANCH_NAME?" + updatedLine := varNamePart + "=" + newRelease + updatedLines = append(updatedLines, updatedLine) + } else { + updatedLines = append(updatedLines, line) + } + } + + return strings.Join(updatedLines, "\n") + +} diff --git a/release/cli/cmd/update-prow.go b/release/cli/cmd/update-prow.go new file mode 100644 index 0000000000000..86b7b94f3422d --- /dev/null +++ b/release/cli/cmd/update-prow.go @@ -0,0 +1,306 @@ +package cmd + +/* + what does this command do? + + (1) creates a folder on user's Desktop + (2) clones github prow repo (update account name) + (3) Renames templater file and updates contents + (4) executes make command + (5) creates a branch on user's fork of prow repo + (6) stages, commits, and pushes changes to newly created branch + (7) creates PR targeting upstream "main" branch + + Only command to include local cloning of repo +*/ + +import ( + "context" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/aws/eks-anywhere-build-tooling/tools/version-tracker/pkg/util/command" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/google/go-github/v62/github" + "github.com/spf13/cobra" +) + +var ( + prowRepo = "eks-anywhere-prow-jobs" +) + +// upProwCmd represents the upProw command +var updateProwCmd = &cobra.Command{ + Use: "update-prow", + Short: "accesses prow-jobs repo and updates version files", + Long: `A`, + Run: func(cmd *cobra.Command, args []string) { + updateProw() + }, +} + +func updateProw() { + + latestRelease := os.Getenv("LATEST_RELEASE") + + // Step 1: Create a folder on the user's desktop + homeDir, err := os.UserHomeDir() + if err != nil { + fmt.Printf("error getting user home directory: %v", err) + return + } + desktopPath := filepath.Join(homeDir, "Desktop") + newFolderPath := filepath.Join(desktopPath, "ProwJobsRepo") + err = os.Mkdir(newFolderPath, 0755) + if err != nil { + fmt.Println("Error creating folder:", err) + return + } + fmt.Println("Folder created successfully at:", newFolderPath) + + //clones github repo into newly created folder + clonedRepoDestination := filepath.Join(homeDir, "Desktop", "ProwJobsRepo") + repo, err := cloneRepo("https://github.com/ibix16/eks-anywhere-prow-jobs", clonedRepoDestination) + if err != nil { + fmt.Printf("error cloning repo: %v", err) + return + } + + // Step 2: Rename the file with the latest version + originalFilePath, err := retrieveFilePath(clonedRepoDestination + "/templater/jobs/periodic/eks-anywhere-build-tooling") + if err != nil { + fmt.Printf("error fetching path to file on cloned repo: %v", err) + } + newFilePath := clonedRepoDestination + "/templater/jobs/periodic/eks-anywhere-build-tooling/eks-anywhere-attribution-periodics-" + latestRelease + ".yaml" + err = os.Rename(originalFilePath, newFilePath) + if err != nil { + fmt.Printf("error renaming file: %v", err) + return + } + + // Step 3: Update file contents + convertedRelease := strings.Replace(latestRelease, ".", "-", 1) + content, err := ioutil.ReadFile(newFilePath) + if err != nil { + log.Fatalf("Failed to read file: %v", err) + } + releasePattern := regexp.MustCompile(`release-0\.\d+\d+`) + jobNamePattern := regexp.MustCompile(`release-0-\d+\d+`) + updatedContent := releasePattern.ReplaceAllString(string(content), latestRelease) + updatedContent = jobNamePattern.ReplaceAllString(updatedContent, convertedRelease) + err = ioutil.WriteFile(newFilePath, []byte(updatedContent), 0644) + if err != nil { + log.Fatalf("Failed to write file: %v", err) + } + fmt.Println("File updated successfully.") + + // Execute make command + err = makeCommand() + if err != nil { + fmt.Printf("error running make command: %v", err) + return + } + fmt.Println("Make command executed successfully.") + + // Create a branch in the user's forked repo + err = createProwBranch(usersForkedRepoAccount, prowRepo) + if err != nil { + fmt.Printf("error creating branch: %v", err) + return + } + + // Commit and push changes to the branch + err = commitAndPushChanges(repo, latestRelease+"-releaser") + if err != nil { + fmt.Printf("error pushing changes to branch: %v", err) + return + } + fmt.Println("Changes pushed successfully.") + + // Create PR + err = createProwPr() + if err != nil { + fmt.Printf("error creating PR: %v", err) + return + } + + // delete folder + err = os.RemoveAll(clonedRepoDestination) + if err != nil { + fmt.Printf("error deleting folder: %s", err) + } + + fmt.Println("Folder deleted successfully from desktop.") + +} + +// return full system file path to templater file on cloned repo +func retrieveFilePath(directory string) (string, error) { + var filePath string + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err // Return the error immediately + } + if !info.IsDir() && strings.Contains(info.Name(), "release-0.") { + filePath = path + return nil // Stop walking after finding the first matching file + } + return nil + }) + if err != nil { + return "", err // Return the error if one occurred during the walk + } + if filePath == "" { + return "", fmt.Errorf("no file found with 'release-0.' in its name") + } + return filePath, nil +} + +// clones prow jobs repo on local machine destination +func cloneRepo(cloneURL, destination string) (*git.Repository, error) { + repo, err := git.PlainClone(destination, false, &git.CloneOptions{ + URL: cloneURL, + Progress: os.Stdout, + }) + if err != nil { + if err == git.ErrRepositoryAlreadyExists { + fmt.Printf("Repo already exists at %s\n", destination) + repo, err = git.PlainOpen(destination) + if err != nil { + return nil, fmt.Errorf("opening repo from %s directory: %v", destination, err) + } + } else { + return nil, fmt.Errorf("cloning repo %s to %s directory: %v", cloneURL, destination, err) + } + } + return repo, nil +} + +// function to execute make command located within templater dir +func makeCommand() error { + desktopPath, err := os.UserHomeDir() + if err != nil { + fmt.Printf("error getting user home directory: %v", err) + return nil + } + clonedRepoPath := filepath.Join(desktopPath, "Desktop", "ProwJobsRepo") + templaterDirPath := filepath.Join(clonedRepoPath, "templater") + updateProwJobsCommandSequence := fmt.Sprintf("make prowjobs -C %s", templaterDirPath) + updateProwJobsCmd := exec.Command("bash", "-c", updateProwJobsCommandSequence) + _, err = command.ExecCommand(updateProwJobsCmd) + if err != nil { + return fmt.Errorf("running make prowjobs command: %v", err) + } + return nil +} + +func createProwBranch(owner, repo string) error { + + latestRelease := os.Getenv("LATEST_RELEASE") + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // Get the latest commit on the "main" branch + mainBranch, _, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/main") + if err != nil { + return fmt.Errorf("failed to get 'main' branch: %v", err) + } + latestCommit, _, err := client.Git.GetCommit(ctx, owner, repo, *mainBranch.Object.SHA) + if err != nil { + return fmt.Errorf("failed to get latest commit: %v", err) + } + // Create a new reference for the branch + newRef := &github.Reference{ + Ref: github.String("refs/heads/" + latestRelease + "-releaser"), + Object: &github.GitObject{ + SHA: latestCommit.SHA, + }, + } + // Create the new branch + _, _, err = client.Git.CreateRef(ctx, owner, repo, newRef) + if err != nil { + return fmt.Errorf("failed to create 'releaser' branch: %v", err) + } + fmt.Println("Branch created successfully") + return nil +} + +// Commits and pushes the changes to the new branch +func commitAndPushChanges(repo *git.Repository, branchName string) error { + + w, err := repo.Worktree() + if err != nil { + return fmt.Errorf("could not get worktree: %v", err) + } + // Stage all changes + err = w.AddGlob(".") + if err != nil { + return fmt.Errorf("could not stage changes: %v", err) + } + // Commit changes + _, err = w.Commit("Update prow jobs for "+branchName, &git.CommitOptions{ + Author: &object.Signature{ + Name: "your-github-username", // Update with your GitHub username + When: time.Now(), + }, + }) + if err != nil { + return fmt.Errorf("could not commit changes: %v", err) + } + // Push changes to the new branch + accessToken := os.Getenv("SECRET_PAT") + err = repo.Push(&git.PushOptions{ + RemoteName: "origin", + Auth: &http.BasicAuth{ + Username: "your-github-username", // Update with your GitHub username + Password: accessToken, // GitHub personal access token + }, + RefSpecs: []config.RefSpec{ + config.RefSpec("refs/heads/main" + ":refs/heads/" + branchName), + }, + }) + if err != nil { + return fmt.Errorf("could not push changes: %v", err) + } + return nil +} + +// Function to create the PR +func createProwPr() error { + + latestRelease := os.Getenv("LATEST_RELEASE") + // Create a GitHub client + accessToken := os.Getenv("SECRET_PAT") + ctx := context.Background() + client := github.NewClient(nil).WithAuthToken(accessToken) + + // Create a PR from this branch + branchName := latestRelease + "-releaser" + base := "main" // target branch in upstream repo + head := fmt.Sprintf("%s:%s", usersForkedRepoAccount, branchName) // PR originates from + title := "Update Prow Jobs Templater file & execute make command" + body := "This pull request contains the most recent commit from the release branch." + newPR := &github.NewPullRequest{ + Title: &title, + Head: &head, + Base: &base, + Body: &body, + } + pr, _, err := client.PullRequests.Create(ctx, upStreamRepoOwner, prowRepo, newPR) + if err != nil { + return fmt.Errorf("error creating PR: %s", err) + } + log.Printf("Pull request created: %s\n", pr.GetHTMLURL()) + return nil +}