Skip to content

Commit d46e15e

Browse files
committed
feat: add hack script for cutting RCs
Also refactored hack/cmd/prepare_rc for reliability. Signed-off-by: bwplotka <bwplotka@google.com>
1 parent a49edd0 commit d46e15e

File tree

5 files changed

+314
-119
lines changed

5 files changed

+314
-119
lines changed

.github/workflows/release-bot.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ jobs:
135135
id: prepare
136136
working-directory: ./main_branch
137137
run: |
138-
go run ./hack/prepare_rc ${{ env.BRANCH_NAME }} ../release_branch
138+
alias yq=go tool yq
139+
go run ./hack/prepare_rc -branch ${{ env.BRANCH_NAME }} -dir ../release_branch
139140
- name: Regen files
140141
working-directory: ./release_branch
141142
# Workflows can't edit workflows. Better to create PR and let tests fail.
@@ -188,4 +189,4 @@ jobs:
188189
--head $BOT_BRANCH \
189190
--title "chore: prepare for $RC release" \
190191
--body "Beep boop. Merging activates deployment. A fresh PR appears on merge. Boop beep."
191-
fi
192+
fi

hack/prepare_rc/main.go

Lines changed: 94 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// package main implements prepare_rc script.
16+
//
17+
// Run this script to prepare the prometheus-engine files for the next release;
18+
// without regenerating them (!).
19+
//
20+
// Example use:
21+
//
22+
// go run ./... \
23+
// -tag=v0.15.4-rc.0 \
24+
// -dir=../../../prometheus-engine
1525
package main
1626

