Skip to content

Commit 05a8c31

Browse files
authored
Add bump version using yaml (#167)
* Add new command to bump version using yaml selector
1 parent 6a90e20 commit 05a8c31

File tree

7 files changed

+298
-9
lines changed

7 files changed

+298
-9
lines changed

.github/workflows/lint.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ jobs:
3232
steps:
3333
- uses: actions/setup-go@v5
3434
with:
35-
go-version: 1.19
35+
go-version: 1.21
3636
- uses: actions/checkout@v4
3737
- name: golangci-lint
38-
uses: golangci/golangci-lint-action@v3
38+
uses: golangci/golangci-lint-action@v4
3939
with:
4040
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
41-
version: v1.50.1
41+
version: v1.57.2
4242

4343
# Optional: working directory, useful for monorepos
4444
# working-directory: somedir

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ telefonistka bump-overwrite \
122122
--file <(echo -e "image:\n tag: v3.4.9") \
123123
```
124124

125-
It currently supports full file overwrite and regex based replacement.
125+
It currently supports full file overwrite, regex and yaml based replacement.
126126
See [here](docs/version_bumping.md) for more details
127127

128128
### GitHub Push events fanout/multiplexing

cmd/telefonistka/bump-version-yaml.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package telefonistka
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
lru "github.com/hashicorp/golang-lru/v2"
10+
"github.com/hexops/gotextdiff"
11+
"github.com/hexops/gotextdiff/myers"
12+
"github.com/hexops/gotextdiff/span"
13+
"github.com/mikefarah/yq/v4/pkg/yqlib"
14+
log "github.com/sirupsen/logrus"
15+
"github.com/spf13/cobra"
16+
"github.com/wayfair-incubator/telefonistka/internal/pkg/githubapi"
17+
)
18+
19+
// This is still(https://github.com/spf13/cobra/issues/1862) the documented way to use cobra
20+
func init() { //nolint:gochecknoinits
21+
var targetRepo string
22+
var targetFile string
23+
var address string
24+
var replacement string
25+
var githubHost string
26+
var triggeringRepo string
27+
var triggeringRepoSHA string
28+
var triggeringActor string
29+
var autoMerge bool
30+
eventCmd := &cobra.Command{
31+
Use: "bump-yaml",
32+
Short: "Bump artifact version in a file using yaml selector",
33+
Long: `Bump artifact version in a file using yaml selector.
34+
This will open a pull request in the target repo.
35+
This command uses yq selector to find the yaml value to replace.
36+
`,
37+
Args: cobra.ExactArgs(0),
38+
Run: func(cmd *cobra.Command, args []string) {
39+
bumpVersionYaml(targetRepo, targetFile, address, replacement, githubHost, triggeringRepo, triggeringRepoSHA, triggeringActor, autoMerge)
40+
},
41+
}
42+
eventCmd.Flags().StringVarP(&targetRepo, "target-repo", "t", getEnv("TARGET_REPO", ""), "Target Git repository slug(e.g. org-name/repo-name), defaults to TARGET_REPO env var.")
43+
eventCmd.Flags().StringVarP(&targetFile, "target-file", "f", getEnv("TARGET_FILE", ""), "Target file path(from repo root), defaults to TARGET_FILE env var.")
44+
eventCmd.Flags().StringVar(&address, "address", "", "Yaml value address described as a yq selector, e.g. '.db.[] | select(.name == \"postgres\").image.tag'.")
45+
eventCmd.Flags().StringVarP(&replacement, "replacement-string", "n", "", "Replacement string that includes the version value of new artifact, e.g. 'v2.7.1'.")
46+
eventCmd.Flags().StringVarP(&githubHost, "github-host", "g", "", "GitHub instance HOSTNAME, defaults to \"github.com\". This is used for GitHub Enterprise Server instances.")
47+
eventCmd.Flags().StringVarP(&triggeringRepo, "triggering-repo", "p", getEnv("GITHUB_REPOSITORY", ""), "Github repo triggering the version bump(e.g. `octocat/Hello-World`) defaults to GITHUB_REPOSITORY env var.")
48+
eventCmd.Flags().StringVarP(&triggeringRepoSHA, "triggering-repo-sha", "s", getEnv("GITHUB_SHA", ""), "Git SHA of triggering repo, defaults to GITHUB_SHA env var.")
49+
eventCmd.Flags().StringVarP(&triggeringActor, "triggering-actor", "a", getEnv("GITHUB_ACTOR", ""), "GitHub user of the person/bot who triggered the bump, defaults to GITHUB_ACTOR env var.")
50+
eventCmd.Flags().BoolVar(&autoMerge, "auto-merge", false, "Automatically merges the created PR, defaults to false.")
51+
rootCmd.AddCommand(eventCmd)
52+
}
53+
54+
func bumpVersionYaml(targetRepo string, targetFile string, address string, value string, githubHost string, triggeringRepo string, triggeringRepoSHA string, triggeringActor string, autoMerge bool) {
55+
ctx := context.Background()
56+
var githubRestAltURL string
57+
58+
if githubHost != "" {
59+
githubRestAltURL = "https://" + githubHost + "/api/v3"
60+
log.Infof("Github REST API endpoint is configured to %s", githubRestAltURL)
61+
}
62+
var mainGithubClientPair githubapi.GhClientPair
63+
mainGhClientCache, _ := lru.New[string, githubapi.GhClientPair](128)
64+
65+
mainGithubClientPair.GetAndCache(mainGhClientCache, "GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY_PATH", "GITHUB_OAUTH_TOKEN", strings.Split(targetRepo, "/")[0], ctx)
66+
67+
var ghPrClientDetails githubapi.GhPrClientDetails
68+
69+
ghPrClientDetails.GhClientPair = &mainGithubClientPair
70+
ghPrClientDetails.Ctx = ctx
71+
ghPrClientDetails.Owner = strings.Split(targetRepo, "/")[0]
72+
ghPrClientDetails.Repo = strings.Split(targetRepo, "/")[1]
73+
ghPrClientDetails.PrLogger = log.WithFields(log.Fields{}) // TODO what fields should be here?
74+
75+
defaultBranch, _ := ghPrClientDetails.GetDefaultBranch()
76+
77+
initialFileContent, err, _ := githubapi.GetFileContent(ghPrClientDetails, defaultBranch, targetFile)
78+
if err != nil {
79+
ghPrClientDetails.PrLogger.Errorf("Fail to fetch file content:%s\n", err)
80+
os.Exit(1)
81+
}
82+
newFileContent, err := updateYaml(initialFileContent, address, value)
83+
if err != nil {
84+
ghPrClientDetails.PrLogger.Errorf("Fail to update yaml:%s\n", err)
85+
os.Exit(1)
86+
}
87+
88+
edits := myers.ComputeEdits(span.URIFromPath(""), initialFileContent, newFileContent)
89+
ghPrClientDetails.PrLogger.Infof("Diff:\n%s", gotextdiff.ToUnified("Before", "After", initialFileContent, edits))
90+
91+
err = githubapi.BumpVersion(ghPrClientDetails, "main", targetFile, newFileContent, triggeringRepo, triggeringRepoSHA, triggeringActor, autoMerge)
92+
if err != nil {
93+
log.Errorf("Failed to bump version: %v", err)
94+
os.Exit(1)
95+
}
96+
}
97+
98+
func updateYaml(yamlContent string, address string, value string) (string, error) {
99+
yqExpression := fmt.Sprintf("(%s)=\"%s\"", address, value)
100+
101+
preferences := yqlib.NewDefaultYamlPreferences()
102+
evaluate, err := yqlib.NewStringEvaluator().Evaluate(yqExpression, yamlContent, yqlib.NewYamlEncoder(preferences), yqlib.NewYamlDecoder(preferences))
103+
if err != nil {
104+
return "", err
105+
}
106+
return evaluate, nil
107+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package telefonistka
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestUpdateYaml(t *testing.T) {
8+
t.Parallel()
9+
10+
tests := []struct {
11+
name string
12+
yamlContent string
13+
address string
14+
value string
15+
want string
16+
}{
17+
{
18+
name: "Test simple",
19+
yamlContent: `
20+
tag: "16.1"
21+
`,
22+
address: `.tag`,
23+
value: "16.2",
24+
want: `
25+
tag: "16.2"
26+
`,
27+
},
28+
{
29+
name: "Test nested",
30+
yamlContent: `
31+
image:
32+
repository: "postgres"
33+
tag: "16.1"
34+
`,
35+
address: `.image.tag`,
36+
value: "16.2",
37+
want: `
38+
image:
39+
repository: "postgres"
40+
tag: "16.2"
41+
`,
42+
},
43+
{
44+
name: "Test nested select",
45+
yamlContent: `
46+
db:
47+
- name: "postgres"
48+
image:
49+
repository: "postgres"
50+
tag: "16.1"
51+
`,
52+
address: `.db.[] | select(.name == "postgres").image.tag`,
53+
value: "16.2",
54+
want: `
55+
db:
56+
- name: "postgres"
57+
image:
58+
repository: "postgres"
59+
tag: "16.2"
60+
`,
61+
},
62+
{
63+
name: "Test add missing",
64+
yamlContent: `
65+
image:
66+
repository: "postgres"
67+
`,
68+
address: `.image.tag`,
69+
value: "16.2",
70+
want: `
71+
image:
72+
repository: "postgres"
73+
tag: "16.2"
74+
`,
75+
},
76+
}
77+
78+
for _, tt := range tests {
79+
tt := tt
80+
t.Run(tt.name, func(t *testing.T) {
81+
t.Parallel()
82+
got, err := updateYaml(tt.yamlContent, tt.address, tt.value)
83+
if err != nil {
84+
t.Errorf("updateYaml() error = %v", err)
85+
return
86+
}
87+
if got != tt.want {
88+
t.Errorf("updateYaml() got = %v, want %v", got, tt.want)
89+
}
90+
})
91+
}
92+
}

docs/version_bumping.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
If your IaC repo deploys software you maintain internally you probably want to automate artifact version bumping.
44
Telefonistka can automate opening the IaC repo PR for the version change from the Code repo pipeline.
55

6-
Currently, two modes of operation are supported:
6+
Currently, three modes of operation are supported:
77

88
## Whole file overwrite
99

@@ -56,3 +56,30 @@ Flags:
5656
notes:
5757

5858
* This assumes the target file already exist in the target repo.
59+
60+
## YAML based value replace
61+
62+
```shell
63+
Bump artifact version in a file using yaml selector.
64+
This will open a pull request in the target repo.
65+
This command uses yq selector to find the yaml value to replace.
66+
67+
Usage:
68+
telefonistka bump-yaml [flags]
69+
70+
Flags:
71+
--address string Yaml value address described as a yq selector, e.g. '.db.[] | select(.name == "postgres").image.tag'.
72+
--auto-merge Automatically merges the created PR, defaults to false.
73+
-g, --github-host string GitHub instance HOSTNAME, defaults to "github.com". This is used for GitHub Enterprise Server instances.
74+
-h, --help help for bump-yaml
75+
-n, --replacement-string string Replacement string that includes the version value of new artifact, e.g. 'v2.7.1'.
76+
-f, --target-file string Target file path(from repo root), defaults to TARGET_FILE env var.
77+
-t, --target-repo string Target Git repository slug(e.g. org-name/repo-name), defaults to TARGET_REPO env var.
78+
-a, --triggering-actor string GitHub user of the person/bot who triggered the bump, defaults to GITHUB_ACTOR env var.
79+
-p, --triggering-repo octocat/Hello-World Github repo triggering the version bump(e.g. octocat/Hello-World) defaults to GITHUB_REPOSITORY env var.
80+
-s, --triggering-repo-sha string Git SHA of triggering repo, defaults to GITHUB_SHA env var.
81+
```
82+
83+
notes:
84+
85+
* This assumes the target file already exist in the target repo.

go.mod

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/wayfair-incubator/telefonistka
22

3-
go 1.20
3+
go 1.21
44

55
require (
66
github.com/alexliesenfeld/health v0.8.0
@@ -25,26 +25,43 @@ require (
2525

2626
require (
2727
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
28+
github.com/a8m/envsubst v1.4.2 // indirect
29+
github.com/alecthomas/participle/v2 v2.1.1 // indirect
2830
github.com/beorn7/perks v1.0.1 // indirect
2931
github.com/cespare/xxhash/v2 v2.2.0 // indirect
3032
github.com/cloudflare/circl v1.3.3 // indirect
33+
github.com/dimchansky/utfbom v1.1.1 // indirect
34+
github.com/elliotchance/orderedmap v1.5.1 // indirect
35+
github.com/fatih/color v1.16.0 // indirect
36+
github.com/goccy/go-json v0.10.2 // indirect
37+
github.com/goccy/go-yaml v1.11.3 // indirect
3138
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
3239
github.com/golang/protobuf v1.5.3 // indirect
3340
github.com/google/go-github/v50 v50.1.0 // indirect
3441
github.com/google/go-github/v56 v56.0.0 // indirect
3542
github.com/google/go-querystring v1.1.0 // indirect
3643
github.com/gorilla/mux v1.8.0 // indirect
3744
github.com/inconshreveable/mousetrap v1.1.0 // indirect
45+
github.com/jinzhu/copier v0.4.0 // indirect
46+
github.com/magiconair/properties v1.8.7 // indirect
47+
github.com/mattn/go-colorable v0.1.13 // indirect
48+
github.com/mattn/go-isatty v0.0.20 // indirect
3849
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
50+
github.com/mikefarah/yq/v4 v4.43.1 // indirect
51+
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
3952
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
4053
github.com/prometheus/common v0.44.0 // indirect
4154
github.com/prometheus/procfs v0.11.1 // indirect
4255
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
4356
github.com/spf13/pflag v1.0.5 // indirect
44-
golang.org/x/crypto v0.16.0 // indirect
45-
golang.org/x/net v0.19.0 // indirect
46-
golang.org/x/sys v0.15.0 // indirect
57+
github.com/yuin/gopher-lua v1.1.1 // indirect
58+
golang.org/x/crypto v0.21.0 // indirect
59+
golang.org/x/net v0.22.0 // indirect
60+
golang.org/x/sys v0.18.0 // indirect
61+
golang.org/x/text v0.14.0 // indirect
4762
golang.org/x/time v0.3.0 // indirect
63+
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
4864
google.golang.org/appengine v1.6.7 // indirect
4965
google.golang.org/protobuf v1.31.0 // indirect
66+
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 // indirect
5067
)

0 commit comments

Comments
 (0)