Skip to content

Commit

Permalink
Add bump version using yaml (#167)
Browse files Browse the repository at this point in the history
* Add new command to bump version using yaml selector
  • Loading branch information
Ben10k authored Apr 15, 2024
1 parent 6a90e20 commit 05a8c31
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 9 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ jobs:
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: 1.21
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v1.50.1
version: v1.57.2

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ telefonistka bump-overwrite \
--file <(echo -e "image:\n tag: v3.4.9") \
```

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

### GitHub Push events fanout/multiplexing
Expand Down
107 changes: 107 additions & 0 deletions cmd/telefonistka/bump-version-yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package telefonistka

import (
"context"
"fmt"
"os"
"strings"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/mikefarah/yq/v4/pkg/yqlib"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/wayfair-incubator/telefonistka/internal/pkg/githubapi"
)

// This is still(https://github.com/spf13/cobra/issues/1862) the documented way to use cobra
func init() { //nolint:gochecknoinits
var targetRepo string
var targetFile string
var address string
var replacement string
var githubHost string
var triggeringRepo string
var triggeringRepoSHA string
var triggeringActor string
var autoMerge bool
eventCmd := &cobra.Command{
Use: "bump-yaml",
Short: "Bump artifact version in a file using yaml selector",
Long: `Bump artifact version in a file using yaml selector.
This will open a pull request in the target repo.
This command uses yq selector to find the yaml value to replace.
`,
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
bumpVersionYaml(targetRepo, targetFile, address, replacement, githubHost, triggeringRepo, triggeringRepoSHA, triggeringActor, autoMerge)
},
}
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.")
eventCmd.Flags().StringVarP(&targetFile, "target-file", "f", getEnv("TARGET_FILE", ""), "Target file path(from repo root), defaults to TARGET_FILE env var.")
eventCmd.Flags().StringVar(&address, "address", "", "Yaml value address described as a yq selector, e.g. '.db.[] | select(.name == \"postgres\").image.tag'.")
eventCmd.Flags().StringVarP(&replacement, "replacement-string", "n", "", "Replacement string that includes the version value of new artifact, e.g. 'v2.7.1'.")
eventCmd.Flags().StringVarP(&githubHost, "github-host", "g", "", "GitHub instance HOSTNAME, defaults to \"github.com\". This is used for GitHub Enterprise Server instances.")
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.")
eventCmd.Flags().StringVarP(&triggeringRepoSHA, "triggering-repo-sha", "s", getEnv("GITHUB_SHA", ""), "Git SHA of triggering repo, defaults to GITHUB_SHA env var.")
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.")
eventCmd.Flags().BoolVar(&autoMerge, "auto-merge", false, "Automatically merges the created PR, defaults to false.")
rootCmd.AddCommand(eventCmd)
}

func bumpVersionYaml(targetRepo string, targetFile string, address string, value string, githubHost string, triggeringRepo string, triggeringRepoSHA string, triggeringActor string, autoMerge bool) {
ctx := context.Background()
var githubRestAltURL string

if githubHost != "" {
githubRestAltURL = "https://" + githubHost + "/api/v3"
log.Infof("Github REST API endpoint is configured to %s", githubRestAltURL)
}
var mainGithubClientPair githubapi.GhClientPair
mainGhClientCache, _ := lru.New[string, githubapi.GhClientPair](128)

mainGithubClientPair.GetAndCache(mainGhClientCache, "GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY_PATH", "GITHUB_OAUTH_TOKEN", strings.Split(targetRepo, "/")[0], ctx)

var ghPrClientDetails githubapi.GhPrClientDetails

ghPrClientDetails.GhClientPair = &mainGithubClientPair
ghPrClientDetails.Ctx = ctx
ghPrClientDetails.Owner = strings.Split(targetRepo, "/")[0]
ghPrClientDetails.Repo = strings.Split(targetRepo, "/")[1]
ghPrClientDetails.PrLogger = log.WithFields(log.Fields{}) // TODO what fields should be here?

defaultBranch, _ := ghPrClientDetails.GetDefaultBranch()

initialFileContent, err, _ := githubapi.GetFileContent(ghPrClientDetails, defaultBranch, targetFile)
if err != nil {
ghPrClientDetails.PrLogger.Errorf("Fail to fetch file content:%s\n", err)
os.Exit(1)
}
newFileContent, err := updateYaml(initialFileContent, address, value)
if err != nil {
ghPrClientDetails.PrLogger.Errorf("Fail to update yaml:%s\n", err)
os.Exit(1)
}

edits := myers.ComputeEdits(span.URIFromPath(""), initialFileContent, newFileContent)
ghPrClientDetails.PrLogger.Infof("Diff:\n%s", gotextdiff.ToUnified("Before", "After", initialFileContent, edits))

err = githubapi.BumpVersion(ghPrClientDetails, "main", targetFile, newFileContent, triggeringRepo, triggeringRepoSHA, triggeringActor, autoMerge)
if err != nil {
log.Errorf("Failed to bump version: %v", err)
os.Exit(1)
}
}

func updateYaml(yamlContent string, address string, value string) (string, error) {
yqExpression := fmt.Sprintf("(%s)=\"%s\"", address, value)

preferences := yqlib.NewDefaultYamlPreferences()
evaluate, err := yqlib.NewStringEvaluator().Evaluate(yqExpression, yamlContent, yqlib.NewYamlEncoder(preferences), yqlib.NewYamlDecoder(preferences))
if err != nil {
return "", err
}
return evaluate, nil
}
92 changes: 92 additions & 0 deletions cmd/telefonistka/bump-version-yaml_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package telefonistka

import (
"testing"
)

func TestUpdateYaml(t *testing.T) {
t.Parallel()

tests := []struct {
name string
yamlContent string
address string
value string
want string
}{
{
name: "Test simple",
yamlContent: `
tag: "16.1"
`,
address: `.tag`,
value: "16.2",
want: `
tag: "16.2"
`,
},
{
name: "Test nested",
yamlContent: `
image:
repository: "postgres"
tag: "16.1"
`,
address: `.image.tag`,
value: "16.2",
want: `
image:
repository: "postgres"
tag: "16.2"
`,
},
{
name: "Test nested select",
yamlContent: `
db:
- name: "postgres"
image:
repository: "postgres"
tag: "16.1"
`,
address: `.db.[] | select(.name == "postgres").image.tag`,
value: "16.2",
want: `
db:
- name: "postgres"
image:
repository: "postgres"
tag: "16.2"
`,
},
{
name: "Test add missing",
yamlContent: `
image:
repository: "postgres"
`,
address: `.image.tag`,
value: "16.2",
want: `
image:
repository: "postgres"
tag: "16.2"
`,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := updateYaml(tt.yamlContent, tt.address, tt.value)
if err != nil {
t.Errorf("updateYaml() error = %v", err)
return
}
if got != tt.want {
t.Errorf("updateYaml() got = %v, want %v", got, tt.want)
}
})
}
}
29 changes: 28 additions & 1 deletion docs/version_bumping.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
If your IaC repo deploys software you maintain internally you probably want to automate artifact version bumping.
Telefonistka can automate opening the IaC repo PR for the version change from the Code repo pipeline.

Currently, two modes of operation are supported:
Currently, three modes of operation are supported:

## Whole file overwrite

Expand Down Expand Up @@ -56,3 +56,30 @@ Flags:
notes:

* This assumes the target file already exist in the target repo.

## YAML based value replace

```shell
Bump artifact version in a file using yaml selector.
This will open a pull request in the target repo.
This command uses yq selector to find the yaml value to replace.

Usage:
telefonistka bump-yaml [flags]

Flags:
--address string Yaml value address described as a yq selector, e.g. '.db.[] | select(.name == "postgres").image.tag'.
--auto-merge Automatically merges the created PR, defaults to false.
-g, --github-host string GitHub instance HOSTNAME, defaults to "github.com". This is used for GitHub Enterprise Server instances.
-h, --help help for bump-yaml
-n, --replacement-string string Replacement string that includes the version value of new artifact, e.g. 'v2.7.1'.
-f, --target-file string Target file path(from repo root), defaults to TARGET_FILE env var.
-t, --target-repo string Target Git repository slug(e.g. org-name/repo-name), defaults to TARGET_REPO env var.
-a, --triggering-actor string GitHub user of the person/bot who triggered the bump, defaults to GITHUB_ACTOR env var.
-p, --triggering-repo octocat/Hello-World Github repo triggering the version bump(e.g. octocat/Hello-World) defaults to GITHUB_REPOSITORY env var.
-s, --triggering-repo-sha string Git SHA of triggering repo, defaults to GITHUB_SHA env var.
```

notes:

* This assumes the target file already exist in the target repo.
25 changes: 21 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/wayfair-incubator/telefonistka

go 1.20
go 1.21

require (
github.com/alexliesenfeld/health v0.8.0
Expand All @@ -25,26 +25,43 @@ require (

require (
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/a8m/envsubst v1.4.2 // indirect
github.com/alecthomas/participle/v2 v2.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/elliotchance/orderedmap v1.5.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.11.3 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-github/v50 v50.1.0 // indirect
github.com/google/go-github/v56 v56.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mikefarah/yq/v4 v4.43.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 // indirect
)
Loading

0 comments on commit 05a8c31

Please sign in to comment.