CI: Add CI check for what's new link (#70046)

* Add whatsnewchecker

* Add whatsnewchecker_test

* Small fixes

* Add step in CI

* Fix lint

* Fix starlark

* t.Cleanup instead of separate func

* Skip check for test tags
This commit is contained in:
Dimitris Sotirakis
2023-06-14 13:51:01 +03:00
committed by GitHub
parent 9f065a8835
commit 91272ee4f9
6 changed files with 256 additions and 3 deletions

View File

@@ -2297,6 +2297,50 @@ environment:
image_pull_secrets: image_pull_secrets:
- dockerconfigjson - dockerconfigjson
kind: pipeline kind: pipeline
name: release-whatsnew-checker
node:
type: no-parallel
platform:
arch: amd64
os: linux
services: []
steps:
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.20.4
name: compile-build-cmd
- commands:
- ./bin/build whatsnew-checker
depends_on:
- compile-build-cmd
image: golang:1.20.4
name: whats-new-checker
trigger:
event:
exclude:
- promote
ref:
exclude:
- refs/tags/*-cloud*
include:
- refs/tags/v*
type: docker
volumes:
- host:
path: /var/run/docker.sock
name: docker
---
clone:
retries: 3
depends_on: []
environment:
EDITION: oss
image_pull_secrets:
- dockerconfigjson
kind: pipeline
name: release-oss-build-e2e-publish name: release-oss-build-e2e-publish
node: node:
type: no-parallel type: no-parallel
@@ -7308,6 +7352,6 @@ kind: secret
name: delivery-bot-app-private-key name: delivery-bot-app-private-key
--- ---
kind: signature kind: signature
hmac: 57a88408eae5719f7ffa85c575c46e5b90ea301627144cb738d2ac8b8396e973 hmac: c1eaa0e7d7427fd28ec0edba24a89aaa2bcb876391bd7ecde5f5fd55ffc0d0b5
... ...

View File

@@ -82,6 +82,11 @@ func main() {
&buildIDFlag, &buildIDFlag,
}, },
}, },
{
Name: "whatsnew-checker",
Usage: "Checks whatsNewUrl in package.json for differences between the tag and the docs version",
Action: WhatsNewChecker,
},
{ {
Name: "build-docker", Name: "build-docker",
Usage: "Build Grafana Docker images", Usage: "Build Grafana Docker images",

View File

@@ -0,0 +1,71 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/grafana/grafana/pkg/build/config"
"github.com/urfave/cli/v2"
"golang.org/x/mod/semver"
)
const GrafanaDir = "."
var whatsNewRegex = regexp.MustCompile(`^.*whats-new-in-(v\d*-[\d+]*)`)
type PackageJSON struct {
Grafana Grafana `json:"grafana"`
Version string `json:"version"`
}
type Grafana struct {
WhatsNewUrl string `json:"whatsNewUrl"`
}
func WhatsNewChecker(c *cli.Context) error {
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
if metadata.ReleaseMode.IsTest {
fmt.Println("test mode, skipping check")
return nil
}
if metadata.ReleaseMode.Mode != config.TagMode {
return fmt.Errorf("non-tag pipeline, exiting")
}
tag := fmt.Sprintf("v%s", metadata.GrafanaVersion)
if !semver.IsValid(tag) {
return fmt.Errorf("non-semver compatible version %s, exiting", tag)
}
majorMinorDigits := strings.Replace(semver.MajorMinor(tag), ".", "-", 1)
pkgJSONPath := filepath.Join(GrafanaDir, "package.json")
//nolint:gosec
pkgJSONB, err := os.ReadFile(pkgJSONPath)
if err != nil {
return fmt.Errorf("failed to read %q: %w", pkgJSONPath, err)
}
var pkgObj PackageJSON
if err := json.Unmarshal(pkgJSONB, &pkgObj); err != nil {
return fmt.Errorf("failed decoding %q: %w", pkgJSONPath, err)
}
whatsNewSplit := whatsNewRegex.FindStringSubmatch(pkgObj.Grafana.WhatsNewUrl)
whatsNewVersion := whatsNewSplit[1]
if whatsNewVersion != majorMinorDigits {
return fmt.Errorf("whatsNewUrl in package.json needs to be updated to %s/", strings.Replace(whatsNewSplit[0], whatsNewVersion, majorMinorDigits, 1))
}
return nil
}

View File

@@ -0,0 +1,83 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
const (
DroneBuildEvent = "DRONE_BUILD_EVENT"
DroneTag = "DRONE_TAG"
DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE"
)
const whatsNewUrl = "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-"
func TestWhatsNewChecker(t *testing.T) {
tests := []struct {
envMap map[string]string
packageJsonVersion string
name string
wantErr bool
errMsg string
}{
{envMap: map[string]string{DroneBuildEvent: config.PullRequest}, packageJsonVersion: "", name: "non-tag event", wantErr: true, errMsg: "non-tag pipeline, exiting"},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "abcd123"}, packageJsonVersion: "", name: "non-semver compatible", wantErr: true, errMsg: "non-semver compatible version vabcd123, exiting"},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v0.0.0", DroneSemverPrerelease: "test"}, packageJsonVersion: "v10-0", name: "skip check for test tags", wantErr: false},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v10.0.0"}, packageJsonVersion: "v10-0", name: "package.json version matches tag", wantErr: false},
{envMap: map[string]string{DroneBuildEvent: config.Tag, DroneTag: "v10.0.0"}, packageJsonVersion: "v9-5", name: "package.json doesn't match tag", wantErr: true, errMsg: "whatsNewUrl in package.json needs to be updated to https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v10-0/"},
}
for _, tt := range tests {
app := cli.NewApp()
app.Version = "1.0.0"
context := cli.NewContext(app, &flag.FlagSet{}, nil)
t.Run(tt.name, func(t *testing.T) {
setUpEnv(t, tt.envMap)
err := createTempPackageJson(t, tt.packageJsonVersion)
require.NoError(t, err)
err = WhatsNewChecker(context)
if tt.wantErr {
require.Error(t, err)
require.Equal(t, tt.errMsg, err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func setUpEnv(t *testing.T, envMap map[string]string) {
t.Helper()
os.Clearenv()
t.Setenv("DRONE_BUILD_NUMBER", "12345")
t.Setenv("DRONE_COMMIT", "abcd12345")
for k, v := range envMap {
t.Setenv(k, v)
}
}
func createTempPackageJson(t *testing.T, version string) error {
t.Helper()
grafanaData := Grafana{WhatsNewUrl: fmt.Sprintf("%s%s/", whatsNewUrl, version)}
data := PackageJSON{Grafana: grafanaData, Version: "1.2.3"}
file, _ := json.MarshalIndent(data, "", " ")
err := os.WriteFile("package.json", file, 0644)
require.NoError(t, err)
t.Cleanup(func() {
err := os.RemoveAll("package.json")
require.NoError(t, err)
})
return nil
}

View File

@@ -64,6 +64,10 @@ load(
"scripts/drone/utils/images.star", "scripts/drone/utils/images.star",
"images", "images",
) )
load(
"scripts/drone/pipelines/whats_new_checker.star",
"whats_new_checker_pipeline",
)
ver_mode = "release" ver_mode = "release"
release_trigger = { release_trigger = {
@@ -210,9 +214,12 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger):
memcached_integration_tests_step(), memcached_integration_tests_step(),
] ]
pipelines = []
# We don't need to run integration tests at release time since they have # We don't need to run integration tests at release time since they have
# been run multiple times before: # been run multiple times before:
if ver_mode in ("release"): if ver_mode in ("release"):
pipelines.append(whats_new_checker_pipeline(release_trigger))
integration_test_steps = [] integration_test_steps = []
volumes = [] volumes = []
@@ -220,7 +227,7 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger):
"{}-oss-build-e2e-publish".format(ver_mode), "{}-oss-build-e2e-publish".format(ver_mode),
"{}-oss-test-frontend".format(ver_mode), "{}-oss-test-frontend".format(ver_mode),
] ]
pipelines = [ pipelines.extend([
pipeline( pipeline(
name = "{}-oss-build-e2e-publish".format(ver_mode), name = "{}-oss-build-e2e-publish".format(ver_mode),
edition = "oss", edition = "oss",
@@ -232,7 +239,7 @@ def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger):
), ),
test_frontend(trigger, ver_mode), test_frontend(trigger, ver_mode),
test_backend(trigger, ver_mode), test_backend(trigger, ver_mode),
] ])
if ver_mode not in ("release"): if ver_mode not in ("release"):
pipelines.append(pipeline( pipelines.append(pipeline(

View File

@@ -0,0 +1,43 @@
"""
This module contains logic for checking if the package.json whats new url matches with the in-flight tag.
"""
load(
"scripts/drone/utils/images.star",
"images",
)
load(
"scripts/drone/steps/lib.star",
"compile_build_cmd",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def whats_new_checker_step():
return {
"name": "whats-new-checker",
"image": images["go_image"],
"depends_on": [
"compile-build-cmd",
],
"commands": [
"./bin/build whatsnew-checker",
],
}
def whats_new_checker_pipeline(trigger):
environment = {"EDITION": "oss"}
steps = [
compile_build_cmd(),
whats_new_checker_step(),
]
return pipeline(
name = "release-whatsnew-checker",
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
environment = environment,
)