diff --git a/.github/workflows/bypass-test.yaml b/.github/workflows/bypass-test.yaml index 3a3102e3e574..93c5ba2869d3 100644 --- a/.github/workflows/bypass-test.yaml +++ b/.github/workflows/bypass-test.yaml @@ -9,6 +9,7 @@ on: - 'mkdocs.yml' - 'LICENSE' - '.release-please-manifest.json' + - 'helm/trivy/Chart.yaml' pull_request: paths: - '**.md' @@ -16,6 +17,7 @@ on: - 'mkdocs.yml' - 'LICENSE' - '.release-please-manifest.json' + - 'helm/trivy/Chart.yaml' jobs: test: name: Test diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index 3a7db4970065..05e494868a1e 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -4,6 +4,11 @@ name: Publish Helm chart on: workflow_dispatch: pull_request: + types: + - opened + - synchronize + - reopened + - closed branches: - main paths: @@ -18,7 +23,9 @@ env: KIND_VERSION: "v0.14.0" KIND_IMAGE: "kindest/node:v1.23.6@sha256:b1fa224cc6c7ff32455e0b1fd9cbfd3d3bc87ecaa8fcb06961ed1afb3db0f9ae" jobs: + # `test-chart` job starts if a PR with Helm Chart is created, merged etc. test-chart: + if: github.event_name != 'push' runs-on: ubuntu-20.04 steps: - name: Checkout @@ -48,8 +55,36 @@ jobs: sed -i -e '136s,false,'true',g' ./helm/trivy/values.yaml ct lint-and-install --validate-maintainers=false --charts helm/trivy + # `update-chart-version` job starts if a new tag is pushed + update-chart-version: + if: github.event_name == 'push' + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4.1.6 + with: + fetch-depth: 0 + - name: Set up Git user + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + + - name: Install tools + uses: aquaproj/aqua-installer@v3.0.1 + with: + aqua_version: v1.25.0 + aqua_opts: "" + + - name: Create a PR with Trivy version + run: mage helm:updateVersion + env: + # Use ORG_REPO_TOKEN instead of GITHUB_TOKEN + # This allows the created PR to trigger tests and other workflows + GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} + + # `publish-chart` job starts if a PR with a new Helm Chart is merged or manually publish-chart: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' needs: - test-chart runs-on: ubuntu-20.04 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8932a683c5bc..05edadf93331 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,6 +7,7 @@ on: - 'mkdocs.yml' - 'LICENSE' - '.release-please-manifest.json' ## don't run tests for release-please PRs + - 'helm/trivy/Chart.yaml' merge_group: workflow_dispatch: diff --git a/magefiles/helm.go b/magefiles/helm.go new file mode 100644 index 000000000000..61d4bac6be8a --- /dev/null +++ b/magefiles/helm.go @@ -0,0 +1,110 @@ +//go:build mage_helm + +package main + +import ( + "fmt" + "log" + "os" + "strconv" + "strings" + + "github.com/magefile/mage/sh" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" +) + +const chartFile = "./helm/trivy/Chart.yaml" + +func main() { + trivyVersion, err := version() + if err != nil { + log.Fatalf("could not determine Trivy version: %v", err) + } + input, err := os.ReadFile(chartFile) + if err != nil { + log.Fatalf("could not find helm chart %s: %v", chartFile, err) + } + jsonData := map[string]interface{}{} + if err := yaml.Unmarshal(input, &jsonData); err != nil { + log.Fatalf("could not unmarshal helm chart %s: %v", chartFile, err) + } + currentAppVersion, ok := jsonData["appVersion"].(string) + if !ok { + log.Fatalf("could not determine current app version") + } + currentHelmVersion, ok := jsonData["version"].(string) + if !ok { + log.Fatalf("could not determine current helm version") + } + newHelmVersion := newHelmVersion(currentHelmVersion, currentAppVersion, trivyVersion) + + log.Printf("Current helm version %q with Trivy %q will bump up %q with Trivy %q", + currentHelmVersion, currentAppVersion, newHelmVersion, trivyVersion) + + newBranch := fmt.Sprintf("ci/helm-chart/bump-trivy-to-%s", trivyVersion) + title := fmt.Sprintf("ci(helm): bump Trivy version to %s", trivyVersion) + description := fmt.Sprintf("This PR bumps Trivy up to the %s version for the Helm chart.", trivyVersion) + + cmds := [][]string{ + []string{"sed", "-i", "-e", fmt.Sprintf("s/appVersion: %s/appVersion: %s/g", currentAppVersion, trivyVersion), chartFile}, + []string{"sed", "-i", "-e", fmt.Sprintf("s/version: %s/version: %s/g", currentHelmVersion, trivyVersion), chartFile}, + []string{"git", "switch", "-c", newBranch}, + []string{"git", "add", "./helm/trivy/Chart.yaml"}, + []string{"git", "commit", "-m", title}, + []string{"git", "push", "origin", newBranch}, + []string{"gh", "pr", "create", "--base", "main", "--head", newBranch, "--title", title, "--body", description, "--repo", "$GITHUB_REPOSITORY"}, + } + + if err := runShCommands(cmds); err != nil { + log.Fatal(err) + } + log.Print("Successfully created PR with a new helm version") +} + +func runShCommands(cmds [][]string) error { + for _, cmd := range cmds { + if err := sh.Run(cmd[0], cmd[1:]...); err != nil { + return xerrors.Errorf("failed to run %v: %w", cmd, err) + } + } + return nil +} + +func splitVersion(version string) []int { + items := strings.Split(version, ".") + result := make([]int, len(items)) + for i, item := range items { + result[i], _ = strconv.Atoi(item) + } + return result +} + +func newHelmVersion(currentHelm, currentTrivy, newTrivy string) string { + ch := splitVersion(currentHelm) + ct := splitVersion(currentTrivy) + tr := splitVersion(newTrivy) + + if len(ch) != len(ct) || len(ch) != len(tr) || len(ch) != 3 { + log.Fatalf("invalid version lengths for %q, %q and %q", currentHelm, currentTrivy, newTrivy) + } + + n := len(ch) + res := make([]string, n) + if tr[0] != ct[0] { + res[0] = strconv.Itoa(tr[0]) + res[1] = strconv.Itoa(tr[1]) + res[2] = "0" + return strings.Join(res, ".") + } + + res[0] = strconv.Itoa(tr[0]) + if tr[1] != ct[1] { + res[1] = strconv.Itoa(ch[1] + tr[1] - ct[1]) + res[2] = "0" + } else { + res[1] = strconv.Itoa(ch[1]) + res[2] = strconv.Itoa(ch[2] + tr[2] - ct[2]) + } + return strings.Join(res, ".") +} diff --git a/magefiles/helm_test.go b/magefiles/helm_test.go new file mode 100644 index 000000000000..2f2611d2a399 --- /dev/null +++ b/magefiles/helm_test.go @@ -0,0 +1,61 @@ +//go:build mage_helm + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewVersion(t *testing.T) { + tests := []struct { + name string + currentHelmVersion string + currentTrivyVersion string + newTrivyVersion string + newHelmVersion string + }{ + { + "created the first patch", + "0.1.0", + "0.55.0", + "0.55.1", + "0.1.1", + }, + { + "created the second patch", + "0.1.1", + "0.55.1", + "0.55.2", + "0.1.2", + }, + { + "created the second patch but helm chart was changed", + "0.1.2", + "0.55.1", + "0.55.2", + "0.1.3", + }, + { + "created a new minor version", + "0.1.1", + "0.55.1", + "0.56.0", + "0.2.0", + }, + { + "created a new major version", + "0.1.1", + "0.55.1", + "1.0.0", + "1.0.0", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.newHelmVersion, newHelmVersion(test.currentHelmVersion, test.currentTrivyVersion, test.newTrivyVersion)) + }) + } +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 06ebd7e665e1..f70491bce2e8 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -489,3 +489,10 @@ func (CloudActions) Generate() error { func VEX(_ context.Context, dir string) error { return sh.RunWith(ENV, "go", "run", "-tags=mage_vex", "./magefiles/vex.go", "--dir", dir) } + +type Helm mg.Namespace + +// UpdateVersion updates a version for Trivy Helm Chart and creates a PR +func (Helm) UpdateVersion() error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_helm", "./magefiles") +}