From aa38fb116c91c65068527e1566111d93746ac88b Mon Sep 17 00:00:00 2001 From: iliana etaoin Date: Thu, 17 Jul 2025 15:09:30 -0700 Subject: [PATCH] release: drop pick-sha command and go-jira dep --- go.mod | 4 - go.sum | 8 -- pkg/cmd/release/git.go | 128 ------------------- pkg/cmd/release/jira.go | 244 ------------------------------------ pkg/cmd/release/main.go | 1 - pkg/cmd/release/metadata.go | 75 ----------- pkg/cmd/release/pick_sha.go | 154 ----------------------- 7 files changed, 614 deletions(-) delete mode 100644 pkg/cmd/release/jira.go delete mode 100644 pkg/cmd/release/metadata.go delete mode 100644 pkg/cmd/release/pick_sha.go diff --git a/go.mod b/go.mod index 583f7bedd98..3e397fd7690 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/VividCortex/ewma v1.1.1 github.com/alessio/shellescape v1.4.1 github.com/andy-kimball/arenaskl v0.0.0-20200617143215-f701008588b9 - github.com/andygrunwald/go-jira v1.14.0 github.com/apache/arrow/go/arrow v0.0.0-20200923215132-ac86123a3f01 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e github.com/aws/aws-sdk-go v1.40.37 @@ -205,7 +204,6 @@ require ( github.com/dimchansky/utfbom v1.1.1 // indirect github.com/djherbis/atime v1.1.0 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-kit/log v0.1.0 // indirect @@ -226,7 +224,6 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/gogo/googleapis v1.4.1 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -301,7 +298,6 @@ require ( github.com/tklauser/go-sysconf v0.3.9 // indirect github.com/tklauser/numcpus v0.3.0 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect - github.com/trivago/tgo v1.0.7 // indirect github.com/twitchtv/twirp v8.1.0+incompatible // indirect github.com/twpayne/go-kml v1.5.2 // indirect github.com/urfave/cli/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index 866507c7553..362913fcfcc 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,6 @@ github.com/andy-kimball/arenaskl v0.0.0-20200617143215-f701008588b9/go.mod h1:V2 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE= github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= -github.com/andygrunwald/go-jira v1.14.0 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI= -github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= @@ -680,7 +678,6 @@ github.com/fanixk/geohash v0.0.0-20150324002647-c1f9b5fa157a/go.mod h1:UgNw+PTmm github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -946,8 +943,6 @@ github.com/golang-commonmark/mdurl v0.0.0-20180910110917-8d018c6567d6 h1:XkgfhPs github.com/golang-commonmark/mdurl v0.0.0-20180910110917-8d018c6567d6/go.mod h1:J66ZGl/dC2mj4ElzGfrLUq0N90HvQoUbrYgYNJUjuMs= github.com/golang-commonmark/puny v0.0.0-20180910110745-050be392d8b8 h1:DUgQdQmDg4sk4SfNR+qOkXcopGz36BL02vp/V7WbPQI= github.com/golang-commonmark/puny v0.0.0-20180910110745-050be392d8b8/go.mod h1:/8a6mcbf/Hwg6MjnHHp5vqCWw0Bsves9HLPObHAj7XA= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -1031,7 +1026,6 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec= github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -2013,8 +2007,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= -github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchtv/twirp v8.1.0+incompatible h1:KGXanpa9LXdVE/V5P/tA27rkKFmXRGCtSNT7zdeeVOY= github.com/twitchtv/twirp v8.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= diff --git a/pkg/cmd/release/git.go b/pkg/cmd/release/git.go index 869e87cd532..bc5bd96faf8 100644 --- a/pkg/cmd/release/git.go +++ b/pkg/cmd/release/git.go @@ -11,26 +11,14 @@ package main import ( - "context" "fmt" "os/exec" - "regexp" "sort" "strings" "github.com/Masterminds/semver/v3" ) -type releaseInfo struct { - prevReleaseVersion string - nextReleaseVersion string - buildInfo buildInfo - // candidateCommits contains all merge commits that can be considered as release candidates - candidateCommits []string - // releaseSeries represents the major release prefix, e.g. 21.2 - releaseSeries string -} - // findNextVersion returns the next release version for given releaseSeries. func findNextVersion(releaseSeries string) (string, error) { prevReleaseVersion, err := findPreviousRelease(releaseSeries) @@ -44,54 +32,6 @@ func findNextVersion(releaseSeries string) (string, error) { return nextReleaseVersion, nil } -// findNextRelease finds all required information for the next release. -func findNextRelease(releaseSeries string) (releaseInfo, error) { - prevReleaseVersion, err := findPreviousRelease(releaseSeries) - if err != nil { - return releaseInfo{}, fmt.Errorf("cannot find previous release: %w", err) - } - nextReleaseVersion, err := bumpVersion(prevReleaseVersion) - if err != nil { - return releaseInfo{}, fmt.Errorf("cannot bump version: %w", err) - } - candidateCommits, err := findCandidateCommits(prevReleaseVersion, releaseSeries) - if err != nil { - return releaseInfo{}, fmt.Errorf("cannot find candidate commits: %w", err) - } - info, err := findHealthyBuild(candidateCommits) - if err != nil { - return releaseInfo{}, fmt.Errorf("cannot find healthy build: %w", err) - } - releasedVersions, err := getVersionsContainingRef(info.SHA) - if err != nil { - return releaseInfo{}, fmt.Errorf("cannot check if the candidate sha was released: %w", err) - } - if len(releasedVersions) > 0 { - return releaseInfo{}, fmt.Errorf("%s has been already released as a part of the following tags: %s", - info.SHA, strings.Join(releasedVersions, ", ")) - } - return releaseInfo{ - prevReleaseVersion: prevReleaseVersion, - nextReleaseVersion: nextReleaseVersion, - buildInfo: info, - candidateCommits: candidateCommits, - releaseSeries: releaseSeries, - }, nil -} - -func getVersionsContainingRef(ref string) ([]string, error) { - cmd := exec.Command("git", "tag", "--contains", ref) - out, err := cmd.Output() - if err != nil { - return []string{}, fmt.Errorf("cannot list tags containing %s: %w", ref, err) - } - var versions []string - for _, v := range findVersions(string(out)) { - versions = append(versions, v.Original()) - } - return versions, nil -} - func findVersions(text string) []*semver.Version { var versions []*semver.Version for _, line := range strings.Split(text, "\n") { @@ -139,71 +79,3 @@ func bumpVersion(version string) (string, error) { nextVersion := semanticVersion.IncPatch() return nextVersion.Original(), nil } - -// filterPullRequests finds commits with a particular merge pattern in the commit message. -// GitHub uses "Merge pull request #NNN" and Bors uses "Merge #NNN" in the generated commit messages. -func filterPullRequests(text string) []string { - var shas []string - matchMerge := regexp.MustCompile(`Merge (#|pull request)`) - for _, line := range strings.Split(text, "\n") { - if !matchMerge.MatchString(line) { - continue - } - sha := strings.Fields(line)[0] - shas = append(shas, sha) - } - return shas -} - -// getMergeCommits lists all merge commits within a range of two refs. -func getMergeCommits(fromRef, toRef string) ([]string, error) { - cmd := exec.Command("git", "log", "--merges", "--format=format:%H %s", "--ancestry-path", - fmt.Sprintf("%s..%s", fromRef, toRef)) - out, err := cmd.Output() - if err != nil { - return []string{}, fmt.Errorf("cannot read git log output: %w", err) - } - return filterPullRequests(string(out)), nil -} - -func getCommonBaseRef(fromRef, toRef string) (string, error) { - cmd := exec.Command("git", "merge-base", fromRef, toRef) - out, err := cmd.Output() - if err != nil { - return "", err - } - return strings.TrimSpace(string(out)), nil -} - -// findCandidateCommits finds all potential merge commits that can be used for the current release. -// It includes all merge commits since previous release. -func findCandidateCommits(prevRelease string, releaseSeries string) ([]string, error) { - releaseBranch := fmt.Sprintf("origin/release-%s", releaseSeries) - commonBaseRef, err := getCommonBaseRef(prevRelease, releaseBranch) - if err != nil { - return []string{}, fmt.Errorf("cannot find common base ref: %w", err) - } - refs, err := getMergeCommits(commonBaseRef, releaseBranch) - if err != nil { - return []string{}, fmt.Errorf("cannot get merge commits: %w", err) - } - return refs, nil -} - -// findHealthyBuild walks all potentials merge commits in reverse order and tries to find the latest healthy build. -// The assumption is that every healthy build has a corresponding metadata file published to the release -// qualification bucket. -func findHealthyBuild(potentialRefs []string) (buildInfo, error) { - for _, ref := range potentialRefs { - fmt.Println("Fetching release qualification metadata for", ref) - meta, err := getBuildInfo(context.Background(), pickSHAFlags.qualifyBucket, - fmt.Sprintf("%s/%s.json", pickSHAFlags.qualifyObjectPrefix, ref)) - if err != nil { - // TODO: retry if error is not 404 - fmt.Println("no metadata qualification for", ref, err) - continue - } - return meta, nil - } - return buildInfo{}, fmt.Errorf("no ref found") -} diff --git a/pkg/cmd/release/jira.go b/pkg/cmd/release/jira.go deleted file mode 100644 index fb7931cd850..00000000000 --- a/pkg/cmd/release/jira.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2019 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package main - -import ( - "fmt" - "strings" - - "github.com/andygrunwald/go-jira" -) - -const jiraBaseURL = "https://cockroachlabs.atlassian.net/" -const dryRunProject = "RE" - -// for DeployToClusterIssue -const customFieldHasSLAKey = "customfield_10073" - -// Jira uses Wiki syntax, see https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all -const trackingIssueTemplate = ` -* Version: *{{ .Version }}* -* SHA: [{{ .SHA }}|https://github.com/cockroachlabs/release-staging/commit/{{ .SHA }}] -* Tag: [{{ .Tag }}|https://github.com/cockroachlabs/release-staging/releases/tag/{{ .Tag }}] -* SRE issue: [{{ .SREIssue }}] -* Deployment status: _fillmein_ -* Publish Cockroach Release: _fillmein_ - -h2. [Release process checklist|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process] - -* Assign the SRE issue [{{ .SREIssue }}] (use "/genie whoisoncall" in Slack). They will be notified by Jira. -* [5-8. Verify node crash reports|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/328859690/Release+Qualification#Verify-node-crash-reports-appear-in-sentry.io] - -h2. Do not proceed below until the release date. - -Release date: _fillmein_ - -* [9. Publish Cockroach Release|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-9.PublishTheRelease] -* Ack security@ and release-engineering-team@ on the generated AWS S3 bucket write alert to confirm these writes were part of a planned release -* [10. Check binaries|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-10.CheckBinaries] -* [12. Announce the release is cut to releases@|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-13.Announcethereleaseiscuttoreleases@] -* [13. Update version numbers|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-14.Updateversionnumbers] -* For production or stable releases in the latest [major release|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-Knowifthereleaseisonthelatestmajorreleaseseries] series only (in August 2020, this is the v20.1 series): -* Update [Brew Recipe|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-Brewrecipe] -* Update [Orchestrator configurations:CRDB|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-Orchestratorconfigurations:CRDB] -* Update [Orchestrator configurations:Helm Charts|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-Orchestratorconfigurations:HelmCharts] - -For all production or stable releases: -* Create a ticket in the [Dev Inf tracker|https://cockroachlabs.atlassian.net/wiki/spaces/devinf/pages/429097164/Submitting+Issues+Requests+to+the+Developer+Infrastructure+team] to update the Red Hat Container Image Repository -* *After docs are updated* [Announce version to registration cluster|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-AnnounceVersionToRegCluster] -* [Update version map in bin/roachtest (all stable releases) and regenerate test fixtures (only major release)|https://cockroachlabs.atlassian.net/wiki/spaces/ENG/pages/73105625/Release+process#Releaseprocess-Updateversionmapinroachtestandregeneratetestfixtures] -* Update docs (handled by Docs team) -* External communications for release (handled by Marketing team) -` -const sreIssueTemplate = ` -Could you deploy the Docker image with the following tag to the release qualification CC cluster? - -* Version: {{ .Version }} -* Build ID: {{ .Tag }} - -Please follow [this playbook|https://github.com/cockroachlabs/production/wiki/Deploy-release-qualification-versions] - -Thank you\! -` - -type jiraClient struct { - client *jira.Client -} - -type trackingIssueTemplateArgs struct { - Version string - SHA string - Tag string - SREIssue string -} - -type sreIssueTemplateArgs struct { - Version string - Tag string -} - -type jiraIssue struct { - ID string - Key string - TypeName string - ProjectKey string - Summary string - Description string - CustomFields jira.CustomFields -} - -// newJiraClient returns jira.Client for username and password (API token). -// To generate an API token, go to https://id.atlassian.com/manage-profile/security/api-tokens. -func newJiraClient(baseURL string, username string, password string) (*jiraClient, error) { - tp := jira.BasicAuthTransport{ - Username: username, - Password: password, - } - client, err := jira.NewClient(tp.Client(), baseURL) - if err != nil { - return nil, fmt.Errorf("cannot create Jira client: %w", err) - } - return &jiraClient{ - client: client, - }, nil -} - -// getIssueDetails stores a subset of details from jira.Issue into jiraIssue. -func (j *jiraClient) getIssueDetails(issueID string) (jiraIssue, error) { - issue, _, err := j.client.Issue.Get(issueID, nil) - if err != nil { - return jiraIssue{}, err - } - customFields, _, err := j.client.Issue.GetCustomFields(issueID) - if err != nil { - return jiraIssue{}, err - } - return jiraIssue{ - ID: issue.ID, - Key: issue.Key, - TypeName: issue.Fields.Type.Name, - ProjectKey: issue.Fields.Project.Name, - Summary: issue.Fields.Summary, - Description: issue.Fields.Description, - CustomFields: customFields, - }, nil -} - -func newIssue(details *jiraIssue) *jira.Issue { - var issue jira.Issue - issue.Fields = &jira.IssueFields{} - issue.Fields.Project = jira.Project{ - Key: details.ProjectKey, - } - issue.Fields.Type = jira.IssueType{ - Name: details.TypeName, - } - issue.Fields.Summary = details.Summary - issue.Fields.Description = details.Description - - if details.CustomFields != nil { - issue.Fields.Unknowns = make(map[string]interface{}) - for key, value := range details.CustomFields { - issue.Fields.Unknowns[key] = map[string]string{"value": value} - } - } - return &issue -} - -func (d jiraIssue) url() string { - return fmt.Sprintf("%s/browse/%s", strings.TrimSuffix(jiraBaseURL, "/"), d.Key) -} - -// createJiraIssue creates a **real** JIRA issue. -func createJiraIssue(client *jiraClient, issue *jira.Issue) (jiraIssue, error) { - newIssue, _, err := client.client.Issue.Create(issue) - if err != nil { - return jiraIssue{}, err - } - details, err := client.getIssueDetails(newIssue.ID) - if err != nil { - return jiraIssue{}, err - } - return details, nil -} - -// createTrackingIssue creates a release tracking issue. -// See example ticket: -// - https://cockroachlabs.atlassian.net/browse/REL-3 -// - https://cockroachlabs.atlassian.net/rest/api/2/issue/REL-3 -func createTrackingIssue( - client *jiraClient, release releaseInfo, sreIssue jiraIssue, dryRun bool, -) (jiraIssue, error) { - templateArgs := trackingIssueTemplateArgs{ - Version: release.nextReleaseVersion, - Tag: release.buildInfo.Tag, - SHA: release.buildInfo.SHA, - SREIssue: sreIssue.Key, - } - description, err := templateToText(trackingIssueTemplate, templateArgs) - if err != nil { - return jiraIssue{}, fmt.Errorf("cannot parse tracking issue template: %w", err) - } - summary := fmt.Sprintf("Release: %s", release.nextReleaseVersion) - projectKey := "RE" - if dryRun { - projectKey = dryRunProject - } - issue := newIssue(&jiraIssue{ - // TODO: remove the following when ready - // Before sending the post request, let's override - // the `REL` project with our test `RE` project. - ProjectKey: projectKey, - // TODO: switch to TypeName: "CRDB Release", which requires some fields to be set - TypeName: "Task", - Summary: summary, - Description: description, - }) - return createJiraIssue(client, issue) -} - -// createSREIssue creates an SREOPS ticket to request release candidate qualification. -// See example ticket: -// - https://cockroachlabs.atlassian.net/browse/SREOPS-4037 -// - https://cockroachlabs.atlassian.net/rest/api/2/issue/SREOPS-4037 -// TODO(celia): [Future "week 0" work] We'll eventually want the ability to specify -// a qualification partition & friendly ID: -// During the stability period, release managers may be qualifying multiple candidates -// at the same time. If that's the case, release managers will want the ability to -// explicitly specify which partition to use, so that we don't "overwrite" the -// qualification of one release candidate by pushing a second release candidate -// to the same cluster. Tracked in: https://cockroachlabs.atlassian.net/browse/RE-83 -func createSREIssue(client *jiraClient, release releaseInfo, dryRun bool) (jiraIssue, error) { - templateArgs := sreIssueTemplateArgs{ - Version: release.nextReleaseVersion, - Tag: release.buildInfo.Tag, - } - description, err := templateToHTML(sreIssueTemplate, templateArgs) - if err != nil { - return jiraIssue{}, fmt.Errorf("cannot parse SRE issue template: %w", err) - } - projectKey := "SREOPS" - summary := fmt.Sprintf("Deploy %s to release qualification cluster", release.nextReleaseVersion) - customFields := make(jira.CustomFields) - customFields[customFieldHasSLAKey] = "Yes" - if dryRun { - projectKey = dryRunProject - customFields = nil - } - issue := newIssue(&jiraIssue{ - ProjectKey: projectKey, - TypeName: "Task", - Summary: summary, - Description: description, - CustomFields: customFields, - }) - return createJiraIssue(client, issue) -} diff --git a/pkg/cmd/release/main.go b/pkg/cmd/release/main.go index 69c67f02baa..83a5fc388a3 100644 --- a/pkg/cmd/release/main.go +++ b/pkg/cmd/release/main.go @@ -34,7 +34,6 @@ func main() { } func init() { - rootCmd.AddCommand(pickSHACmd) rootCmd.AddCommand(postReleaseSeriesBlockersCmd) rootCmd.AddCommand(setOrchestrationVersionCmd) } diff --git a/pkg/cmd/release/metadata.go b/pkg/cmd/release/metadata.go deleted file mode 100644 index b7b04257058..00000000000 --- a/pkg/cmd/release/metadata.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2019 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package main - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - - "cloud.google.com/go/storage" -) - -type buildInfo struct { - Tag string `json:"tag"` - SHA string `json:"sha"` - Timestamp string `json:"timestamp"` -} - -// getBuildInfo retrieves the release qualification metadata file and returns its unmarshalled struct -func getBuildInfo(ctx context.Context, bucket string, obj string) (buildInfo, error) { - client, err := storage.NewClient(ctx) - if err != nil { - return buildInfo{}, fmt.Errorf("cannot create GCS client: %w", err) - } - reader, err := client.Bucket(bucket).Object(obj).NewReader(ctx) - if err != nil { - return buildInfo{}, fmt.Errorf("cannot create GCS reader: %w", err) - } - defer func() { - _ = reader.Close() - }() - - data, err := ioutil.ReadAll(reader) - if err != nil { - return buildInfo{}, fmt.Errorf("cannot read GCS object: %w", err) - } - var info buildInfo - if err := json.Unmarshal(data, &info); err != nil { - return buildInfo{}, fmt.Errorf("cannot unmarshal metadata: %w", err) - } - return info, nil -} - -// publishReleaseCandidateInfo copies release candidate metadata to a separate location. -// This file will be used by the week 1 automation. -func publishReleaseCandidateInfo( - ctx context.Context, next releaseInfo, bucket string, obj string, -) error { - marshalled, err := json.MarshalIndent(next.buildInfo, "", " ") - if err != nil { - return fmt.Errorf("cannot marshall buildInfo: %w", err) - } - client, err := storage.NewClient(ctx) - if err != nil { - return fmt.Errorf("cannot create storage client: %w", err) - } - wc := client.Bucket(bucket).Object(obj).NewWriter(ctx) - wc.ContentType = "application/json" - if _, err := wc.Write(marshalled); err != nil { - return fmt.Errorf("cannot write to bucket: %w", err) - } - if err := wc.Close(); err != nil { - return fmt.Errorf("cannot close storage writer filehandle: %w", err) - } - return nil -} diff --git a/pkg/cmd/release/pick_sha.go b/pkg/cmd/release/pick_sha.go deleted file mode 100644 index 4cd48e166ba..00000000000 --- a/pkg/cmd/release/pick_sha.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2019 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package main - -import ( - "context" - "fmt" - "html/template" - "os" - - "github.com/spf13/cobra" -) - -const ( - qualifyBucket = "qualify-bucket" - qualifyObjectPrefix = "qualify-object-prefix" - releaseBucket = "release-bucket" - releaseObjectPrefix = "release-object-prefix" -) - -var pickSHAFlags = struct { - qualifyBucket string - qualifyObjectPrefix string - releaseBucket string - releaseObjectPrefix string - releaseSeries string - templatesDir string - smtpUser string - smtpHost string - smtpPort int - emailAddresses []string - dryRun bool -}{} - -var pickSHACmd = &cobra.Command{ - Use: "pick-sha", - Short: "Pick release git SHA for a particular version and communicate the choice via Jira and email", - // TODO: improve Long description - Long: "Pick release git SHA for a particular version and communicate the choice via Jira and email", - RunE: pickSHA, -} - -func init() { - // TODO: improve flag usage comments - pickSHACmd.Flags().StringVar(&pickSHAFlags.qualifyBucket, qualifyBucket, "", "release qualification metadata GCS bucket") - pickSHACmd.Flags().StringVar(&pickSHAFlags.qualifyObjectPrefix, qualifyObjectPrefix, "", - "release qualification object prefix") - pickSHACmd.Flags().StringVar(&pickSHAFlags.releaseBucket, releaseBucket, "", "release candidates metadata GCS bucket") - pickSHACmd.Flags().StringVar(&pickSHAFlags.releaseObjectPrefix, releaseObjectPrefix, "", "release candidate object prefix") - pickSHACmd.Flags().StringVar(&pickSHAFlags.releaseSeries, releaseSeries, "", "major release series") - pickSHACmd.Flags().StringVar(&pickSHAFlags.templatesDir, templatesDir, "", "templates directory") - pickSHACmd.Flags().StringVar(&pickSHAFlags.smtpUser, smtpUser, os.Getenv(envSMTPUser), "SMTP user name") - pickSHACmd.Flags().StringVar(&pickSHAFlags.smtpHost, smtpHost, "", "SMTP host") - pickSHACmd.Flags().IntVar(&pickSHAFlags.smtpPort, smtpPort, 0, "SMTP port") - pickSHACmd.Flags().StringArrayVar(&pickSHAFlags.emailAddresses, emailAddresses, []string{}, "email addresses") - pickSHACmd.Flags().BoolVar(&pickSHAFlags.dryRun, dryRun, false, "use dry run Jira project for issues") - - requiredFlags := []string{ - qualifyBucket, - qualifyObjectPrefix, - releaseBucket, - releaseObjectPrefix, - releaseSeries, - smtpUser, - smtpHost, - smtpPort, - emailAddresses, - } - for _, flag := range requiredFlags { - if err := pickSHACmd.MarkFlagRequired(flag); err != nil { - panic(err) - } - } -} - -func pickSHA(_ *cobra.Command, _ []string) error { - smtpPassword := os.Getenv("SMTP_PASSWORD") - if smtpPassword == "" { - return fmt.Errorf("SMTP_PASSWORD environment variable should be set") - } - jiraUsername := os.Getenv("JIRA_USERNAME") - if jiraUsername == "" { - return fmt.Errorf("JIRA_USERNAME environment variable should be set") - } - jiraToken := os.Getenv("JIRA_TOKEN") - if jiraToken == "" { - return fmt.Errorf("JIRA_TOKEN environment variable should be set") - } - - nextRelease, err := findNextRelease(pickSHAFlags.releaseSeries) - if err != nil { - return fmt.Errorf("cannot find next release: %w", err) - } - // TODO: improve stdout message - fmt.Println("Previous version:", nextRelease.prevReleaseVersion) - fmt.Println("Next version:", nextRelease.nextReleaseVersion) - fmt.Println("Release SHA:", nextRelease.buildInfo.SHA) - - // TODO: before copying check if it's already there and bail if exists, can be forced by -f - releaseInfoPath := fmt.Sprintf("%s/%s.json", pickSHAFlags.releaseObjectPrefix, nextRelease.nextReleaseVersion) - fmt.Println("Publishing release candidate metadata") - if err := publishReleaseCandidateInfo(context.Background(), nextRelease, pickSHAFlags.releaseBucket, releaseInfoPath); err != nil { - return fmt.Errorf("cannot publish release metadata: %w", err) - } - - fmt.Println("Creating SRE issue") - jiraClient, err := newJiraClient(jiraBaseURL, jiraUsername, jiraToken) - if err != nil { - return fmt.Errorf("cannot create Jira client: %w", err) - } - sreIssue, err := createSREIssue(jiraClient, nextRelease, pickSHAFlags.dryRun) - if err != nil { - return err - } - - fmt.Println("Creating tracking issue") - trackingIssue, err := createTrackingIssue(jiraClient, nextRelease, sreIssue, pickSHAFlags.dryRun) - if err != nil { - return fmt.Errorf("cannot create tracking issue: %w", err) - } - diffURL := template.URL( - fmt.Sprintf("https://github.com/cockroachdb/cockroach/compare/%s...%s", - nextRelease.prevReleaseVersion, - nextRelease.buildInfo.SHA)) - args := messageDataPickSHA{ - Version: nextRelease.nextReleaseVersion, - SHA: nextRelease.buildInfo.SHA, - TrackingIssue: trackingIssue.Key, - TrackingIssueURL: template.URL(trackingIssue.url()), - DiffURL: diffURL, - } - opts := sendOpts{ - templatesDir: pickSHAFlags.templatesDir, - from: fmt.Sprintf("Justin Beaver <%s>", pickSHAFlags.smtpUser), - host: pickSHAFlags.smtpHost, - port: pickSHAFlags.smtpPort, - user: pickSHAFlags.smtpUser, - password: smtpPassword, - to: pickSHAFlags.emailAddresses, - } - fmt.Println("Sending email") - if err := sendMailPickSHA(args, opts); err != nil { - return fmt.Errorf("cannot send email: %w", err) - } - return nil -}