1727
import (
1828
"errors"
29+
"flag"
1930
"fmt"
2031
"os"
2132
"path/filepath"
@@ -24,8 +35,16 @@ import (
2435
"strings"
2536
)
2637

27-
const VersionFile = "pkg/export/export.go"
28-
const ValuesFile = "charts/values.global.yaml"
38+
const (
39+
versionFile = "pkg/export/export.go"
40+
valuesFile = "charts/values.global.yaml"
41+
)
42+
43+
var (
44+
dir = flag.String("dir", ".", "The path with prometheus-engine repo/")
45+
tag = flag.String("tag", "", "Tag to release. Empty means the tag will be a next RC number from the last git tag in a given -branch.")
46+
branch = flag.String("branch", "", "Branch to use for auto-detecting tag to release. Must be matching ^release/(\\d+)\\.(\\d+)$. Can't be used with -tag.")
47+
)
2948

3049
type Branch struct {
3150
major int
@@ -54,95 +73,24 @@ func getBranch(branch string) (*Branch, error) {
5473
}, nil
5574
}
5675

57-
type ReleaseCandidate struct {
58-
branch *Branch
59-
patch int
60-
rc int
61-
}
62-
63-
func (b *Branch) lastTag() (string, error) {
76+
func (b *Branch) currentTag() (Tag, error) {
6477
tagRegex := fmt.Sprintf("v%d.%d.*", b.major, b.minor)
6578
tags, err := Cmd("git", "tag", "--list", tagRegex, "--sort=-v:refname").Run()
66-
return strings.SplitN(tags, "\n", 2)[0], err
67-
}
68-
69-
func (b *Branch) lastReleaseCandidate() (*ReleaseCandidate, error) {
70-
tag, err := b.lastTag()
71-
if err != nil {
72-
return nil, err
73-
}
74-
if tag == "" {
75-
return nil, nil
76-
}
77-
re := regexp.MustCompile(`^v\d+.\d+.(\d+)-rc.(\d+)$`)
78-
matches := re.FindStringSubmatch(tag)
79-
if matches == nil {
80-
return nil, fmt.Errorf("malformatted tag name %q", tag)
81-
}
82-
patchRaw, rcRaw := matches[1], matches[2]
83-
patch, err := strconv.Atoi(patchRaw)
8479
if err != nil {
85-
return nil, fmt.Errorf("malformatted tag name %q", tag)
80+
return Tag{}, err
8681
}
87-
rc, err := strconv.Atoi(rcRaw)
88-
if err != nil {
89-
return nil, fmt.Errorf("malformatted tag name %q", tag)
90-
}
91-
return &ReleaseCandidate{
92-
branch: b,
93-
patch: patch,
94-
rc: rc,
95-
}, nil
82+
return NewTag(strings.SplitN(tags, "\n", 2)[0])
9683
}
9784

98-
func (b *Branch) nextReleaseCandidate() (*ReleaseCandidate, error) {
99-
last, err := b.lastReleaseCandidate()
100-
if err != nil {
101-
return nil, err
102-
}
103-
if last == nil {
104-
return &ReleaseCandidate{
105-
branch: b,
106-
patch: 0,
107-
rc: 0,
108-
}, nil
109-
}
110-
hasRelease, err := last.hasRelease()
111-
if err != nil {
112-
return nil, err
113-
}
114-
if hasRelease {
115-
return &ReleaseCandidate{
116-
branch: last.branch,
117-
patch: last.patch + 1,
118-
rc: 0,
119-
}, nil
120-
}
121-
return &ReleaseCandidate{
122-
branch: last.branch,
123-
patch: last.patch,
124-
rc: last.rc + 1,
125-
}, nil
126-
}
127-
128-
func (rc *ReleaseCandidate) version() string {
129-
return fmt.Sprintf("v%d.%d.%d", rc.branch.major, rc.branch.minor, rc.patch)
130-
}
131-
132-
func (rc *ReleaseCandidate) hasRelease() (bool, error) {
133-
out, err := Cmd("git", "tag", "--list", rc.version()).Run()
134-
return out != "", err
135-
}
136-
137-
func (rc *ReleaseCandidate) updateVersionFile(repoPath string) error {
138-
path := filepath.Join(repoPath, VersionFile)
85+
func updateVersionFile(repoPath string, tag Tag) error {
86+
path := filepath.Join(repoPath, versionFile)
13987
contentBytes, err := os.ReadFile(path)
14088
if err != nil {
14189
return fmt.Errorf("failed to read file %s: %w", path, err)
14290
}
14391
content := string(contentBytes)
14492
re := regexp.MustCompile(`(mainModuleVersion\s*=\s*)".*"`)
145-
replacement := fmt.Sprintf(`${1}"%s"`, rc.version())
93+
replacement := fmt.Sprintf(`${1}"%s"`, tag.String())
14694
newContent := re.ReplaceAllString(content, replacement)
14795

14896
err = os.WriteFile(path, []byte(newContent), 0664)
@@ -152,18 +100,20 @@ func (rc *ReleaseCandidate) updateVersionFile(repoPath string) error {
152100
return nil
153101
}
154102

155-
func (rc *ReleaseCandidate) updateValuesFile(repoPath string) error {
156-
path := filepath.Join(repoPath, ValuesFile)
157-
_, err := Cmd("go", "tool", "yq", "e",
158-
fmt.Sprintf(`.version = "%d.%d.%d"`, rc.branch.major, rc.branch.minor, rc.patch),
103+
func updateValuesFile(repoPath string, tag Tag) error {
104+
path := filepath.Join(repoPath, valuesFile)
105+
if _, err := Cmd("yq", "e",
106+
fmt.Sprintf(`.version = "%d.%d.%d"`, tag.Major(), tag.Minor(), tag.Patch()),
159107
"-i", path,
160-
).Run()
161-
if err != nil {
108+
).Run(); err != nil {
162109
return err
163110
}
164111
for _, image := range []string{"configReloader", "operator", "ruleEvaluator", "datasourceSyncer"} {
165-
_, err := Cmd("go", "tool", "yq", "e",
166-
fmt.Sprintf(`.images.%s.tag = "%s-gke.%d"`, image, rc.version(), rc.rc),
112+
_, err := Cmd("yq", "e",
113+
// This assumes RC number is always same as the GKE number -- this is not always
114+
// correct e.g. when we retry releases on Louhi side. It's the best guess though
115+
// and we can update manifests later on.
116+
fmt.Sprintf(`.images.%s.tag = "v%d.%d.%d-gke.%d"`, image, tag.Major(), tag.Minor(), tag.Patch(), tag.RC()),
167117
"-i", path,
168118
).Run()
169119
if err != nil {
@@ -174,46 +124,73 @@ func (rc *ReleaseCandidate) updateValuesFile(repoPath string) error {
174124
}
175125

176126
func main() {
177-
if len(os.Args) < 3 {
178-
fmt.Printf("::error::Usage: %s <branch-name> <repo-path>\n", os.Args[0])
179-
fmt.Println("::error::Error: Missing arguments")
127+
flag.Parse()
128+
129+
if *dir == "" {
130+
fmt.Println("::error::Error: -dir has to be specified.")
131+
flag.Usage()
180132
os.Exit(1)
181133
}
182-
if _, err := Cmd("git", "fetch", "--tags", "-f").Run(); err != nil {
183-
fmt.Printf("::error::Failed to fetch tags: %v\n", err)
184-
}
185-
branch, err := getBranch(os.Args[1])
186-
if err != nil {
187-
fmt.Printf("::error::Failed to parse branch: %v\n", err)
134+
135+
if *tag != "" && *branch != "" {
136+
fmt.Println("::error::Error: -tag and -branch can't be specified in the same time.")
137+
flag.Usage()
138+
os.Exit(1)
188139
}
189-
nextRc, err := branch.nextReleaseCandidate()
190-
if err != nil {
191-
fmt.Printf("::error::Failed to get next release candidate: %v\n", err)
140+
141+
var releaseTag Tag
142+
if *tag != "" {
143+
var err error
144+
releaseTag, err = NewTag(*tag)
145+
if err != nil {
146+
fmt.Printf("::error::Failed to parse the -tag: %v\n", err)
147+
os.Exit(1)
148+
}
149+
} else if *branch != "" {
150+
// TODO(bwplotka): Why we ask for repo directory if we this command require this
151+
// script to be in the current directory? Fix it.
152+
if _, err := Cmd("git", "fetch", "--tags", "-f").Run(); err != nil {
153+
fmt.Printf("::error::Failed to fetch tags: %v\n", err)
154+
}
155+
branch, err := getBranch(os.Args[1])
156+
if err != nil {
157+
fmt.Printf("::error::Failed to parse branch: %v\n", err)
158+
}
159+
ct, err := branch.currentTag()
160+
if err != nil {
161+
fmt.Printf("::error::Failed to get next release candidate: %v\n", err)
162+
}
163+
releaseTag = ct.NextRC()
164+
} else {
165+
fmt.Println("::error::Error: Either -tag or -branch has to be specified.")
166+
flag.Usage()
167+
os.Exit(1)
192168
}
193169

194-
repoPath := os.Args[2]
195-
if err := nextRc.updateVersionFile(repoPath); err != nil {
170+
fmt.Println("Updating files to", tag)
171+
if err := updateVersionFile(*dir, releaseTag); err != nil {
196172
fmt.Printf("::error::Failed to update version file: %v\n", err)
173+
os.Exit(1)
197174
}
198-
if err := nextRc.updateValuesFile(repoPath); err != nil {
175+
if err := updateValuesFile(*dir, releaseTag); err != nil {
199176
fmt.Printf("::error::Failed to update value file: %v\n", err)
200-
}
201-
// For GH actions
202-
outputPath := os.Getenv("GITHUB_OUTPUT")
203-
if outputPath == "" {
204-
fmt.Println("::error::GITHUB_OUTPUT environment variable not set.")
205177
os.Exit(1)
206178
}
207-
outputFile, err := os.OpenFile(outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
208-
if err != nil {
209-
fmt.Printf("::error::Failed to open GITHUB_OUTPUT: %v\n", err)
210-
return
211-
}
212-
defer outputFile.Close()
213179

214-
s := fmt.Sprintf("full_rc_version=%s-rc.%d\n", nextRc.version(), nextRc.rc)
215-
_, err = outputFile.WriteString(s)
216-
if err != nil {
217-
fmt.Printf("::error::Failed to write to GITHUB_OUTPUT: %v\n", err)
180+
// For GH actions, optionally.
181+
outputPath := os.Getenv("GITHUB_OUTPUT")
182+
if outputPath != "" {
183+
outputFile, err := os.OpenFile(outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
184+
if err != nil {
185+
fmt.Printf("::error::Failed to open GITHUB_OUTPUT: %v\n", err)
186+
return
187+
}
188+
defer outputFile.Close()
189+
190+
s := fmt.Sprintf("full_rc_version=%v\n", releaseTag.String())
191+
_, err = outputFile.WriteString(s)
192+
if err != nil {
193+
fmt.Printf("::error::Failed to write to GITHUB_OUTPUT: %v\n", err)
194+
}
218195
}
219196
}

hack/prepare_rc/tag.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
8+
"github.com/Masterminds/semver/v3"
9+
)
10+
11+
var (
12+
rcRe = regexp.MustCompile(`^rc.(\d+)$`)
13+
)
14+
15+
// Tag is a helper for the tag operations.
16+
type Tag struct {
17+
semver.Version
18+
rc int
19+
}
20+
21+
// NewTag constructs a valid tag.
22+
func NewTag(tag string) (Tag, error) {
23+
v, err := semver.NewVersion(tag)
24+
if err != nil {
25+
return Tag{}, err
26+
}
27+
rcNum := -1
28+
if rc := v.Prerelease(); rc != "" {
29+
// We expect only -rc.X suffixes, nothing else.
30+
matches := rcRe.FindStringSubmatch(rc)
31+
if len(matches) < 2 {
32+
return Tag{}, fmt.Errorf(`tag suffix has to be empty or match <tag>-rc.(\d+)$ regex; got %v`, rc)
33+
}
34+
rcNum, err = strconv.Atoi(matches[1])
35+
if err != nil {
36+
panic(fmt.Errorf("regexp suppose to validate it's a number, but we can't parse it %v; %w", matches[1], err))
37+
}
38+
}
39+
return Tag{Version: *v, rc: rcNum}, nil
40+
}
41+
42+
func (t Tag) String() string {
43+
return "v" + t.Version.String()
44+
}
45+
46+
func (t Tag) RC() int {
47+
return t.rc
48+
}
49+
50+
// NextRC returns the next RC tag to release, bases on the current one.
51+
// If the current one is not an RC, new patch RC.0 will be returned.
52+
func (t Tag) NextRC() Tag {
53+
version := t.Version
54+
if t.rc == -1 {
55+
// Current tag is not a RC, we need to bump patch version to cut a new RC.
56+
version = t.Version.IncPatch()
57+
}
58+
rcNum := t.rc + 1
59+
var err error
60+
version, err = version.SetPrerelease(fmt.Sprintf("rc.%v", rcNum))
61+
if err != nil {
62+
panic(err)
63+
}
64+
return Tag{Version: version, rc: rcNum}
65+
}

hack/prepare_rc/tag_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestNextRC(t *testing.T) {
10+
tag, err := NewTag("v0.15.3-rc.5")
11+
require.NoError(t, err)
12+
require.Equal(t, "v0.15.3-rc.6", tag.NextRC().String())
13+
14+
tag, err = NewTag("v0.15.3")
15+
require.NoError(t, err)
16+
require.Equal(t, "v0.15.4-rc.0", tag.NextRC().String())
17+
18+
_, err = NewTag("v0.15.3-rc.5-magicsuffix")
19+
require.Error(t, err)
20+
}

0 commit comments

Comments
 (0)