CI: Backport CI/Release related code to v9.3.x (#62752)

* Batch-move everything

* go mod tidy

* make drone

* Remove genversions

* Bump alpine image

* Revert back pkg/build/docker/build.go

* Make sure correct enterprise branch is checked out

* Add enterprise2 version

* Remove extensions

* Bump build container

* backport node 18 test fix

(cherry picked from commit 4ff03fdbfb)

* Update scripts/drone

* Add more commands

* Fix starlark link

* Copy .drone.star

* Add drone target branch for custom events

---------
This commit is contained in:
Dimitris Sotirakis
2023-02-03 11:43:48 +02:00
committed by GitHub
parent d9b5c5f4c6
commit 03b1cf763d
85 changed files with 8622 additions and 2695 deletions

View File

@@ -3,32 +3,55 @@
# 2. Login to drone and export the env variables (token and server) shown here: https://drone.grafana.net/account
# 3. Run `make drone`
# More information about this process here: https://github.com/grafana/deployment_tools/blob/master/docs/infrastructure/drone/signing.md
"""
This module returns a Drone configuration including pipelines and secrets.
"""
load('scripts/drone/events/pr.star', 'pr_pipelines')
load('scripts/drone/events/main.star', 'main_pipelines')
load('scripts/drone/pipelines/docs.star', 'docs_pipelines')
load('scripts/drone/events/release.star', 'release_pipelines', 'publish_artifacts_pipelines', 'publish_npm_pipelines', 'publish_packages_pipeline', 'artifacts_page_pipeline')
load('scripts/drone/pipelines/publish_images.star', 'publish_image_pipelines_public', 'publish_image_pipelines_security')
load('scripts/drone/pipelines/github.star', 'publish_github_pipeline')
load('scripts/drone/version.star', 'version_branch_pipelines')
load('scripts/drone/events/cron.star', 'cronjobs')
load('scripts/drone/vault.star', 'secrets')
load("scripts/drone/events/pr.star", "pr_pipelines")
load("scripts/drone/events/main.star", "main_pipelines")
load(
"scripts/drone/events/release.star",
"artifacts_page_pipeline",
"enterprise2_pipelines",
"enterprise_pipelines",
"oss_pipelines",
"publish_artifacts_pipelines",
"publish_npm_pipelines",
"publish_packages_pipeline",
)
load(
"scripts/drone/pipelines/publish_images.star",
"publish_image_pipelines_public",
"publish_image_pipelines_security",
)
load("scripts/drone/pipelines/github.star", "publish_github_pipeline")
load("scripts/drone/pipelines/aws_marketplace.star", "publish_aws_marketplace_pipeline")
load("scripts/drone/version.star", "version_branch_pipelines")
load("scripts/drone/events/cron.star", "cronjobs")
load("scripts/drone/vault.star", "secrets")
edition = 'oss'
def main(ctx):
def main(_ctx):
return (
pr_pipelines(edition=edition)
+ main_pipelines(edition=edition)
+ release_pipelines()
+ publish_image_pipelines_public()
+ publish_image_pipelines_security()
+ publish_artifacts_pipelines('security')
+ publish_artifacts_pipelines('public')
+ publish_npm_pipelines('public')
+ publish_packages_pipeline()
+ artifacts_page_pipeline()
+ version_branch_pipelines()
+ cronjobs(edition=edition)
+ secrets()
pr_pipelines() +
main_pipelines() +
oss_pipelines() +
enterprise_pipelines() +
enterprise2_pipelines() +
enterprise2_pipelines(
prefix = "custom-",
trigger = {"event": ["custom"]},
) +
publish_image_pipelines_public() +
publish_image_pipelines_security() +
publish_github_pipeline("public") +
publish_github_pipeline("security") +
publish_aws_marketplace_pipeline("public") +
publish_artifacts_pipelines("security") +
publish_artifacts_pipelines("public") +
publish_npm_pipelines() +
publish_packages_pipeline() +
artifacts_page_pipeline() +
version_branch_pipelines() +
cronjobs() +
secrets()
)

1907
.drone.yml

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ COPY emails emails
ENV NODE_ENV production
RUN yarn build
FROM golang:1.19.4-alpine3.15 as go-builder
FROM golang:1.19.4-alpine3.17 as go-builder
RUN apk add --no-cache gcc g++ make

7
go.mod
View File

@@ -244,11 +244,13 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0
github.com/Azure/azure-storage-blob-go v0.15.0
github.com/Azure/go-autorest/autorest/adal v0.9.20
github.com/Masterminds/semver/v3 v3.1.1
github.com/armon/go-radix v1.0.0
github.com/blugelabs/bluge v0.1.9
github.com/blugelabs/bluge_segment_api v0.2.0
github.com/bufbuild/connect-go v1.0.0
github.com/dlmiddlecote/sqlstats v1.0.2
github.com/docker/docker v20.10.16+incompatible
github.com/drone/drone-cli v1.6.1
github.com/getkin/kin-openapi v0.103.0
github.com/golang-migrate/migrate/v4 v4.7.0
@@ -273,8 +275,11 @@ require (
github.com/armon/go-metrics v0.3.10 // indirect
github.com/bmatcuk/doublestar v1.1.1 // indirect
github.com/buildkite/yaml v2.1.0+incompatible // indirect
github.com/containerd/containerd v1.6.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/drone-runners/drone-runner-docker v1.8.2 // indirect
github.com/drone/drone-go v1.7.1 // indirect
github.com/drone/envsubst v1.0.3 // indirect
@@ -292,6 +297,8 @@ require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-ieproxy v0.0.3 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.1.4 // indirect

6
go.sum
View File

@@ -144,6 +144,7 @@ github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9B
github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@@ -237,6 +238,7 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM=
@@ -575,6 +577,7 @@ github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoT
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw=
github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs=
github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@@ -747,6 +750,7 @@ github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@@ -1920,6 +1924,7 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3P
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE=
@@ -2008,6 +2013,7 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=

View File

@@ -2,7 +2,22 @@ package main
import "github.com/urfave/cli/v2"
func ArgCountWrapper(max int, action cli.ActionFunc) cli.ActionFunc {
// ArgCountWrapper will cause the action to fail if there were not exactly `num` args provided.
func ArgCountWrapper(num int, action cli.ActionFunc) cli.ActionFunc {
return func(ctx *cli.Context) error {
if ctx.NArg() != num {
if err := cli.ShowSubcommandHelp(ctx); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
return action(ctx)
}
}
// ArgCountWrapper will cause the action to fail if there were more than `num` args provided.
func MaxArgCountWrapper(max int, action cli.ActionFunc) cli.ActionFunc {
return func(ctx *cli.Context) error {
if ctx.NArg() > max {
if err := cli.ShowSubcommandHelp(ctx); err != nil {

View File

@@ -13,7 +13,7 @@ import (
)
func BuildBackend(ctx *cli.Context) error {
metadata, err := GenerateMetadata(ctx)
metadata, err := config.GenerateMetadata(ctx)
if err != nil {
return err
}

View File

@@ -14,7 +14,7 @@ func BuildDocker(c *cli.Context) error {
return err
}
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -3,6 +3,7 @@ package main
import (
"log"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/errutil"
"github.com/grafana/grafana/pkg/build/frontend"
"github.com/grafana/grafana/pkg/build/syncutil"
@@ -10,7 +11,7 @@ import (
)
func BuildFrontend(c *cli.Context) error {
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -17,7 +17,7 @@ func BuildInternalPlugins(c *cli.Context) error {
}
const grafanaDir = "."
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -0,0 +1,132 @@
package main
import (
"fmt"
"log"
"os"
"strconv"
"github.com/grafana/grafana/pkg/build/env"
"github.com/grafana/grafana/pkg/build/git"
"github.com/urfave/cli/v2"
)
// checkOpts are options used to create a new GitHub check for the enterprise downstream test.
type checkOpts struct {
SHA string
URL string
Branch string
PR int
}
func getCheckOpts(args []string) (*checkOpts, error) {
branch, ok := env.Lookup("DRONE_SOURCE_BRANCH", args)
if !ok {
return nil, cli.Exit("Unable to retrieve build source branch", 1)
}
var (
rgx = git.PRCheckRegexp()
matches = rgx.FindStringSubmatch(branch)
)
sha, ok := env.Lookup("SOURCE_COMMIT", args)
if !ok {
if matches == nil || len(matches) <= 1 {
return nil, cli.Exit("Unable to retrieve source commit", 1)
}
sha = matches[2]
}
url, ok := env.Lookup("DRONE_BUILD_LINK", args)
if !ok {
return nil, cli.Exit(`missing environment variable "DRONE_BUILD_LINK"`, 1)
}
prStr, ok := env.Lookup("OSS_PULL_REQUEST", args)
if !ok {
if matches == nil || len(matches) <= 1 {
return nil, cli.Exit("Unable to retrieve PR number", 1)
}
prStr = matches[1]
}
pr, err := strconv.Atoi(prStr)
if err != nil {
return nil, err
}
return &checkOpts{
Branch: branch,
PR: pr,
SHA: sha,
URL: url,
}, nil
}
// EnterpriseCheckBegin creates the GitHub check and signals the beginning of the downstream build / test process
func EnterpriseCheckBegin(c *cli.Context) error {
var (
ctx = c.Context
client = git.NewGitHubClient(ctx, c.String("github-token"))
)
opts, err := getCheckOpts(os.Environ())
if err != nil {
return err
}
if _, err = git.CreateEnterpriseStatus(ctx, client.Repositories, opts.SHA, opts.URL, "pending"); err != nil {
return err
}
return nil
}
func EnterpriseCheckSuccess(c *cli.Context) error {
return completeEnterpriseCheck(c, true)
}
func EnterpriseCheckFail(c *cli.Context) error {
return completeEnterpriseCheck(c, false)
}
func completeEnterpriseCheck(c *cli.Context, success bool) error {
var (
ctx = c.Context
client = git.NewGitHubClient(ctx, c.String("github-token"))
)
// Update the pull request labels
opts, err := getCheckOpts(os.Environ())
if err != nil {
return err
}
status := "failure"
if success {
status = "success"
}
// Update the GitHub check...
if _, err := git.CreateEnterpriseStatus(ctx, client.Repositories, opts.SHA, opts.URL, status); err != nil {
return err
}
// Delete branch if needed
log.Printf("Checking branch '%s' against '%s'", git.PRCheckRegexp().String(), opts.Branch)
if git.PRCheckRegexp().MatchString(opts.Branch) {
log.Println("Deleting branch", opts.Branch)
if err := git.DeleteEnterpriseBranch(ctx, client.Git, opts.Branch); err != nil {
return fmt.Errorf("error deleting enterprise branch: %w", err)
}
}
label := "enterprise-failed"
if success {
label = "enterprise-ok"
}
return git.AddLabelToPR(ctx, client.Issues, opts.PR, label)
}

View File

@@ -0,0 +1,69 @@
package main
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetCheckOpts(t *testing.T) {
t.Run("it should return the checkOpts if the correct environment variables are set", func(t *testing.T) {
args := []string{
"SOURCE_COMMIT=1234",
"DRONE_SOURCE_BRANCH=test",
"DRONE_BUILD_LINK=http://example.com",
"OSS_PULL_REQUEST=1",
}
opts, err := getCheckOpts(args)
require.NoError(t, err)
require.Equal(t, opts.SHA, "1234")
require.Equal(t, opts.URL, "http://example.com")
})
t.Run("it should return an error if SOURCE_COMMIT is not set", func(t *testing.T) {
args := []string{
"DRONE_BUILD_LINK=http://example.com",
"DRONE_SOURCE_BRANCH=test",
"DRONE_BUILD_LINK=http://example.com",
"OSS_PULL_REQUEST=1",
}
opts, err := getCheckOpts(args)
require.Nil(t, opts)
require.Error(t, err)
})
t.Run("it should return an error if DRONE_BUILD_LINK is not set", func(t *testing.T) {
args := []string{
"SOURCE_COMMIT=1234",
"DRONE_SOURCE_BRANCH=test",
"OSS_PULL_REQUEST=1",
}
opts, err := getCheckOpts(args)
require.Nil(t, opts)
require.Error(t, err)
})
t.Run("it should return an error if OSS_PULL_REQUEST is not set", func(t *testing.T) {
args := []string{
"SOURCE_COMMIT=1234",
"DRONE_SOURCE_BRANCH=test",
"DRONE_BUILD_LINK=http://example.com",
}
opts, err := getCheckOpts(args)
require.Nil(t, opts)
require.Error(t, err)
})
t.Run("it should return an error if OSS_PULL_REQUEST is not an integer", func(t *testing.T) {
args := []string{
"SOURCE_COMMIT=1234",
"DRONE_SOURCE_BRANCH=test",
"DRONE_BUILD_LINK=http://example.com",
"OSS_PULL_REQUEST=http://example.com",
}
opts, err := getCheckOpts(args)
require.Nil(t, opts)
require.Error(t, err)
})
}

View File

@@ -4,11 +4,12 @@ import (
"os"
"path/filepath"
"github.com/grafana/grafana/pkg/build/config"
"github.com/urfave/cli/v2"
)
func ExportVersion(c *cli.Context) error {
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -20,12 +20,12 @@ const (
func FetchImages(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.NewExitError(err.Error(), 1)
return cli.Exit(err.Error(), 1)
}
return cli.NewExitError("", 1)
return cli.Exit("", 1)
}
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -46,4 +46,14 @@ var (
Usage: "Google Cloud Platform key file",
Required: true,
}
gitHubTokenFlag = cli.StringFlag{
Name: "github-token",
Value: "",
EnvVars: []string{"GITHUB_TOKEN"},
Usage: "GitHub token",
}
tagFlag = cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
}
)

View File

@@ -1,92 +0,0 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/droneutil"
"github.com/urfave/cli/v2"
)
func GenerateMetadata(c *cli.Context) (config.Metadata, error) {
var metadata config.Metadata
version := ""
event, err := droneutil.GetDroneEventFromEnv()
if err != nil {
return config.Metadata{}, err
}
var releaseMode config.ReleaseMode
switch event {
case string(config.PullRequestMode):
releaseMode = config.ReleaseMode{Mode: config.PullRequestMode}
case config.Push:
mode, err := config.CheckDroneTargetBranch()
if err != nil {
return config.Metadata{}, err
}
releaseMode = config.ReleaseMode{Mode: mode}
case config.Custom:
mode, err := config.CheckDroneTargetBranch()
if err != nil {
return config.Metadata{}, err
}
// if there is a custom event targeting the main branch, that's an enterprise downstream build
if mode == config.MainBranch {
releaseMode = config.ReleaseMode{Mode: config.CustomMode}
} else {
releaseMode = config.ReleaseMode{Mode: mode}
}
case config.Tag, config.Promote:
tag, ok := os.LookupEnv("DRONE_TAG")
if !ok || tag == "" {
return config.Metadata{}, err
}
version = strings.TrimPrefix(tag, "v")
mode, err := config.CheckSemverSuffix()
if err != nil {
return config.Metadata{}, err
}
releaseMode = mode
case config.Cronjob:
releaseMode = config.ReleaseMode{Mode: config.CronjobMode}
}
if version == "" {
version, err = generateVersionFromBuildID()
if err != nil {
return config.Metadata{}, err
}
}
currentCommit, err := config.GetDroneCommit()
if err != nil {
return config.Metadata{}, err
}
metadata = config.Metadata{
GrafanaVersion: version,
ReleaseMode: releaseMode,
GrabplVersion: c.App.Version,
CurrentCommit: currentCommit,
}
fmt.Printf("building Grafana version: %s, release mode: %+v", metadata.GrafanaVersion, metadata.ReleaseMode)
return metadata, nil
}
func generateVersionFromBuildID() (string, error) {
buildID, ok := os.LookupEnv("DRONE_BUILD_NUMBER")
if !ok {
return "", fmt.Errorf("unable to get DRONE_BUILD_NUMBER environmental variable")
}
var err error
version, err := config.GetGrafanaVersion(buildID, ".")
if err != nil {
return "", err
}
return version, nil
}

View File

@@ -1,81 +0,0 @@
package main
import (
"flag"
"os"
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
const (
DroneBuildEvent = "DRONE_BUILD_EVENT"
DroneTargetBranch = "DRONE_TARGET_BRANCH"
DroneTag = "DRONE_TAG"
DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE"
DroneBuildNumber = "DRONE_BUILD_NUMBER"
)
const (
hashedGrafanaVersion = "9.2.0-12345pre"
versionedBranch = "v9.2.x"
)
func TestGetMetadata(t *testing.T) {
tcs := []struct {
envMap map[string]string
expVersion string
mode config.ReleaseMode
}{
{map[string]string{DroneBuildEvent: config.PullRequest, DroneTargetBranch: "", DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, config.ReleaseMode{Mode: config.PullRequestMode}},
{map[string]string{DroneBuildEvent: config.Push, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, config.ReleaseMode{Mode: config.ReleaseBranchMode}},
{map[string]string{DroneBuildEvent: config.Push, DroneTargetBranch: config.MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, config.ReleaseMode{Mode: config.MainMode}},
{map[string]string{DroneBuildEvent: config.Custom, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, config.ReleaseMode{Mode: config.ReleaseBranchMode}},
{map[string]string{DroneBuildEvent: config.Custom, DroneTargetBranch: config.MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, config.ReleaseMode{Mode: config.Custom}},
{map[string]string{DroneBuildEvent: config.Tag, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", config.ReleaseMode{Mode: config.TagMode, IsBeta: false, IsTest: false}},
{map[string]string{DroneBuildEvent: config.Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-beta", DroneSemverPrerelease: "beta", DroneBuildNumber: "12345"}, "9.2.0-beta", config.ReleaseMode{Mode: config.TagMode, IsBeta: true, IsTest: false}},
{map[string]string{DroneBuildEvent: config.Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", config.ReleaseMode{Mode: config.TagMode, IsBeta: false, IsTest: true}},
{map[string]string{DroneBuildEvent: config.Promote, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", config.ReleaseMode{Mode: config.TagMode, IsBeta: false, IsTest: false}},
{map[string]string{DroneBuildEvent: config.Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-beta", DroneSemverPrerelease: "beta", DroneBuildNumber: "12345"}, "9.2.0-beta", config.ReleaseMode{Mode: config.TagMode, IsBeta: true, IsTest: false}},
{map[string]string{DroneBuildEvent: config.Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", config.ReleaseMode{Mode: config.TagMode, IsBeta: false, IsTest: true}},
}
ctx := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)
for _, tc := range tcs {
t.Run("Should return valid metadata, ", func(t *testing.T) {
setUpEnv(t, tc.envMap)
testMetadata(t, ctx, tc.expVersion, tc.mode)
})
}
}
func testMetadata(t *testing.T, ctx *cli.Context, version string, releaseMode config.ReleaseMode) {
t.Helper()
metadata, err := GenerateMetadata(ctx)
require.NoError(t, err)
t.Run("with a valid version", func(t *testing.T) {
expVersion := metadata.GrafanaVersion
require.Equal(t, expVersion, version)
})
t.Run("with a valid release mode from the built-in list", func(t *testing.T) {
expMode := metadata.ReleaseMode
require.NoError(t, err)
require.Equal(t, expMode, releaseMode)
})
}
func setUpEnv(t *testing.T, envMap map[string]string) {
t.Helper()
os.Clearenv()
err := os.Setenv("DRONE_COMMIT", "abcd12345")
require.NoError(t, err)
for k, v := range envMap {
err := os.Setenv(k, v)
require.NoError(t, err)
}
}

View File

@@ -15,11 +15,12 @@ import (
"strings"
"time"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/grafana/grafana/pkg/build/packaging"
"github.com/urfave/cli/v2"
)
const grafanaAPI = "https://grafana.com/api"
@@ -33,7 +34,7 @@ func GrafanaCom(c *cli.Context) error {
return fmt.Errorf("couldn't activate service account, err: %w", err)
}
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
@@ -66,11 +67,11 @@ func GrafanaCom(c *cli.Context) error {
grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY"))
if grafanaAPIKey == "" {
return cli.NewExitError("the environment variable GRAFANA_COM_API_KEY must be set", 1)
return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1)
}
whatsNewURL, releaseNotesURL, err := getReleaseURLs()
if err != nil {
return cli.NewExitError(err.Error(), 1)
return cli.Exit(err.Error(), 1)
}
// TODO: Verify config values
@@ -89,7 +90,7 @@ func GrafanaCom(c *cli.Context) error {
}
if err := publishPackages(cfg); err != nil {
return cli.NewExitError(err.Error(), 1)
return cli.Exit(err.Error(), 1)
}
log.Println("Successfully published packages to grafana.com!")
@@ -146,7 +147,7 @@ func publishPackages(cfg packaging.PublishConfig) error {
}
switch cfg.ReleaseMode.Mode {
case config.MainMode, config.CustomMode, config.CronjobMode:
case config.MainMode, config.DownstreamMode, config.CronjobMode:
pth = path.Join(pth, packaging.MainFolder)
default:
pth = path.Join(pth, packaging.ReleaseFolder)
@@ -177,7 +178,7 @@ func publishPackages(cfg packaging.PublishConfig) error {
Version: cfg.Version,
ReleaseDate: time.Now().UTC(),
Builds: builds,
Stable: cfg.ReleaseMode.Mode == config.TagMode,
Stable: cfg.ReleaseMode.Mode == config.TagMode && !cfg.ReleaseMode.IsBeta && !cfg.ReleaseMode.IsTest,
Beta: cfg.ReleaseMode.IsBeta,
Nightly: cfg.ReleaseMode.Mode == config.CronjobMode,
}

View File

@@ -5,10 +5,18 @@ import (
"os"
"strings"
"github.com/grafana/grafana/pkg/build/docker"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/docker"
)
var additionalCommands []*cli.Command = make([]*cli.Command, 0, 5)
//nolint:unused
func registerAppCommand(c *cli.Command) {
additionalCommands = append(additionalCommands, c)
}
func main() {
app := cli.NewApp()
app.Commands = cli.Commands{
@@ -16,7 +24,7 @@ func main() {
Name: "build-backend",
Usage: "Build one or more variants of back-end binaries",
ArgsUsage: "[version]",
Action: ArgCountWrapper(1, BuildBackend),
Action: MaxArgCountWrapper(1, BuildBackend),
Flags: []cli.Flag{
&jobsFlag,
&variantsFlag,
@@ -67,7 +75,7 @@ func main() {
Name: "build-frontend",
Usage: "Build front-end artifacts",
ArgsUsage: "[version]",
Action: ArgCountWrapper(1, BuildFrontend),
Action: MaxArgCountWrapper(1, BuildFrontend),
Flags: []cli.Flag{
&jobsFlag,
&editionFlag,
@@ -77,7 +85,7 @@ func main() {
{
Name: "build-docker",
Usage: "Build Grafana Docker images",
Action: ArgCountWrapper(1, BuildDocker),
Action: MaxArgCountWrapper(1, BuildDocker),
Flags: []cli.Flag{
&jobsFlag,
&editionFlag,
@@ -96,6 +104,14 @@ func main() {
},
},
},
{
Name: "upload-cdn",
Usage: "Upload public/* to a cdn bucket",
Action: UploadCDN,
Flags: []cli.Flag{
&editionFlag,
},
},
{
Name: "shellcheck",
Usage: "Run shellcheck on shell scripts",
@@ -104,7 +120,7 @@ func main() {
{
Name: "build-plugins",
Usage: "Build internal plug-ins",
Action: ArgCountWrapper(1, BuildInternalPlugins),
Action: MaxArgCountWrapper(1, BuildInternalPlugins),
Flags: []cli.Flag{
&jobsFlag,
&editionFlag,
@@ -117,13 +133,19 @@ func main() {
Name: "publish-metrics",
Usage: "Publish a set of metrics from stdin",
ArgsUsage: "<api-key>",
Action: ArgCountWrapper(1, PublishMetrics),
Action: MaxArgCountWrapper(1, PublishMetrics),
},
{
Name: "verify-drone",
Usage: "Verify Drone configuration",
Action: VerifyDrone,
},
{
Name: "verify-starlark",
Usage: "Verify Starlark configuration",
ArgsUsage: "<workspace path>",
Action: VerifyStarlark,
},
{
Name: "export-version",
Usage: "Exports version in dist/grafana.version",
@@ -133,7 +155,7 @@ func main() {
Name: "package",
Usage: "Package one or more Grafana variants",
ArgsUsage: "[version]",
Action: ArgCountWrapper(1, Package),
Action: MaxArgCountWrapper(1, Package),
Flags: []cli.Flag{
&jobsFlag,
&variantsFlag,
@@ -144,7 +166,7 @@ func main() {
},
{
Name: "store-storybook",
Usage: "Integrity check for storybook build",
Usage: "Stores storybook to GCS buckets",
Action: StoreStorybook,
Flags: []cli.Flag{
&cli.StringFlag{
@@ -153,10 +175,81 @@ func main() {
},
},
},
{
Name: "verify-storybook",
Usage: "Integrity check for storybook build",
Action: VerifyStorybook,
},
{
Name: "upload-packages",
Usage: "Upload Grafana packages",
Action: UploadPackages,
Flags: []cli.Flag{
&jobsFlag,
&editionFlag,
&cli.BoolFlag{
Name: "enterprise2",
Usage: "Declare if the edition is enterprise2",
},
},
},
{
Name: "artifacts",
Usage: "Handle Grafana artifacts",
Subcommands: cli.Commands{
{
Name: "publish",
Usage: "Publish Grafana artifacts",
Action: PublishArtifactsAction,
Flags: []cli.Flag{
&editionFlag,
&cli.BoolFlag{
Name: "security",
Usage: "Security release",
},
&cli.StringFlag{
Name: "security-dest-bucket",
Usage: "Google Cloud Storage bucket for security packages (or $SECURITY_DEST_BUCKET)",
},
&cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
},
&cli.StringFlag{
Name: "src-bucket",
Value: "grafana-prerelease",
Usage: "Google Cloud Storage bucket",
},
&cli.StringFlag{
Name: "dest-bucket",
Value: "grafana-downloads",
Usage: "Google Cloud Storage bucket for published packages",
},
&cli.StringFlag{
Name: "enterprise2-dest-bucket",
Value: "grafana-downloads-enterprise2",
Usage: "Google Cloud Storage bucket for published packages",
},
&cli.StringFlag{
Name: "enterprise2-security-prefix",
Usage: "Bucket path prefix for enterprise2 security releases (or $ENTERPRISE2_SECURITY_PREFIX)",
},
&cli.StringFlag{
Name: "static-assets-bucket",
Value: "grafana-static-assets",
Usage: "Google Cloud Storage bucket for static assets",
},
&cli.StringSliceFlag{
Name: "static-asset-editions",
Usage: "All the editions of the static assets (or $STATIC_ASSET_EDITIONS)",
},
&cli.StringFlag{
Name: "storybook-bucket",
Value: "grafana-storybook",
Usage: "Google Cloud Storage bucket for storybooks",
},
},
},
{
Name: "docker",
Usage: "Handle Grafana Docker images",
@@ -165,11 +258,54 @@ func main() {
Name: "fetch",
Usage: "Fetch Grafana Docker images",
ArgsUsage: "[version]",
Action: ArgCountWrapper(1, FetchImages),
Action: MaxArgCountWrapper(1, FetchImages),
Flags: []cli.Flag{
&editionFlag,
},
},
{
Name: "publish-enterprise2",
Usage: "Handle Grafana Enterprise2 Docker images",
ArgsUsage: "[version]",
Action: Enterprise2,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "dockerhub-repo",
Usage: "DockerHub repo to push images",
},
},
},
},
},
{
Name: "npm",
Usage: "Handle Grafana npm packages",
Subcommands: cli.Commands{
{
Name: "release",
Usage: "Release npm packages",
ArgsUsage: "[version]",
Action: NpmReleaseAction,
Flags: []cli.Flag{
&tagFlag,
},
},
{
Name: "store",
Usage: "Store npm packages tarball",
Action: NpmStoreAction,
Flags: []cli.Flag{
&tagFlag,
},
},
{
Name: "retrieve",
Usage: "Retrieve npm packages tarball",
Action: NpmRetrieveAction,
Flags: []cli.Flag{
&tagFlag,
},
},
},
},
},
@@ -196,7 +332,7 @@ func main() {
{
Name: "github",
Usage: "Publish packages to GitHub releases",
Action: PublishGitHub,
Action: PublishGithub,
Flags: []cli.Flag{
&dryRunFlag,
&cli.StringFlag{
@@ -210,7 +346,7 @@ func main() {
},
&cli.StringFlag{
Name: "tag",
Usage: "Release tag (default from metadata)ß",
Usage: "Release tag (default from metadata)",
},
&cli.BoolFlag{
Name: "create",
@@ -218,10 +354,69 @@ func main() {
},
},
},
{
Name: "aws",
Usage: "Publish image to AWS Marketplace releases",
Action: PublishAwsMarketplace,
Flags: []cli.Flag{
&dryRunFlag,
&cli.StringFlag{
Name: "version",
Usage: "Release version (default from metadata)",
},
&cli.StringFlag{
Name: "image",
Required: true,
Usage: "Name of the image to be released",
},
&cli.StringFlag{
Name: "repo",
Required: true,
Usage: "AWS Marketplace ECR repository",
},
&cli.StringFlag{
Name: "product",
Required: true,
Usage: "AWS Marketplace product identifier",
},
},
},
},
},
{
Name: "enterprise-check",
Usage: "Commands for testing against Grafana Enterprise",
Subcommands: cli.Commands{
{
Name: "begin",
Usage: "Creates the GitHub check in a pull request and begins the tests",
Action: EnterpriseCheckBegin,
Flags: []cli.Flag{
&gitHubTokenFlag,
},
},
{
Name: "success",
Usage: "Updates the GitHub check in a pull request to show a successful build and updates the pull request labels",
Action: EnterpriseCheckSuccess,
Flags: []cli.Flag{
&gitHubTokenFlag,
},
},
{
Name: "fail",
Usage: "Updates the GitHub check in a pull request to show a failed build and updates the pull request labels",
Action: EnterpriseCheckFail,
Flags: []cli.Flag{
&gitHubTokenFlag,
},
},
},
},
}
app.Commands = append(app.Commands, additionalCommands...)
if err := app.Run(os.Args); err != nil {
log.Fatalln(err)
}

88
pkg/build/cmd/npm.go Normal file
View File

@@ -0,0 +1,88 @@
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/grafana/grafana/pkg/build/npm"
"github.com/urfave/cli/v2"
)
func NpmRetrieveAction(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
tag := c.String("tag")
if tag == "" {
return fmt.Errorf("no tag version specified, exitting")
}
prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET"))
if prereleaseBucket == "" {
return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1)
}
err := npm.FetchNpmPackages(c.Context, tag, prereleaseBucket)
if err != nil {
return err
}
return nil
}
func NpmStoreAction(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
tag := c.String("tag")
if tag == "" {
return fmt.Errorf("no tag version specified, exiting")
}
prereleaseBucket := strings.TrimSpace(os.Getenv("PRERELEASE_BUCKET"))
if prereleaseBucket == "" {
return cli.Exit("the environment variable PRERELEASE_BUCKET must be set", 1)
}
err := npm.StoreNpmPackages(c.Context, tag, prereleaseBucket)
if err != nil {
return err
}
return nil
}
func NpmReleaseAction(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
tag := c.String("tag")
if tag == "" {
return fmt.Errorf("no tag version specified, exitting")
}
cmd := exec.Command("git", "checkout", ".")
if err := cmd.Run(); err != nil {
fmt.Println("command failed to run, err: ", err)
return err
}
err := npm.PublishNpmPackages(c.Context, tag)
if err != nil {
return err
}
return nil
}

View File

@@ -13,7 +13,7 @@ import (
)
func Package(c *cli.Context) error {
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
@@ -22,12 +22,12 @@ func Package(c *cli.Context) error {
releaseMode, err := metadata.GetReleaseMode()
if err != nil {
return cli.NewExitError(err.Error(), 1)
return cli.Exit(err.Error(), 1)
}
releaseModeConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil {
return cli.NewExitError(err.Error(), 1)
return cli.Exit(err.Error(), 1)
}
cfg := config.Config{

View File

@@ -0,0 +1,211 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/versions"
"github.com/urfave/cli/v2"
)
type publishConfig struct {
tag string
srcBucket string
destBucket string
enterprise2DestBucket string
enterprise2SecurityPrefix string
staticAssetsBucket string
staticAssetEditions []string
storybookBucket string
security bool
}
// requireListWithEnvFallback first checks the CLI for a flag with the required
// name. If this is empty, it falls back to taking the environment variable.
// Sadly, we cannot use cli.Flag.EnvVars for this due to it potentially leaking
// environment variables as default values in usage-errors.
func requireListWithEnvFallback(cctx *cli.Context, name string, envName string) ([]string, error) {
result := cctx.StringSlice(name)
if len(result) == 0 {
for _, v := range strings.Split(os.Getenv(envName), ",") {
value := strings.TrimSpace(v)
if value != "" {
result = append(result, value)
}
}
}
if len(result) == 0 {
return nil, cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
}
return result, nil
}
func requireStringWithEnvFallback(cctx *cli.Context, name string, envName string) (string, error) {
result := cctx.String(name)
if result == "" {
result = os.Getenv(envName)
}
if result == "" {
return "", cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
}
return result, nil
}
// Action implements the sub-command "publish-artifacts".
func PublishArtifactsAction(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
staticAssetEditions, err := requireListWithEnvFallback(c, "static-asset-editions", "STATIC_ASSET_EDITIONS")
if err != nil {
return err
}
securityDestBucket, err := requireStringWithEnvFallback(c, "security-dest-bucket", "SECURITY_DEST_BUCKET")
if err != nil {
return err
}
enterprise2SecurityPrefix, err := requireStringWithEnvFallback(c, "enterprise2-security-prefix", "ENTERPRISE2_SECURITY_PREFIX")
if err != nil {
return err
}
if err := gcloud.ActivateServiceAccount(); err != nil {
return fmt.Errorf("error connecting to gcp, %q", err)
}
cfg := publishConfig{
srcBucket: c.String("src-bucket"),
destBucket: c.String("dest-bucket"),
enterprise2DestBucket: c.String("enterprise2-dest-bucket"),
enterprise2SecurityPrefix: enterprise2SecurityPrefix,
staticAssetsBucket: c.String("static-assets-bucket"),
staticAssetEditions: staticAssetEditions,
storybookBucket: c.String("storybook-bucket"),
security: c.Bool("security"),
tag: strings.TrimPrefix(c.String("tag"), "v"),
}
if cfg.security {
cfg.destBucket = securityDestBucket
}
err = copyStaticAssets(cfg)
if err != nil {
return err
}
err = copyStorybook(cfg)
if err != nil {
return err
}
err = copyDownloads(cfg)
if err != nil {
return err
}
err = copyEnterprise2Downloads(cfg)
if err != nil {
return err
}
return nil
}
func copyStaticAssets(cfg publishConfig) error {
for _, edition := range cfg.staticAssetEditions {
log.Printf("Copying static assets for %s", edition)
srcURL := fmt.Sprintf("%s/artifacts/static-assets/%s/%s/*", cfg.srcBucket, edition, cfg.tag)
destURL := fmt.Sprintf("%s/%s/%s/", cfg.staticAssetsBucket, edition, cfg.tag)
err := gcsCopy("static assets", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying static assets, %q", err)
}
}
log.Printf("Successfully copied static assets!")
return nil
}
func copyStorybook(cfg publishConfig) error {
if cfg.security {
log.Printf("skipping storybook copy - not needed for a security release")
return nil
}
log.Printf("Copying storybooks...")
srcURL := fmt.Sprintf("%s/artifacts/storybook/v%s/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/%s", cfg.storybookBucket, cfg.tag)
err := gcsCopy("storybook", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying storybook. %q", err)
}
stableVersion, err := versions.GetLatestVersion(versions.LatestStableVersionURL)
if err != nil {
return err
}
isLatest, err := versions.IsGreaterThanOrEqual(cfg.tag, stableVersion)
if err != nil {
return err
}
if isLatest {
log.Printf("Copying storybooks to latest...")
srcURL := fmt.Sprintf("%s/artifacts/storybook/v%s/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/latest", cfg.storybookBucket)
err := gcsCopy("storybook (latest)", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying storybook to latest. %q", err)
}
}
log.Printf("Successfully copied storybook!")
return nil
}
func copyDownloads(cfg publishConfig) error {
for _, edition := range []string{
"oss", "enterprise",
} {
destURL := fmt.Sprintf("%s/%s/", cfg.destBucket, edition)
srcURL := fmt.Sprintf("%s/artifacts/downloads/v%s/%s/release/*", cfg.srcBucket, cfg.tag, edition)
if !cfg.security {
destURL = filepath.Join(destURL, "release")
}
log.Printf("Copying downloads for %s, from %s bucket to %s bucket", edition, srcURL, destURL)
err := gcsCopy("downloads", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying downloads, %q", err)
}
}
log.Printf("Successfully copied downloads.")
return nil
}
func copyEnterprise2Downloads(cfg publishConfig) error {
var prefix string
if cfg.security {
prefix = cfg.enterprise2SecurityPrefix
}
srcURL := fmt.Sprintf("%s/artifacts/downloads-enterprise2/v%s/enterprise2/release/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/enterprise2/%srelease", cfg.enterprise2DestBucket, prefix)
log.Printf("Copying downloads for enterprise2, from %s bucket to %s bucket", srcURL, destURL)
err := gcsCopy("enterprise2 downloads", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying ")
}
return nil
}
func gcsCopy(desc, src, dest string) error {
args := strings.Split(fmt.Sprintf("-m cp -r gs://%s gs://%s", src, dest), " ")
// nolint:gosec
cmd := exec.Command("gsutil", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to publish %s: %w\n%s", desc, err, out)
}
return nil
}

308
pkg/build/cmd/publishaws.go Normal file
View File

@@ -0,0 +1,308 @@
package main
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/grafana/grafana/pkg/build/config"
"github.com/urfave/cli/v2"
)
const (
marketplaceChangeSetName = "Add new version"
marketplaceCatalogId = "AWSMarketplace"
marketplaceRegistryId = "709825985650"
marketplaceRegistryRegion = "us-east-1"
marketplaceRegistryUrl = "709825985650.dkr.ecr.us-east-1.amazonaws.com"
marketplaceRequestsUrl = "https://aws.amazon.com/marketplace/management/requests/"
releaseNotesTemplateUrl = "https://grafana.com/docs/grafana/latest/release-notes/release-notes-${TAG}/"
helmChartsUrl = "https://grafana.github.io/helm-charts/"
docsUrl = "https://grafana.com/docs/grafana/latest/enterprise/license/"
imagePlatform = "linux/amd64"
publishAwsMarketplaceTestKey publishAwsMarketplaceTestKeyType = "test-client"
)
var (
errEmptyVersion = errors.New(`failed to retrieve release version from metadata, use "--version" to set it manually`)
)
type publishAwsMarketplaceTestKeyType string
type publishAwsMarketplaceFlags struct {
dryRun bool
version string
repo string
image string
product string
}
type AwsMarketplacePublishingService struct {
auth string
docker AwsMarketplaceDocker
ecr AwsMarketplaceRegistry
mkt AwsMarketplaceCatalog
}
type AwsMarketplaceDocker interface {
ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error)
ImageTag(ctx context.Context, source string, target string) error
ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error)
}
type AwsMarketplaceRegistry interface {
GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error)
}
type AwsMarketplaceCatalog interface {
DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error)
StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error)
}
func PublishAwsMarketplace(ctx *cli.Context) error {
f, err := getPublishAwsMarketplaceFlags(ctx)
if err != nil {
return err
}
if f.version == "" {
return errEmptyVersion
}
svc, err := getAwsMarketplacePublishingService()
if err != nil {
return err
}
if ctx.Context.Value(publishAwsMarketplaceTestKey) != nil {
svc = ctx.Context.Value(publishAwsMarketplaceTestKey).(*AwsMarketplacePublishingService)
}
fmt.Println("Logging in to AWS Marketplace registry")
err = svc.Login(ctx.Context)
if err != nil {
return err
}
fmt.Printf("Retrieving image '%s:%s' from Docker Hub\n", f.image, f.version)
err = svc.PullImage(ctx.Context, f.image, f.version)
if err != nil {
return err
}
fmt.Printf("Renaming image '%s:%s' to '%s/%s:%s'\n", f.image, f.version, marketplaceRegistryUrl, f.repo, f.version)
err = svc.TagImage(ctx.Context, f.image, f.repo, f.version)
if err != nil {
return err
}
if !f.dryRun {
fmt.Printf("Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version)
err = svc.PushToMarketplace(ctx.Context, f.repo, f.version)
if err != nil {
return err
}
} else {
fmt.Printf("Dry-Run: Pushing image '%s/%s:%s' to the AWS Marketplace ECR\n", marketplaceRegistryUrl, f.repo, f.version)
}
fmt.Printf("Retrieving product identifier for product '%s'\n", f.product)
pid, err := svc.GetProductIdentifier(ctx.Context, f.product)
if err != nil {
return err
}
if !f.dryRun {
fmt.Printf("Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl)
return svc.ReleaseToProduct(ctx.Context, pid, f.repo, f.version)
} else {
fmt.Printf("Dry-Run: Releasing to product, you can view the progress of the release on %s\n", marketplaceRequestsUrl)
}
return nil
}
func getAwsMarketplacePublishingService() (*AwsMarketplacePublishingService, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, err
}
mySession := session.Must(session.NewSession())
ecr := ecr.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion))
mkt := marketplacecatalog.New(mySession, aws.NewConfig().WithRegion(marketplaceRegistryRegion))
return &AwsMarketplacePublishingService{
docker: cli,
ecr: ecr,
mkt: mkt,
}, nil
}
func (s *AwsMarketplacePublishingService) Login(ctx context.Context) error {
out, err := s.ecr.GetAuthorizationTokenWithContext(ctx, &ecr.GetAuthorizationTokenInput{})
if err != nil {
return err
}
s.auth = *out.AuthorizationData[0].AuthorizationToken
authData, err := base64.StdEncoding.DecodeString(s.auth)
if err != nil {
return err
}
authString := strings.Split(string(authData), ":")
authData, err = json.Marshal(types.AuthConfig{
Username: authString[0],
Password: authString[1],
})
s.auth = base64.StdEncoding.EncodeToString(authData)
return err
}
func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, image string, version string) error {
reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", image, version), types.ImagePullOptions{
Platform: imagePlatform,
})
if err != nil {
return err
}
_, err = io.Copy(os.Stdout, reader)
if err != nil {
return err
}
err = reader.Close()
if err != nil {
return err
}
return nil
}
func (s *AwsMarketplacePublishingService) TagImage(ctx context.Context, image string, repo string, version string) error {
err := s.docker.ImageTag(ctx, fmt.Sprintf("%s:%s", image, version), fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version))
if err != nil {
return err
}
return nil
}
func (s *AwsMarketplacePublishingService) PushToMarketplace(ctx context.Context, repo string, version string) error {
reader, err := s.docker.ImagePush(ctx, fmt.Sprintf("%s/%s:%s", marketplaceRegistryUrl, repo, version), types.ImagePushOptions{
RegistryAuth: s.auth,
})
if err != nil {
return err
}
_, err = io.Copy(os.Stdout, reader)
if err != nil {
return err
}
err = reader.Close()
if err != nil {
return err
}
return nil
}
func (s *AwsMarketplacePublishingService) GetProductIdentifier(ctx context.Context, product string) (string, error) {
out, err := s.mkt.DescribeEntityWithContext(ctx, &marketplacecatalog.DescribeEntityInput{
EntityId: aws.String(product),
Catalog: aws.String(marketplaceCatalogId),
})
if err != nil {
return "", err
}
return *out.EntityIdentifier, nil
}
func (s *AwsMarketplacePublishingService) ReleaseToProduct(ctx context.Context, pid string, repo string, version string) error {
_, err := s.mkt.StartChangeSetWithContext(ctx, &marketplacecatalog.StartChangeSetInput{
Catalog: aws.String(marketplaceCatalogId),
ChangeSetName: aws.String(marketplaceChangeSetName),
ChangeSet: []*marketplacecatalog.Change{
buildAwsMarketplaceChangeSet(pid, repo, version),
},
})
return err
}
func getPublishAwsMarketplaceFlags(ctx *cli.Context) (*publishAwsMarketplaceFlags, error) {
metadata, err := config.GenerateMetadata(ctx)
if err != nil {
return nil, err
}
version := ctx.String("version")
if version == "" && metadata.GrafanaVersion != "" {
version = metadata.GrafanaVersion
}
image := ctx.String("image")
repo := ctx.String("repo")
product := ctx.String("product")
dryRun := ctx.Bool("dry-run")
return &publishAwsMarketplaceFlags{
dryRun: dryRun,
version: version,
image: image,
repo: repo,
product: product,
}, nil
}
func buildAwsMarketplaceReleaseNotesUrl(version string) string {
sanitizedVersion := strings.ReplaceAll(version, ".", "-")
return strings.ReplaceAll(releaseNotesTemplateUrl, "${TAG}", sanitizedVersion)
}
func buildAwsMarketplaceChangeSet(entityId string, repo string, version string) *marketplacecatalog.Change {
return &marketplacecatalog.Change{
ChangeType: aws.String("AddDeliveryOptions"),
Entity: &marketplacecatalog.Entity{
Type: aws.String("ContainerProduct@1.0"),
Identifier: aws.String(entityId),
},
Details: aws.String(buildAwsMarketplaceVersionDetails(repo, version)),
}
}
func buildAwsMarketplaceVersionDetails(repo string, version string) string {
releaseNotesUrl := buildAwsMarketplaceReleaseNotesUrl(version)
return fmt.Sprintf(`{
"Version": {
"ReleaseNotes": "Release notes are available on the website %s",
"VersionTitle": "v%s"
},
"DeliveryOptions": [
{
"Details": {
"EcrDeliveryOptionDetails": {
"DeploymentResources": [
{
"Name": "Helm Charts",
"Url": "%s"
}
],
"CompatibleServices": ["EKS", "ECS", "ECS-Anywhere", "EKS-Anywhere"],
"ContainerImages": ["%s/%s:%s"],
"Description": "Grafana Enterprise can be installed using the official Grafana Helm chart repository. The repository is available on Github: %s",
"UsageInstructions": "You can apply your Grafana Enterprise license to a new or existing Grafana Enterprise deployment by updating a configuration setting or environment variable. Your Grafana instance must be deployed on AWS, or have network access to AWS. For more information, see %s"
}
},
"DeliveryOptionTitle": "Helm Chart"
}
]
}`, releaseNotesUrl, version, helmChartsUrl, marketplaceRegistryUrl, repo, version, helmChartsUrl, docsUrl)
}

View File

@@ -0,0 +1,207 @@
package main
import (
"bytes"
"context"
"encoding/base64"
"errors"
"io"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
type awsPublishTestCase struct {
name string
args []string
expectedError error
errorContains string
expectedOutput string
mockedService *AwsMarketplacePublishingService
}
func TestPublishAwsMarketplace(t *testing.T) {
t.Setenv("DRONE_BUILD_EVENT", "promote")
t.Setenv("DRONE_TAG", "v1.0.0")
t.Setenv("DRONE_COMMIT", "abcdefgh")
testApp := setupPublishAwsMarketplaceTests(t)
errShouldNotCallMock := errors.New("shouldn't call")
testCases := []awsPublishTestCase{
{
name: "try to publish without required flags",
errorContains: `Required flags "image, repo, product" not set`,
},
{
name: "try to publish without credentials",
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
mockedService: &AwsMarketplacePublishingService{
ecr: &mockAwsMarketplaceRegistry{
GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain,
},
},
expectedError: credentials.ErrNoValidProvidersFoundInChain,
},
{
name: "try to publish with valid credentials and nonexisting version",
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
mockedService: &AwsMarketplacePublishingService{
ecr: &mockAwsMarketplaceRegistry{},
docker: &mockAwsMarketplaceDocker{},
mkt: &mockAwsMarketplaceCatalog{},
},
expectedOutput: "Releasing to product",
},
{
name: "try to publish with valid credentials and existing version",
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
mockedService: &AwsMarketplacePublishingService{
ecr: &mockAwsMarketplaceRegistry{},
docker: &mockAwsMarketplaceDocker{},
mkt: &mockAwsMarketplaceCatalog{},
},
expectedOutput: "Releasing to product",
},
{
name: "dry run with invalid credentials",
args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
mockedService: &AwsMarketplacePublishingService{
ecr: &mockAwsMarketplaceRegistry{
GetAuthorizationTokenWithContextError: credentials.ErrNoValidProvidersFoundInChain,
},
},
expectedError: credentials.ErrNoValidProvidersFoundInChain,
},
{
name: "dry run with valid credentials",
args: []string{"--dry-run", "--image", "test/test", "--repo", "test/test", "--product", "test", "--version", "1.0.0"},
mockedService: &AwsMarketplacePublishingService{
ecr: &mockAwsMarketplaceRegistry{},
docker: &mockAwsMarketplaceDocker{
ImagePushError: errShouldNotCallMock,
},
mkt: &mockAwsMarketplaceCatalog{
StartChangeSetWithContextError: errShouldNotCallMock,
},
},
expectedOutput: "Dry-Run: Releasing to product",
},
}
if os.Getenv("DRONE_COMMIT") == "" {
// this test only works locally due to Drone environment
testCases = append(testCases,
awsPublishTestCase{
name: "try to publish without version",
args: []string{"--image", "test/test", "--repo", "test/test", "--product", "test"},
expectedError: errEmptyVersion,
},
)
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
ctx := context.WithValue(context.Background(), publishAwsMarketplaceTestKey, test.mockedService)
args := []string{"run"}
args = append(args, test.args...)
out, err := captureStdout(t, func() error {
return testApp.RunContext(ctx, args)
})
if test.expectedOutput != "" {
assert.Contains(t, out, test.expectedOutput)
}
if test.expectedError != nil || test.errorContains != "" {
assert.Error(t, err)
if test.expectedError != nil {
assert.ErrorIs(t, err, test.expectedError)
}
if test.errorContains != "" {
assert.ErrorContains(t, err, test.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
func setupPublishAwsMarketplaceTests(t *testing.T) *cli.App {
t.Helper()
testApp := cli.NewApp()
testApp.Action = PublishAwsMarketplace
testApp.Flags = []cli.Flag{
&dryRunFlag,
&cli.StringFlag{
Name: "version",
Usage: "Release version (default from metadata)",
},
&cli.StringFlag{
Name: "image",
Required: true,
Usage: "Name of the image to be released",
},
&cli.StringFlag{
Name: "repo",
Required: true,
Usage: "AWS Marketplace ECR repository",
},
&cli.StringFlag{
Name: "product",
Required: true,
Usage: "AWS Marketplace product identifier",
},
}
return testApp
}
type mockAwsMarketplaceDocker struct {
ImagePullError error
ImageTagError error
ImagePushError error
}
func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError
}
func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error {
return m.ImageTagError
}
func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError
}
type mockAwsMarketplaceRegistry struct {
GetAuthorizationTokenWithContextError error
}
func (m *mockAwsMarketplaceRegistry) GetAuthorizationTokenWithContext(ctx context.Context, input *ecr.GetAuthorizationTokenInput, opts ...request.Option) (*ecr.GetAuthorizationTokenOutput, error) {
return &ecr.GetAuthorizationTokenOutput{
AuthorizationData: []*ecr.AuthorizationData{
{
AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString([]byte("username:password"))),
},
},
}, m.GetAuthorizationTokenWithContextError
}
type mockAwsMarketplaceCatalog struct {
DescribeEntityWithContextError error
StartChangeSetWithContextError error
}
func (m *mockAwsMarketplaceCatalog) DescribeEntityWithContext(ctx context.Context, input *marketplacecatalog.DescribeEntityInput, opts ...request.Option) (*marketplacecatalog.DescribeEntityOutput, error) {
return &marketplacecatalog.DescribeEntityOutput{
EntityIdentifier: aws.String("productid"),
}, m.DescribeEntityWithContextError
}
func (m *mockAwsMarketplaceCatalog) StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) {
return &marketplacecatalog.StartChangeSetOutput{}, m.StartChangeSetWithContextError
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"github.com/google/go-github/github"
"github.com/grafana/grafana/pkg/build/config"
"github.com/urfave/cli/v2"
"golang.org/x/oauth2"
)
@@ -39,9 +40,9 @@ var (
errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`)
)
func PublishGitHub(ctx *cli.Context) error {
func PublishGithub(ctx *cli.Context) error {
token := os.Getenv("GH_TOKEN")
f, err := getFlags(ctx)
f, err := getPublishGithubFlags(ctx)
if err != nil {
return err
}
@@ -55,7 +56,7 @@ func PublishGitHub(ctx *cli.Context) error {
}
if f.dryRun {
return runDryRun(f, token, ctx)
return runPublishGithubDryRun(f, token, ctx)
}
client := newGithubClient(ctx.Context, token)
@@ -99,8 +100,8 @@ func githubRepositoryClient(ctx context.Context, token string) githubRepositoryS
return client.Repositories
}
func getFlags(ctx *cli.Context) (*publishGithubFlags, error) {
metadata, err := GenerateMetadata(ctx)
func getPublishGithubFlags(ctx *cli.Context) (*publishGithubFlags, error) {
metadata, err := config.GenerateMetadata(ctx)
if err != nil {
return nil, err
}
@@ -130,12 +131,12 @@ func getFlags(ctx *cli.Context) (*publishGithubFlags, error) {
}, nil
}
func runDryRun(f *publishGithubFlags, token string, ctx *cli.Context) error {
func runPublishGithubDryRun(f *publishGithubFlags, token string, ctx *cli.Context) error {
client := newGithubClient(ctx.Context, token)
fmt.Println("Dry-Run: Retrieving release on repository by tag")
release, res, err := client.GetReleaseByTag(ctx.Context, f.repo.owner, f.repo.name, f.tag)
if err != nil && res.StatusCode != 404 {
fmt.Println("Dry-Run: GitHub communication error:\n", err)
fmt.Println("Dry-Run: Github communication error:\n", err)
return nil
}

View File

@@ -21,17 +21,19 @@ type githubPublishTestCases struct {
expectedError error
errorContains string
expectedOutput string
mockedService *mockGitHubRepositoryServiceImpl
mockedService *mockGithubRepositoryServiceImpl
}
var mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{}
var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
func mockGithubRepositoryClient(context.Context, string) githubRepositoryService {
return mockGitHubRepositoryService
return mockGithubRepositoryService
}
func TestPublishGitHub(t *testing.T) {
func TestPublishGithub(t *testing.T) {
t.Setenv("DRONE_BUILD_EVENT", "promote")
t.Setenv("DRONE_TAG", "v1.0.0")
t.Setenv("DRONE_COMMIT", "abcdefgh")
testApp, testPath := setupPublishGithubTests(t)
mockErrUnauthorized := errors.New("401")
@@ -49,21 +51,21 @@ func TestPublishGitHub(t *testing.T) {
name: "try to publish with invalid token",
token: "invalid",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
expectedError: mockErrUnauthorized,
},
{
name: "try to publish with valid token and nonexisting tag with create disabled",
token: "valid",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound},
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
expectedError: errReleaseNotFound,
},
{
name: "try to publish with valid token and nonexisting tag with create enabled",
token: "valid",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound},
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
},
{
name: "try to publish with valid token and existing tag",
@@ -74,21 +76,21 @@ func TestPublishGitHub(t *testing.T) {
name: "dry run with invalid token",
token: "invalid",
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
expectedOutput: "GitHub communication error",
mockedService: &mockGithubRepositoryServiceImpl{tagErr: mockErrUnauthorized},
expectedOutput: "Github communication error",
},
{
name: "dry run with valid token and nonexisting tag with create disabled",
token: "valid",
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound},
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
expectedOutput: "Release doesn't exist",
},
{
name: "dry run with valid token and nonexisting tag with create enabled",
token: "valid",
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0", "--create"},
mockedService: &mockGitHubRepositoryServiceImpl{tagErr: errReleaseNotFound},
mockedService: &mockGithubRepositoryServiceImpl{tagErr: errReleaseNotFound},
expectedOutput: "Would upload asset",
},
{
@@ -116,9 +118,9 @@ func TestPublishGitHub(t *testing.T) {
t.Setenv("GH_TOKEN", test.token)
}
if test.mockedService != nil {
mockGitHubRepositoryService = test.mockedService
mockGithubRepositoryService = test.mockedService
} else {
mockGitHubRepositoryService = &mockGitHubRepositoryServiceImpl{}
mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
}
args := []string{"run"}
args = append(args, test.args...)
@@ -154,7 +156,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) {
newGithubClient = mockGithubRepositoryClient
testApp := cli.NewApp()
testApp.Action = PublishGitHub
testApp.Action = PublishGithub
testApp.Flags = []cli.Flag{
&dryRunFlag,
&cli.StringFlag{
@@ -165,7 +167,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) {
&cli.StringFlag{
Name: "repo",
Required: true,
Usage: "GitHub repository",
Usage: "Github repository",
},
&cli.StringFlag{
Name: "tag",
@@ -194,13 +196,13 @@ func captureStdout(t *testing.T, fn func() error) (string, error) {
return string(out), err
}
type mockGitHubRepositoryServiceImpl struct {
type mockGithubRepositoryServiceImpl struct {
tagErr error
createErr error
uploadErr error
}
func (m *mockGitHubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) {
func (m *mockGithubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error) {
var release *github.RepositoryRelease
res := &github.Response{Response: &http.Response{}}
if m.tagErr == nil {
@@ -212,12 +214,12 @@ func (m *mockGitHubRepositoryServiceImpl) GetReleaseByTag(ctx context.Context, o
return release, res, m.tagErr
}
func (m *mockGitHubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) {
func (m *mockGithubRepositoryServiceImpl) CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) {
releaseID := int64(1)
return &github.RepositoryRelease{ID: &releaseID}, &github.Response{}, m.createErr
}
func (m *mockGitHubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) {
func (m *mockGithubRepositoryServiceImpl) UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) {
assetName := "test"
assetUrl := "testurl.com.br"
return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr

View File

@@ -0,0 +1,100 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/docker"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/urfave/cli/v2"
)
func Enterprise2(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
if err := gcloud.ActivateServiceAccount(); err != nil {
return fmt.Errorf("couldn't activate service account, err: %w", err)
}
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil {
return err
}
cfg := docker.Config{
Archs: buildConfig.Docker.Architectures,
Distribution: buildConfig.Docker.Distribution,
DockerHubRepo: c.String("dockerhub-repo"),
Tag: metadata.GrafanaVersion,
}
err = dockerLoginEnterprise2()
if err != nil {
return err
}
var distributionStr []string
for _, distribution := range cfg.Distribution {
switch distribution {
case alpine:
distributionStr = append(distributionStr, "")
case ubuntu:
distributionStr = append(distributionStr, "-ubuntu")
default:
return fmt.Errorf("unrecognized distribution %q", distribution)
}
}
for _, distribution := range distributionStr {
var imageFileNames []string
for _, arch := range cfg.Archs {
imageFilename := fmt.Sprintf("%s:%s%s-%s", cfg.DockerHubRepo, cfg.Tag, distribution, arch)
err := docker.PushImage(imageFilename)
if err != nil {
return err
}
imageFileNames = append(imageFileNames, imageFilename)
}
manifest := fmt.Sprintf("%s:%s%s", cfg.DockerHubRepo, cfg.Tag, distribution)
args := []string{"manifest", "create", manifest}
args = append(args, imageFileNames...)
//nolint:gosec
cmd := exec.Command("docker", args...)
cmd.Env = append(os.Environ(), "DOCKER_CLI_EXPERIMENTAL=enabled")
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to create Docker manifest: %w\n%s", err, output)
}
err = docker.PushManifest(manifest)
if err != nil {
return err
}
}
return nil
}
func dockerLoginEnterprise2() error {
log.Println("Docker login...")
cmd := exec.Command("gcloud", "auth", "configure-docker")
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("error logging in to DockerHub: %s %q", out, err)
}
log.Println("Successful login!")
return nil
}

View File

@@ -8,8 +8,9 @@ import (
"os"
"regexp"
"github.com/grafana/grafana/pkg/build/metrics"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/metrics"
)
func PublishMetrics(c *cli.Context) error {
@@ -17,24 +18,24 @@ func PublishMetrics(c *cli.Context) error {
input, err := io.ReadAll(os.Stdin)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Reading from stdin failed: %s", err), 1)
return cli.Exit(fmt.Sprintf("Reading from stdin failed: %s", err), 1)
}
reMetrics := regexp.MustCompile(`(?ms)^Metrics: (\{.+\})`)
ms := reMetrics.FindSubmatch(input)
if len(ms) == 0 {
return cli.NewExitError(fmt.Sprintf("Input on wrong format: %q", string(input)), 1)
return cli.Exit(fmt.Sprintf("Input on wrong format: %q", string(input)), 1)
}
m := map[string]string{}
if err := json.Unmarshal(ms[1], &m); err != nil {
return cli.NewExitError(fmt.Sprintf("decoding metrics failed: %s", err), 1)
return cli.Exit(fmt.Sprintf("decoding metrics failed: %s", err), 1)
}
log.Printf("Received metrics %+v", m)
if err := metrics.Publish(m, apiKey); err != nil {
return cli.NewExitError(fmt.Sprintf("publishing metrics failed: %s", err), 1)
return cli.Exit(fmt.Sprintf("publishing metrics failed: %s", err), 1)
}
return nil

View File

@@ -13,7 +13,7 @@ import (
func StoreStorybook(c *cli.Context) error {
deployment := c.String("deployment")
metadata, err := GenerateMetadata(c)
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}

View File

@@ -0,0 +1,75 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/urfave/cli/v2"
)
// UploadCDN implements the sub-command "upload-cdn".
func UploadCDN(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
version := metadata.GrafanaVersion
if err != nil {
return cli.Exit(err.Error(), 1)
}
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil {
return err
}
edition := os.Getenv("EDITION")
log.Printf("Uploading Grafana CDN Assets, version %s, %s edition...", version, edition)
editionPath := ""
switch config.Edition(edition) {
case config.EditionOSS:
editionPath = "grafana-oss"
case config.EditionEnterprise:
editionPath = "grafana"
case config.EditionEnterprise2:
editionPath = os.Getenv("ENTERPRISE2_CDN_PATH")
default:
panic(fmt.Sprintf("unrecognized edition %q", edition))
}
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(buildConfig.Buckets.CDNAssets)
srcPath := buildConfig.Buckets.CDNAssetsDir
srcPath = filepath.Join(srcPath, editionPath, version)
if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil {
return err
}
log.Printf("Successfully cleaned source: %s/%s\n", buildConfig.Buckets.CDNAssets, srcPath)
if err := gcs.CopyLocalDir(c.Context, "./public", bucket, srcPath, false); err != nil {
return err
}
log.Printf("Successfully uploaded cdn static assets to: %s/%s!\n", buildConfig.Buckets.CDNAssets, srcPath)
return nil
}

View File

@@ -0,0 +1,213 @@
package main
import (
"encoding/base64"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/droneutil"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/packaging"
"github.com/urfave/cli/v2"
)
const releaseFolder = "release"
const mainFolder = "main"
const releaseBranchFolder = "prerelease"
type uploadConfig struct {
config.Config
edition config.Edition
versionMode config.VersionMode
gcpKey string
distDir string
versionFolder string
}
// UploadPackages implements the sub-command "upload-packages".
func UploadPackages(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
gcpKeyB64 := strings.TrimSpace(os.Getenv("GCP_KEY"))
if gcpKeyB64 == "" {
return cli.Exit("the environment variable GCP_KEY must be set", 1)
}
gcpKeyB, err := base64.StdEncoding.DecodeString(gcpKeyB64)
if err != nil {
return cli.Exit("failed to base64 decode $GCP_KEY", 1)
}
gcpKey := string(gcpKeyB)
distDir, err := filepath.Abs("dist")
if err != nil {
return cli.Exit(err.Error(), 1)
}
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
version := metadata.GrafanaVersion
releaseMode, err := metadata.GetReleaseMode()
if err != nil {
return cli.Exit(err.Error(), 1)
}
releaseModeConfig, err := config.GetBuildConfig(releaseMode.Mode)
if err != nil {
return cli.Exit(err.Error(), 1)
}
var edition config.Edition
if e, ok := os.LookupEnv("EDITION"); ok {
edition = config.Edition(e)
}
if c.Bool("enterprise2") {
edition = config.EditionEnterprise2
}
if edition == "" {
return fmt.Errorf("both EDITION envvar and '--enterprise2' flag are missing. At least one of those is required")
}
// TODO: Verify config values
cfg := uploadConfig{
Config: config.Config{
Version: version,
Bucket: releaseModeConfig.Buckets.Artifacts,
},
edition: edition,
versionMode: releaseMode.Mode,
gcpKey: gcpKey,
distDir: distDir,
}
event, err := droneutil.GetDroneEventFromEnv()
if err != nil {
return err
}
if cfg.edition == config.EditionEnterprise2 {
cfg.Bucket, err = bucketForEnterprise2(releaseModeConfig, event)
if err != nil {
return err
}
}
cfg.versionFolder, err = getVersionFolder(cfg, event)
if err != nil {
return err
}
if err := uploadPackages(cfg); err != nil {
return cli.Exit(err.Error(), 1)
}
log.Println("Successfully uploaded packages!")
return nil
}
// Corner case for custom enterprise2 mode
func bucketForEnterprise2(releaseModeConfig *config.BuildConfig, event string) (string, error) {
if event == config.Custom {
buildConfig, err := config.GetBuildConfig(config.ReleaseBranchMode)
if err != nil {
return "", err
}
return buildConfig.Buckets.ArtifactsEnterprise2, nil
}
if releaseModeConfig.Buckets.ArtifactsEnterprise2 != "" {
return releaseModeConfig.Buckets.ArtifactsEnterprise2, nil
}
return "", fmt.Errorf("enterprise2 bucket var doesn't exist")
}
func getVersionFolder(cfg uploadConfig, event string) (string, error) {
switch cfg.versionMode {
case config.TagMode:
return releaseFolder, nil
case config.MainMode, config.DownstreamMode:
return mainFolder, nil
case config.ReleaseBranchMode:
return releaseBranchFolder, nil
default:
// Corner case for custom enterprise2 mode
if event == config.Custom && cfg.versionMode == config.Enterprise2Mode {
return releaseFolder, nil
}
return "", fmt.Errorf("unrecognized version mode: %s", cfg.versionMode)
}
}
func uploadPackages(cfg uploadConfig) error {
log.Printf("Uploading Grafana packages, version %s, %s edition, %s mode...\n", cfg.Version, cfg.edition,
cfg.versionMode)
if err := gcloud.ActivateServiceAccount(); err != nil {
return fmt.Errorf("couldn't activate service account, err: %w", err)
}
edition := strings.ToLower(string(cfg.edition))
var sfx string
switch cfg.edition {
case config.EditionOSS:
case config.EditionEnterprise:
sfx = "-enterprise"
case config.EditionEnterprise2:
sfx = "-enterprise2"
default:
panic(fmt.Sprintf("unrecognized edition %q", cfg.edition))
}
matches, err := filepath.Glob(filepath.Join(cfg.distDir, fmt.Sprintf("grafana%s*", sfx)))
if err != nil {
return fmt.Errorf("failed to list packages: %w", err)
}
fpaths := []string{}
rePkg := packaging.PackageRegexp(cfg.edition)
for _, fpath := range matches {
fname := filepath.Base(fpath)
if strings.Contains(fname, "latest") || !rePkg.MatchString(fname) {
log.Printf("Ignoring file %q\n", fpath)
continue
}
fpaths = append(fpaths, fpath)
}
var tag, gcsPath string
droneTag := strings.TrimSpace(os.Getenv("DRONE_TAG"))
if droneTag != "" {
tag = droneTag
gcsPath = fmt.Sprintf("gs://%s/%s/%s/%s", cfg.Bucket, tag, edition, cfg.versionFolder)
} else {
gcsPath = fmt.Sprintf("gs://%s/%s/%s/", cfg.Bucket, edition, cfg.versionFolder)
}
log.Printf("Uploading %d file(s) to GCS (%s)...\n", len(fpaths), gcsPath)
args := []string{"-m", "cp"}
args = append(args, fpaths...)
args = append(args, gcsPath)
cmd := exec.Command("gsutil", args...)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to upload files to GCS: %s", output)
}
return nil
}

View File

@@ -0,0 +1,66 @@
package main
import (
"errors"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_getVersionFolder(t *testing.T) {
type args struct {
cfg uploadConfig
event string
versionFolder string
}
tests := []struct {
name string
args args
err error
}{
{"tag mode", args{uploadConfig{versionMode: config.TagMode}, "", releaseFolder}, nil},
{"main mode", args{uploadConfig{versionMode: config.MainMode}, "", mainFolder}, nil},
{"downstream mode", args{uploadConfig{versionMode: config.DownstreamMode}, "", mainFolder}, nil},
{"release branch mode", args{uploadConfig{versionMode: config.ReleaseBranchMode}, "", releaseBranchFolder}, nil},
{"enterprise pro mode", args{uploadConfig{versionMode: config.Enterprise2Mode}, config.Custom, releaseFolder}, nil},
{"unrecognised version mode", args{uploadConfig{versionMode: "foo"}, config.Custom, ""}, errors.New("")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
versionMode, err := getVersionFolder(tt.args.cfg, tt.args.event)
if tt.err != nil {
require.Error(t, err)
}
require.Equal(t, versionMode, tt.args.versionFolder)
})
}
}
func Test_checkForEnterprise2Edition(t *testing.T) {
type args struct {
releaseModeConfig *config.BuildConfig
event string
}
tests := []struct {
name string
args args
want string
err error
}{
{"event is not custom", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: "dummy"}}}, "dummy", nil},
{"event is not custom and string is empty", args{releaseModeConfig: &config.BuildConfig{Buckets: config.Buckets{ArtifactsEnterprise2: ""}}}, "", fmt.Errorf("enterprise2 bucket var doesn't exist")},
{"event is custom", args{releaseModeConfig: nil, event: "custom"}, "grafana-downloads-enterprise2", nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := bucketForEnterprise2(tt.args.releaseModeConfig, tt.args.event)
if tt.err != nil {
require.Error(t, err)
}
assert.Equalf(t, tt.want, got, "bucketForEnterprise2(%v, %v)", tt.args.releaseModeConfig, tt.args.event)
})
}
}

View File

@@ -13,10 +13,11 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/build/fsutil"
cliv1 "github.com/urfave/cli"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/build/fsutil"
)
func VerifyDrone(c *cli.Context) error {
@@ -24,7 +25,7 @@ func VerifyDrone(c *cli.Context) error {
const backup = ".drone.yml.bak"
if err := fsutil.CopyFile(yml, backup); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1)
return cli.Exit(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1)
}
defer func() {
if err := os.Remove(yml); err != nil {
@@ -73,7 +74,7 @@ func readConfig(fpath string) ([]map[string]interface{}, error) {
//nolint:gosec
f, err := os.Open(fpath)
if err != nil {
return nil, cli.NewExitError(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
return nil, cli.Exit(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
}
defer func() {
if err := f.Close(); err != nil {
@@ -90,7 +91,7 @@ func readConfig(fpath string) ([]map[string]interface{}, error) {
if errors.Is(err, io.EOF) {
break
}
return nil, cli.NewExitError(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
return nil, cli.Exit(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
}
if m["kind"] == "signature" {
@@ -118,7 +119,7 @@ func verifyYAML(yml, backup string) error {
}
if !cmp.Equal(c1, c2) {
return cli.NewExitError(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
return cli.Exit(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
yml), 1)
}

View File

@@ -0,0 +1,142 @@
package main
import (
"context"
"errors"
"fmt"
"io/fs"
"os/exec"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
)
func mapSlice[I any, O any](a []I, f func(I) O) []O {
o := make([]O, len(a))
for i, e := range a {
o[i] = f(e)
}
return o
}
// VerifyStarlark is the CLI Action for verifying Starlark files in a workspace.
// It expects a single context argument which is the path to the workspace.
// The actual verification procedure can return multiple errors which are
// joined together to be one holistic error for the action.
func VerifyStarlark(c *cli.Context) error {
if c.NArg() != 1 {
var message string
if c.NArg() == 0 {
message = "ERROR: missing required argument <workspace path>"
}
if c.NArg() > 1 {
message = "ERROR: too many arguments"
}
if err := cli.ShowSubcommandHelp(c); err != nil {
return err
}
return cli.Exit(message, 1)
}
workspace := c.Args().Get(0)
verificationErrs, executionErr := verifyStarlark(c.Context, workspace, buildifierLintCommand)
if executionErr != nil {
return executionErr
}
if len(verificationErrs) == 0 {
return nil
}
noun := "file"
if len(verificationErrs) > 1 {
noun += "s"
}
return fmt.Errorf("verification failed for %d %s:\n%s",
len(verificationErrs),
noun,
strings.Join(
mapSlice(verificationErrs, func(e error) string { return e.Error() }),
"\n",
))
}
type commandFunc = func(path string) (command string, args []string)
func buildifierLintCommand(path string) (string, []string) {
return "buildifier", []string{"-lint", "warn", "-mode", "check", path}
}
// verifyStarlark walks all directories starting at provided workspace path and
// verifies any Starlark files it finds.
// Starlark files are assumed to end with the .star extension.
// The verification relies on linting frovided by the 'buildifier' binary which
// must be in the PATH.
// A slice of verification errors are returned, one for each file that failed verification.
// If any execution of the `buildifier` command fails, this is returned separately.
// commandFn is executed on every Starlark file to determine the command and arguments to be executed.
// The caller is trusted and it is the callers responsibility to ensure that the resulting command is safe to execute.
func verifyStarlark(ctx context.Context, workspace string, commandFn commandFunc) ([]error, error) {
var verificationErrs []error
// All errors from filepath.WalkDir are filtered by the fs.WalkDirFunc.
// Lstat or ReadDir errors are reported as verificationErrors.
// If any execution of the `buildifier` command fails or if the context is cancelled,
// it is reported as an error and any verification of subsequent files is skipped.
err := filepath.WalkDir(workspace, func(path string, d fs.DirEntry, err error) error {
// Skip verification of the file or files within the directory if there is an error
// returned by Lstat or ReadDir.
if err != nil {
verificationErrs = append(verificationErrs, err)
return nil
}
if d.IsDir() {
return nil
}
if filepath.Ext(path) == ".star" {
command, args := commandFn(path)
// The caller is trusted.
//nolint:gosec
cmd := exec.CommandContext(ctx, command, args...)
cmd.Dir = workspace
_, err = cmd.Output()
if err == nil { // No error, early return.
return nil
}
// The error returned from cmd.Output() is never wrapped.
//nolint:errorlint
if err, ok := err.(*exec.ExitError); ok {
switch err.ExitCode() {
// Case comments are informed by the output of `buildifier --help`
case 1: // syntax errors in input
verificationErrs = append(verificationErrs, errors.New(string(err.Stderr)))
return nil
case 2: // usage errors: invoked incorrectly
return fmt.Errorf("command %q: %s", cmd, err.Stderr)
case 3: // unexpected runtime errors: file I/O problems or internal bugs
return fmt.Errorf("command %q: %s", cmd, err.Stderr)
case 4: // check mode failed (reformat is needed)
verificationErrs = append(verificationErrs, errors.New(string(err.Stderr)))
return nil
default:
return fmt.Errorf("command %q: %s", cmd, err.Stderr)
}
}
// Error was not an exit error from the command.
return fmt.Errorf("command %q: %v", cmd, err)
}
return nil
})
return verificationErrs, err
}

View File

@@ -0,0 +1,137 @@
//go:build requires_buildifier
package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestVerifyStarlark(t *testing.T) {
t.Run("execution errors", func(t *testing.T) {
t.Run("invalid usage", func(t *testing.T) {
ctx := context.Background()
workspace := t.TempDir()
err := os.WriteFile(filepath.Join(workspace, "ignored.star"), []byte{}, os.ModePerm)
if err != nil {
t.Fatalf(err.Error())
}
_, executionErr := verifyStarlark(ctx, workspace, func(string) (string, []string) { return "buildifier", []string{"--invalid"} })
if executionErr == nil {
t.Fatalf("Expected execution error but got none")
}
})
t.Run("context cancellation", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
workspace := t.TempDir()
err := os.WriteFile(filepath.Join(workspace, "ignored.star"), []byte{}, os.ModePerm)
if err != nil {
t.Fatalf(err.Error())
}
err = os.WriteFile(filepath.Join(workspace, "other-ignored.star"), []byte{}, os.ModePerm)
if err != nil {
t.Fatalf(err.Error())
}
cancel()
_, executionErr := verifyStarlark(ctx, workspace, buildifierLintCommand)
if executionErr == nil {
t.Fatalf("Expected execution error but got none")
}
})
})
t.Run("verification errors", func(t *testing.T) {
t.Run("a single file with lint", func(t *testing.T) {
ctx := context.Background()
workspace := t.TempDir()
invalidContent := []byte(`load("scripts/drone/other.star", "function")
function()`)
err := os.WriteFile(filepath.Join(workspace, "has-lint.star"), invalidContent, os.ModePerm)
if err != nil {
t.Fatalf(err.Error())
}
verificationErrs, executionErr := verifyStarlark(ctx, workspace, buildifierLintCommand)
if executionErr != nil {
t.Fatalf("Unexpected execution error: %v", executionErr)
}
if len(verificationErrs) == 0 {
t.Fatalf(`"has-lint.star" requires linting but the verifyStarlark function provided no linting error`)
}
if len(verificationErrs) > 1 {
t.Fatalf(`verifyStarlark returned multiple errors for the "has-lint.star" file but only one was expected: %v`, verificationErrs)
}
if !strings.Contains(verificationErrs[0].Error(), "has-lint.star:1: module-docstring: The file has no module docstring.") {
t.Fatalf(`"has-lint.star" is missing a module docstring but the verifyStarlark function linting error did not mention this, instead we got: %v`, verificationErrs[0])
}
})
t.Run("no files with lint", func(t *testing.T) {
ctx := context.Background()
workspace := t.TempDir()
content := []byte(`"""
This module does nothing.
"""
load("scripts/drone/other.star", "function")
function()
`)
require.NoError(t, os.WriteFile(filepath.Join(workspace, "no-lint.star"), content, os.ModePerm))
verificationErrs, executionErr := verifyStarlark(ctx, workspace, buildifierLintCommand)
if executionErr != nil {
t.Fatalf("Unexpected execution error: %v", executionErr)
}
if len(verificationErrs) != 0 {
t.Log(`"no-lint.star" has no lint but the verifyStarlark function provided at least one error`)
for _, err := range verificationErrs {
t.Log(err)
}
t.FailNow()
}
})
t.Run("multiple files with lint", func(t *testing.T) {
ctx := context.Background()
workspace := t.TempDir()
invalidContent := []byte(`load("scripts/drone/other.star", "function")
function()`)
require.NoError(t, os.WriteFile(filepath.Join(workspace, "has-lint.star"), invalidContent, os.ModePerm))
require.NoError(t, os.WriteFile(filepath.Join(workspace, "has-lint2.star"), invalidContent, os.ModePerm))
verificationErrs, executionErr := verifyStarlark(ctx, workspace, buildifierLintCommand)
if executionErr != nil {
t.Fatalf("Unexpected execution error: %v", executionErr)
}
if len(verificationErrs) == 0 {
t.Fatalf(`Two files require linting but the verifyStarlark function provided no linting error`)
}
if len(verificationErrs) == 1 {
t.Fatalf(`Two files require linting but the verifyStarlark function provided only one linting error: %v`, verificationErrs[0])
}
if len(verificationErrs) > 2 {
t.Fatalf(`verifyStarlark returned more errors than expected: %v`, verificationErrs)
}
if !strings.Contains(verificationErrs[0].Error(), "has-lint.star:1: module-docstring: The file has no module docstring.") {
t.Errorf(`"has-lint.star" is missing a module docstring but the verifyStarlark function linting error did not mention this, instead we got: %v`, verificationErrs[0])
}
if !strings.Contains(verificationErrs[1].Error(), "has-lint2.star:1: module-docstring: The file has no module docstring.") {
t.Fatalf(`"has-lint2.star" is missing a module docstring but the verifyStarlark function linting error did not mention this, instead we got: %v`, verificationErrs[0])
}
})
})
}

View File

@@ -0,0 +1,33 @@
// Package verifystorybook contains the sub-command "verify-storybook".
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/infra/fs"
)
// VerifyStorybook Action implements the sub-command "verify-storybook".
func VerifyStorybook(c *cli.Context) error {
const grafanaDir = "."
paths := []string{
"packages/grafana-ui/dist/storybook/index.html",
"packages/grafana-ui/dist/storybook/iframe.html"}
for _, p := range paths {
exists, err := fs.Exists(filepath.Join(grafanaDir, p))
if err != nil {
return cli.Exit(fmt.Sprintf("failed to verify Storybook build: %s", err), 1)
}
if !exists {
return fmt.Errorf("failed to verify Storybook build, missing %q", p)
}
}
log.Printf("Successfully verified Storybook integrity")
return nil
}

View File

@@ -1,20 +1,18 @@
package config
type Config struct {
Version string
Bucket string
DebRepoBucket string
DebDBBucket string
RPMRepoBucket string
GPGPassPath string
GPGPrivateKey string
GPGPublicKey string
GCPKeyFile string
NumWorkers int
GitHubUser string
GitHubToken string
PullEnterprise bool
NetworkConcurrency bool
PackageVersion string
SignPackages bool
Version string
Bucket string
DebRepoBucket string
DebDBBucket string
RPMRepoBucket string
GPGPassPath string
GPGPrivateKey string
GPGPublicKey string
NumWorkers int
GitHubUser string
GitHubToken string
PullEnterprise bool
PackageVersion string
SignPackages bool
}

View File

@@ -0,0 +1,102 @@
package config
import (
"fmt"
"os"
"strings"
"github.com/grafana/grafana/pkg/build/droneutil"
"github.com/urfave/cli/v2"
)
func GenerateMetadata(c *cli.Context) (Metadata, error) {
var metadata Metadata
version := ""
event, err := droneutil.GetDroneEventFromEnv()
if err != nil {
return Metadata{}, err
}
tag, ok := os.LookupEnv("DRONE_TAG")
if !ok {
fmt.Println("DRONE_TAG envvar not present, %w", err)
}
var releaseMode ReleaseMode
switch event {
case string(PullRequestMode):
releaseMode = ReleaseMode{Mode: PullRequestMode}
case Push:
mode, err := CheckDroneTargetBranch()
if err != nil {
return Metadata{}, err
}
releaseMode = ReleaseMode{Mode: mode}
case Custom:
if edition, _ := os.LookupEnv("EDITION"); edition == string(EditionEnterprise2) {
releaseMode = ReleaseMode{Mode: Enterprise2Mode}
if tag != "" {
version = strings.TrimPrefix(tag, "v")
}
break
}
mode, err := CheckDroneTargetBranch()
if err != nil {
return Metadata{}, err
}
// if there is a custom event targeting the main branch, that's an enterprise downstream build
if mode == MainBranch {
releaseMode = ReleaseMode{Mode: DownstreamMode}
} else {
releaseMode = ReleaseMode{Mode: mode}
}
case Tag, Promote:
if tag == "" {
return Metadata{}, fmt.Errorf("DRONE_TAG envvar not present for a tag/promotion event, %w", err)
}
version = strings.TrimPrefix(tag, "v")
mode, err := CheckSemverSuffix()
if err != nil {
return Metadata{}, err
}
releaseMode = mode
case Cronjob:
releaseMode = ReleaseMode{Mode: CronjobMode}
}
if version == "" {
version, err = generateVersionFromBuildID()
if err != nil {
return Metadata{}, err
}
}
currentCommit, err := GetDroneCommit()
if err != nil {
return Metadata{}, err
}
metadata = Metadata{
GrafanaVersion: version,
ReleaseMode: releaseMode,
GrabplVersion: c.App.Version,
CurrentCommit: currentCommit,
}
fmt.Printf("building Grafana version: %s, release mode: %+v", metadata.GrafanaVersion, metadata.ReleaseMode)
return metadata, nil
}
func generateVersionFromBuildID() (string, error) {
buildID, ok := os.LookupEnv("DRONE_BUILD_NUMBER")
if !ok {
return "", fmt.Errorf("unable to get DRONE_BUILD_NUMBER environmental variable")
}
var err error
version, err := GetGrafanaVersion(buildID, ".")
if err != nil {
return "", err
}
return version, nil
}

View File

@@ -0,0 +1,81 @@
package config
import (
"flag"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
const (
DroneBuildEvent = "DRONE_BUILD_EVENT"
DroneTargetBranch = "DRONE_TARGET_BRANCH"
DroneTag = "DRONE_TAG"
DroneSemverPrerelease = "DRONE_SEMVER_PRERELEASE"
DroneBuildNumber = "DRONE_BUILD_NUMBER"
)
const (
hashedGrafanaVersion = "9.2.0-12345pre"
versionedBranch = "v9.2.x"
)
func TestGetMetadata(t *testing.T) {
tcs := []struct {
envMap map[string]string
expVersion string
mode ReleaseMode
}{
{map[string]string{DroneBuildEvent: PullRequest, DroneTargetBranch: "", DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: PullRequestMode}},
{map[string]string{DroneBuildEvent: Push, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}},
{map[string]string{DroneBuildEvent: Push, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: MainMode}},
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: versionedBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: ReleaseBranchMode}},
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, hashedGrafanaVersion, ReleaseMode{Mode: DownstreamMode}},
{map[string]string{DroneBuildEvent: Custom, DroneTargetBranch: MainBranch, DroneTag: "", DroneSemverPrerelease: "", DroneBuildNumber: "12345", "EDITION": string(EditionEnterprise2)}, hashedGrafanaVersion, ReleaseMode{Mode: Enterprise2Mode}},
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsBeta: false, IsTest: false}},
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-beta", DroneSemverPrerelease: "beta", DroneBuildNumber: "12345"}, "9.2.0-beta", ReleaseMode{Mode: TagMode, IsBeta: true, IsTest: false}},
{map[string]string{DroneBuildEvent: Tag, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsBeta: false, IsTest: true}},
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0", DroneSemverPrerelease: "", DroneBuildNumber: "12345"}, "9.2.0", ReleaseMode{Mode: TagMode, IsBeta: false, IsTest: false}},
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-beta", DroneSemverPrerelease: "beta", DroneBuildNumber: "12345"}, "9.2.0-beta", ReleaseMode{Mode: TagMode, IsBeta: true, IsTest: false}},
{map[string]string{DroneBuildEvent: Promote, DroneTargetBranch: "", DroneTag: "v9.2.0-test", DroneSemverPrerelease: "test", DroneBuildNumber: "12345"}, "9.2.0-test", ReleaseMode{Mode: TagMode, IsBeta: false, IsTest: true}},
}
ctx := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)
for _, tc := range tcs {
t.Run("Should return valid metadata, ", func(t *testing.T) {
setUpEnv(t, tc.envMap)
testMetadata(t, ctx, tc.expVersion, tc.mode)
})
}
}
func testMetadata(t *testing.T, ctx *cli.Context, version string, releaseMode ReleaseMode) {
t.Helper()
metadata, err := GenerateMetadata(ctx)
require.NoError(t, err)
t.Run("with a valid version", func(t *testing.T) {
expVersion := metadata.GrafanaVersion
require.Equal(t, expVersion, version)
})
t.Run("with a valid release mode from the built-in list", func(t *testing.T) {
expMode := metadata.ReleaseMode
require.NoError(t, err)
require.Equal(t, expMode, releaseMode)
})
}
func setUpEnv(t *testing.T, envMap map[string]string) {
t.Helper()
os.Clearenv()
err := os.Setenv("DRONE_COMMIT", "abcd12345")
require.NoError(t, err)
for k, v := range envMap {
err := os.Setenv(k, v)
require.NoError(t, err)
}
}

View File

@@ -0,0 +1,3 @@
{
"version": "9.2.0-pre"
}

View File

@@ -7,6 +7,8 @@ import (
"path/filepath"
"regexp"
"strings"
"github.com/grafana/grafana/pkg/build/git"
)
type Metadata struct {
@@ -100,8 +102,8 @@ func GetGrafanaVersion(buildID, grafanaDir string) (string, error) {
}
func CheckDroneTargetBranch() (VersionMode, error) {
rePRCheckBranch := git.PRCheckRegexp()
reRlsBranch := regexp.MustCompile(`^v\d+\.\d+\.x$`)
rePRCheckBranch := regexp.MustCompile(`^pr-check-\d+`)
target := os.Getenv("DRONE_TARGET_BRANCH")
if target == "" {
return "", fmt.Errorf("failed to get DRONE_TARGET_BRANCH environmental variable")

View File

@@ -8,7 +8,8 @@ const (
TagMode VersionMode = "release"
ReleaseBranchMode VersionMode = "branch"
PullRequestMode VersionMode = "pull_request"
CustomMode VersionMode = "custom"
DownstreamMode VersionMode = "downstream"
Enterprise2Mode VersionMode = "enterprise2"
CronjobMode VersionMode = "cron"
)

View File

@@ -59,7 +59,7 @@ var Versions = VersionMap{
Storybook: "grafana-storybook",
},
},
CustomMode: {
DownstreamMode: {
Variants: []Variant{
VariantArmV6,
VariantArmV7,
@@ -165,4 +165,42 @@ var Versions = VersionMap{
StorybookSrcDir: "artifacts/storybook",
},
},
Enterprise2Mode: {
Variants: []Variant{
VariantArmV6,
VariantArmV7,
VariantArmV7Musl,
VariantArm64,
VariantArm64Musl,
VariantDarwinAmd64,
VariantWindowsAmd64,
VariantLinuxAmd64,
VariantLinuxAmd64Musl,
},
PluginSignature: PluginSignature{
Sign: true,
AdminSign: true,
},
Docker: Docker{
ShouldSave: true,
Architectures: []Architecture{
ArchAMD64,
ArchARM64,
ArchARMv7,
},
Distribution: []Distribution{
Alpine,
Ubuntu,
},
PrereleaseBucket: "grafana-prerelease/artifacts/docker",
},
Buckets: Buckets{
Artifacts: "grafana-prerelease/artifacts/downloads",
ArtifactsEnterprise2: "grafana-prerelease/artifacts/downloads-enterprise2",
CDNAssets: "grafana-prerelease",
CDNAssetsDir: "artifacts/static-assets",
Storybook: "grafana-prerelease",
StorybookSrcDir: "artifacts/storybook",
},
},
}

View File

@@ -85,6 +85,11 @@ func BuildImage(version string, arch config.Architecture, grafanaDir string, use
var additionalDockerRepo string
var tags []string
var imageFileBase string
var dockerEnterprise2Repo string
if repo, ok := os.LookupEnv("DOCKER_ENTERPRISE2_REPO"); ok {
dockerEnterprise2Repo = repo
}
switch edition {
case config.EditionOSS:
dockerRepo = "grafana/grafana-image-tags"
@@ -94,6 +99,10 @@ func BuildImage(version string, arch config.Architecture, grafanaDir string, use
dockerRepo = "grafana/grafana-enterprise-image-tags"
imageFileBase = "grafana-enterprise"
editionStr = "-enterprise"
case config.EditionEnterprise2:
dockerRepo = dockerEnterprise2Repo
imageFileBase = "grafana-enterprise2"
editionStr = "-enterprise2"
default:
return []string{}, fmt.Errorf("unrecognized edition %s", edition)
}

62
pkg/build/docker/push.go Normal file
View File

@@ -0,0 +1,62 @@
package docker
import (
"fmt"
"log"
"os"
"os/exec"
"time"
)
const (
tries = 3
sleepTime = 30
)
func PushImage(newImage string) error {
var err error
for i := 0; i < tries; i++ {
log.Printf("push attempt #%d...", i+1)
var out []byte
cmd := exec.Command("docker", "push", newImage)
cmd.Dir = "."
out, err = cmd.CombinedOutput()
if err != nil {
log.Printf("output: %s", out)
log.Printf("sleep for %d, before retrying...", sleepTime)
time.Sleep(sleepTime * time.Second)
} else {
log.Printf("Successfully pushed %s!", newImage)
break
}
}
if err != nil {
return fmt.Errorf("error pushing images to DockerHub: %q", err)
}
return nil
}
func PushManifest(manifest string) error {
log.Printf("Pushing Docker manifest %s...", manifest)
var err error
for i := 0; i < tries; i++ {
log.Printf("push attempt #%d...", i+1)
var out []byte
cmd := exec.Command("docker", "manifest", "push", manifest)
cmd.Env = append(os.Environ(), "DOCKER_CLI_EXPERIMENTAL=enabled")
out, err = cmd.CombinedOutput()
if err != nil {
log.Printf("output: %s", out)
log.Printf("sleep for %d, before retrying...", sleepTime)
time.Sleep(sleepTime * time.Second)
} else {
log.Printf("Successful manifest push! %s", string(out))
break
}
}
if err != nil {
return fmt.Errorf("failed to push manifest, err: %w", err)
}
return nil
}

18
pkg/build/env/lookup.go vendored Normal file
View File

@@ -0,0 +1,18 @@
package env
import (
"strings"
)
// Lookup is the equivalent of os.LookupEnv, only you are able to provide the list of environment variables.
// To use this as os.LookupEnv would be used, simply call
// `env.Lookup("ENVIRONMENT_VARIABLE", os.Environ())`
func Lookup(name string, vars []string) (string, bool) {
for _, v := range vars {
if strings.HasPrefix(v, name) {
return strings.TrimPrefix(v, name+"="), true
}
}
return "", false
}

43
pkg/build/env/lookup_test.go vendored Normal file
View File

@@ -0,0 +1,43 @@
package env_test
import (
"testing"
"github.com/grafana/grafana/pkg/build/env"
"github.com/stretchr/testify/require"
)
func TestLookup(t *testing.T) {
values := []string{"ENV_1=a", "ENV_2=b", "ENV_3=c", "ENV_4_TEST="}
{
v, ok := env.Lookup("ENV_1", values)
require.Equal(t, v, "a")
require.True(t, ok)
}
{
v, ok := env.Lookup("ENV_2", values)
require.Equal(t, v, "b")
require.True(t, ok)
}
{
v, ok := env.Lookup("ENV_3", values)
require.Equal(t, v, "c")
require.True(t, ok)
}
{
v, ok := env.Lookup("ENV_4_TEST", values)
require.Equal(t, v, "")
require.True(t, ok)
}
{
v, ok := env.Lookup("NOT_THERE", values)
require.Equal(t, v, "")
require.False(t, ok)
}
}

142
pkg/build/git/git.go Normal file
View File

@@ -0,0 +1,142 @@
package git
import (
"context"
"errors"
"fmt"
"net/http"
"regexp"
"github.com/google/go-github/v45/github"
"github.com/grafana/grafana/pkg/build/stringutil"
"golang.org/x/oauth2"
)
const (
MainBranch = "main"
HomeDir = "."
RepoOwner = "grafana"
OSSRepo = "grafana"
EnterpriseRepo = "grafana-enterprise"
EnterpriseCheckName = "Grafana Enterprise"
EnterpriseCheckDescription = "Downstream tests to ensure that your changes are compatible with Grafana Enterprise"
)
var EnterpriseCheckLabels = []string{"enterprise-ok", "enterprise-failed", "enterprise-override"}
var (
ErrorNoDroneBuildLink = errors.New("no drone build link")
)
type GitService interface {
DeleteRef(ctx context.Context, owner string, repo string, ref string) (*github.Response, error)
}
type LabelsService interface {
ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error)
RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error)
AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error)
}
type CommentService interface {
CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
}
type StatusesService interface {
CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error)
}
// NewGitHubClient creates a new Client using the provided GitHub token if not empty.
func NewGitHubClient(ctx context.Context, token string) *github.Client {
var tc *http.Client
if token != "" {
ts := oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: token,
})
tc = oauth2.NewClient(ctx, ts)
}
return github.NewClient(tc)
}
func PRCheckRegexp() *regexp.Regexp {
reBranch, err := regexp.Compile(`^prc-([0-9]+)-([A-Za-z0-9]+)\/(.+)$`)
if err != nil {
panic(fmt.Sprintf("Failed to compile regexp: %s", err))
}
return reBranch
}
func AddLabelToPR(ctx context.Context, client LabelsService, prID int, newLabel string) error {
// Check existing labels
labels, _, err := client.ListLabelsByIssue(ctx, RepoOwner, OSSRepo, prID, nil)
if err != nil {
return err
}
duplicate := false
for _, label := range labels {
if *label.Name == newLabel {
duplicate = true
continue
}
// Delete existing "enterprise-xx" labels
if stringutil.Contains(EnterpriseCheckLabels, *label.Name) {
_, err := client.RemoveLabelForIssue(ctx, RepoOwner, OSSRepo, prID, *label.Name)
if err != nil {
return err
}
}
}
if duplicate {
return nil
}
_, _, err = client.AddLabelsToIssue(ctx, RepoOwner, OSSRepo, prID, []string{newLabel})
if err != nil {
return err
}
return nil
}
func DeleteEnterpriseBranch(ctx context.Context, client GitService, branchName string) error {
ref := "heads/" + branchName
if _, err := client.DeleteRef(ctx, RepoOwner, EnterpriseRepo, ref); err != nil {
return err
}
return nil
}
// CreateEnterpriseStatus sets the status on a commit for the enterprise build check.
func CreateEnterpriseStatus(ctx context.Context, client StatusesService, sha, link, status string) (*github.RepoStatus, error) {
check, _, err := client.CreateStatus(ctx, RepoOwner, OSSRepo, sha, &github.RepoStatus{
Context: github.String(EnterpriseCheckName),
Description: github.String(EnterpriseCheckDescription),
TargetURL: github.String(link),
State: github.String(status),
})
if err != nil {
return nil, err
}
return check, nil
}
func CreateEnterpriseBuildFailedComment(ctx context.Context, client CommentService, link string, prID int) error {
body := fmt.Sprintf("Drone build failed: %s", link)
_, _, err := client.CreateComment(ctx, RepoOwner, OSSRepo, prID, &github.IssueComment{
Body: &body,
})
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,55 @@
package git_test
import (
"context"
"errors"
"testing"
"github.com/google/go-github/v45/github"
"github.com/grafana/grafana/pkg/build/git"
"github.com/stretchr/testify/require"
)
type TestChecksService struct {
CreateCheckRunError error
}
func (s *TestChecksService) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) {
if s.CreateCheckRunError != nil {
return nil, nil, s.CreateCheckRunError
}
return &github.RepoStatus{
ID: github.Int64(1),
URL: status.URL,
}, nil, nil
}
func TestCreateEnterpriseRepoStatus(t *testing.T) {
t.Run("It should create a repo status", func(t *testing.T) {
var (
ctx = context.Background()
client = &TestChecksService{}
link = "http://example.com"
sha = "1234"
)
_, err := git.CreateEnterpriseStatus(ctx, client, link, sha, "success")
require.NoError(t, err)
})
t.Run("It should return an error if GitHub fails to create the status", func(t *testing.T) {
var (
ctx = context.Background()
createCheckError = errors.New("create check run error")
client = &TestChecksService{
CreateCheckRunError: createCheckError,
}
link = "http://example.com"
sha = "1234"
)
_, err := git.CreateEnterpriseStatus(ctx, client, link, sha, "success")
require.ErrorIs(t, err, createCheckError)
})
}

View File

@@ -0,0 +1,134 @@
package git_test
import (
"context"
"errors"
"testing"
"github.com/google/go-github/v45/github"
"github.com/grafana/grafana/pkg/build/git"
"github.com/stretchr/testify/require"
)
type TestLabelsService struct {
Labels []*github.Label
ListLabelsError error
RemoveLabelError error
AddLabelsError error
}
func (s *TestLabelsService) ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error) {
if s.ListLabelsError != nil {
return nil, nil, s.ListLabelsError
}
labels := s.Labels
if labels == nil {
labels = []*github.Label{}
}
return labels, nil, nil
}
func (s *TestLabelsService) RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error) {
if s.RemoveLabelError != nil {
return nil, s.RemoveLabelError
}
return &github.Response{}, nil
}
func (s *TestLabelsService) AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error) {
if s.AddLabelsError != nil {
return nil, nil, s.AddLabelsError
}
l := make([]*github.Label, len(labels))
for i, v := range labels {
l[i] = &github.Label{
Name: github.String(v),
}
}
return l, nil, nil
}
func TestAddLabelToPR(t *testing.T) {
t.Run("It should add a label to a pull request", func(t *testing.T) {
var (
ctx = context.Background()
client = &TestLabelsService{}
pr = 20
label = "test-label"
)
require.NoError(t, git.AddLabelToPR(ctx, client, pr, label))
})
t.Run("It should not return an error if the label already exists", func(t *testing.T) {
var (
ctx = context.Background()
client = &TestLabelsService{
Labels: []*github.Label{
{
Name: github.String("test-label"),
},
},
}
pr = 20
label = "test-label"
)
require.NoError(t, git.AddLabelToPR(ctx, client, pr, label))
})
t.Run("It should return an error if GitHub returns an error when listing labels", func(t *testing.T) {
var (
ctx = context.Background()
listLabelsError = errors.New("list labels error")
client = &TestLabelsService{
ListLabelsError: listLabelsError,
Labels: []*github.Label{},
}
pr = 20
label = "test-label"
)
require.ErrorIs(t, git.AddLabelToPR(ctx, client, pr, label), listLabelsError)
})
t.Run("It should not return an error if there are existing enterprise-check labels.", func(t *testing.T) {
var (
ctx = context.Background()
client = &TestLabelsService{
Labels: []*github.Label{
{
Name: github.String("enterprise-failed"),
},
},
}
pr = 20
label = "test-label"
)
require.NoError(t, git.AddLabelToPR(ctx, client, pr, label))
})
t.Run("It should return an error if GitHub returns an error when removing existing enterprise-check labels", func(t *testing.T) {
var (
ctx = context.Background()
removeLabelError = errors.New("remove label error")
client = &TestLabelsService{
RemoveLabelError: removeLabelError,
Labels: []*github.Label{
{
Name: github.String("enterprise-failed"),
},
},
}
pr = 20
label = "test-label"
)
require.ErrorIs(t, git.AddLabelToPR(ctx, client, pr, label), removeLabelError)
})
}

56
pkg/build/git/git_test.go Normal file
View File

@@ -0,0 +1,56 @@
package git_test
import (
"testing"
"github.com/grafana/grafana/pkg/build/git"
"github.com/stretchr/testify/assert"
)
func TestPRCheckRegexp(t *testing.T) {
type match struct {
String string
Commit string
Branch string
PR string
}
var (
shouldMatch = []match{
{
String: "prc-1-a1b2c3d4/branch-name",
Branch: "branch-name",
Commit: "a1b2c3d4",
PR: "1",
},
{
String: "prc-111-a1b2c3d4/branch/name",
Branch: "branch/name",
Commit: "a1b2c3d4",
PR: "111",
},
{
String: "prc-102930122-a1b2c3d4/branch-name",
Branch: "branch-name",
Commit: "a1b2c3d4",
PR: "102930122",
},
}
shouldNotMatch = []string{"prc-a/branch", "km/test", "test", "prc", "prc/test", "price"}
)
regex := git.PRCheckRegexp()
for _, v := range shouldMatch {
assert.Truef(t, regex.MatchString(v.String), "regex '%s' should match %s", regex.String(), v)
m := regex.FindStringSubmatch(v.String)
assert.Equal(t, m[1], v.PR)
assert.Equal(t, m[2], v.Commit)
assert.Equal(t, m[3], v.Branch)
}
for _, v := range shouldNotMatch {
assert.False(t, regex.MatchString(v), "regex '%s' should not match %s", regex.String(), v)
}
}

View File

@@ -1,6 +1,7 @@
package lerna
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -9,6 +10,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/fsutil"
)
// BuildFrontendPackages will bump the version for the package to the latest canary build
@@ -56,3 +58,29 @@ func GetLernaVersion(grafanaDir string) (string, error) {
}
return strings.TrimSpace(version), nil
}
func PackFrontendPackages(ctx context.Context, tag, grafanaDir, artifactsDir string) error {
exists, err := fsutil.Exists(artifactsDir)
if err != nil {
return err
}
if exists {
err = os.RemoveAll(artifactsDir)
if err != nil {
return err
}
}
// nolint:gosec
if err = os.MkdirAll(artifactsDir, 0755); err != nil {
return err
}
// nolint:gosec
cmd := exec.CommandContext(ctx, "yarn", "lerna", "exec", "--no-private", "--", "yarn", "pack", "--out", fmt.Sprintf("../../npm-artifacts/%%s-%v.tgz", tag))
cmd.Dir = grafanaDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), output, err)
}
return nil
}

240
pkg/build/npm/npm.go Normal file
View File

@@ -0,0 +1,240 @@
package npm
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/grafana/grafana/pkg/build/lerna"
"github.com/grafana/grafana/pkg/build/versions"
)
const GrafanaDir = "."
const NpmArtifactDir = "./npm-artifacts"
// TODO: could this be replaced by `yarn lerna list -p` ?
var packages = []string{
"@grafana/ui",
"@grafana/data",
"@grafana/toolkit",
"@grafana/runtime",
"@grafana/e2e",
"@grafana/e2e-selectors",
"@grafana/schema",
}
// PublishNpmPackages will publish local NPM packages to NPM registry.
func PublishNpmPackages(ctx context.Context, tag string) error {
version, err := versions.GetVersion(tag)
if err != nil {
return err
}
log.Printf("Grafana version: %s", version.Version)
if err := setNpmCredentials(); err != nil {
return err
}
npmArtifacts, err := storage.ListLocalFiles(NpmArtifactDir)
if err != nil {
return err
}
for _, packedFile := range npmArtifacts {
// nolint:gosec
cmd := exec.CommandContext(ctx, "npm", "publish", packedFile.FullPath, "--tag", version.Channel)
cmd.Dir = GrafanaDir
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err)
}
}
return updateTag(ctx, version, tag)
}
// StoreNpmPackages will store local NPM packages in GCS bucket `bucketName`.
func StoreNpmPackages(ctx context.Context, tag, bucketName string) error {
err := lerna.PackFrontendPackages(ctx, tag, GrafanaDir, NpmArtifactDir)
if err != nil {
return err
}
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(bucketName)
bucketPath := fmt.Sprintf("artifacts/npm/%s/", tag)
if err = gcs.CopyLocalDir(ctx, NpmArtifactDir, bucket, bucketPath, true); err != nil {
return err
}
log.Print("Successfully stored npm packages!")
return nil
}
// FetchNpmPackages will store NPM packages stored in GCS bucket `bucketName` on local disk in `frontend.NpmArtifactDir`.
func FetchNpmPackages(ctx context.Context, tag, bucketName string) error {
gcs, err := storage.New()
if err != nil {
return err
}
bucketPath := fmt.Sprintf("artifacts/npm/%s/", tag)
bucket := gcs.Bucket(bucketName)
err = gcs.DownloadDirectory(ctx, bucket, NpmArtifactDir, storage.FilesFilter{
Prefix: bucketPath,
FileExts: []string{".tgz"},
})
if err != nil {
return err
}
return nil
}
// updateTag will move next or latest npm dist-tags, if needed.
//
// Note: This function makes the assumption that npm dist-tags has already
// been updated and hence why move of dist-tags not always happens:
//
// If stable the dist-tag latest was used.
// If beta the dist-tag next was used.
//
// Scenarios:
//
// 1. Releasing a newer stable than the current stable
// Latest and next is 9.1.5.
// 9.1.6 is released, latest and next should point to 9.1.6.
// The next dist-tag is moved to point to 9.1.6.
//
// 2. Releasing during an active beta period:
// Latest and next is 9.1.6.
// 9.2.0-beta1 is released, the latest should stay on 9.1.6, next should point to 9.2.0-beta1
// No move of dist-tags
// 9.1.7 is relased, the latest should point to 9.1.7, next should stay to 9.2.0-beta1
// No move of dist-tags
// Next week 9.2.0-beta2 is released, the latest should point to 9.1.7, next should point to 9.2.0-beta2
// No move of dist-tags
// In two weeks 9.2.0 stable is relased, the latest and next should point to 9.2.0.
// The next dist-tag is moved to point to 9.2.0.
//
// 3. Releasing an older stable than the current stable
// Latest and next is 9.2.0.
// Next 9.1.8 is released, latest should point to 9.2.0, next should point to 9.2.0
// The latest dist-tag is moved to point to 9.2.0.
func updateTag(ctx context.Context, version *versions.Version, releaseVersion string) error {
if version.Channel != versions.Latest {
return nil
}
latestStableVersion, err := getLatestStableVersion()
if err != nil {
return err
}
betaVersion, err := getLatestBetaVersion()
if err != nil {
return err
}
isLatest, err := versions.IsGreaterThanOrEqual(releaseVersion, latestStableVersion)
if err != nil {
return err
}
isNewerThanLatestBeta, err := versions.IsGreaterThanOrEqual(releaseVersion, betaVersion)
if err != nil {
return err
}
for _, pkg := range packages {
if !isLatest {
err = runMoveLatestNPMTagCommand(ctx, pkg, latestStableVersion)
if err != nil {
return err
}
}
if isLatest && isNewerThanLatestBeta {
err = runMoveNextNPMTagCommand(ctx, pkg, version.Version)
if err != nil {
return err
}
}
}
return nil
}
func getLatestStableVersion() (string, error) {
return versions.GetLatestVersion(versions.LatestStableVersionURL)
}
func getLatestBetaVersion() (string, error) {
return versions.GetLatestVersion(versions.LatestBetaVersionURL)
}
func runMoveNextNPMTagCommand(ctx context.Context, pkg string, packageVersion string) error {
// nolint:gosec
cmd := exec.CommandContext(ctx, "npm", "dist-tag", "add", fmt.Sprintf("%s@%s", pkg, packageVersion), "next")
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err)
}
return nil
}
func runMoveLatestNPMTagCommand(ctx context.Context, pkg string, latestStableVersion string) error {
// nolint:gosec
cmd := exec.CommandContext(ctx, "npm", "dist-tag", "add", fmt.Sprintf("%s@%s", pkg, latestStableVersion), "latest")
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("command '%s' failed to run, output: %s, err: %q", cmd.String(), out, err)
}
return nil
}
// setNpmCredentials Creates a .npmrc file in the users home folder and writes the
// necessary credentials to it for publishing packages to the NPM registry.
func setNpmCredentials() error {
npmToken := strings.TrimSpace(os.Getenv("NPM_TOKEN"))
if npmToken == "" {
return fmt.Errorf("npm token is not set")
}
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to obtain home directory, err: %q", err)
}
npmPath := filepath.Join(homeDir, ".npmrc")
registry := []byte(fmt.Sprintf("//registry.npmjs.org/:_authToken=%s", npmToken))
if _, err = os.Stat(npmPath); os.IsNotExist(err) {
// nolint:gosec
f, err := os.Create(npmPath)
if err != nil {
return fmt.Errorf("couldn't create npmrc file, err: %q", err)
}
_, err = f.Write(registry)
if err != nil {
return fmt.Errorf("failed to write to file, err: %q", err)
}
defer func() {
if err := f.Close(); err != nil {
log.Printf("Failed to close file: %s", err.Error())
}
}()
} else {
err = os.WriteFile(npmPath, registry, 0644)
if err != nil {
return fmt.Errorf("error writing to file, err: %q", err)
}
}
return nil
}

View File

@@ -8,10 +8,11 @@ import (
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/fsutil"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/urfave/cli/v2"
)
func writeAptlyConf(dbDir, repoDir string) error {
@@ -166,7 +167,7 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
cmd := exec.Command("aptly", "publish", "update", "-batch", passArg, "-force-overwrite", tp,
"filesystem:repo:grafana")
if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to update Debian %q repository: %s", tp, output), 1)
return cli.Exit(fmt.Sprintf("failed to update Debian %q repository: %s", tp, output), 1)
}
}
@@ -179,7 +180,7 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
//nolint:gosec
cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", dbDir, u)
if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to upload Debian repo database to GCS: %s", output), 1)
return cli.Exit(fmt.Sprintf("failed to upload Debian repo database to GCS: %s", output), 1)
}
}
@@ -193,14 +194,14 @@ func UpdateDebRepo(cfg PublishConfig, workDir string) error {
//nolint:gosec
cmd = exec.Command("gsutil", "-m", "rsync", "-r", "-d", grafDir, u)
if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to upload Debian repo resources to GCS: %s", output), 1)
return cli.Exit(fmt.Sprintf("failed to upload Debian repo resources to GCS: %s", output), 1)
}
allRepoResources := fmt.Sprintf("%s/**/*", u)
log.Printf("Setting cache ttl for Debian repo resources on GCS (%s)...\n", allRepoResources)
//nolint:gosec
cmd = exec.Command("gsutil", "-m", "setmeta", "-h", CacheSettings+cfg.TTL, allRepoResources)
if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to set cache ttl for Debian repo resources on GCS: %s", output), 1)
return cli.Exit(fmt.Sprintf("failed to set cache ttl for Debian repo resources on GCS: %s", output), 1)
}
}
@@ -238,7 +239,7 @@ func addPkgsToRepo(cfg PublishConfig, workDir, tmpDir, repoName string) error {
//nolint:gosec
cmd := exec.Command("aptly", "repo", "add", "-force-replace", repoName, tmpDir)
if output, err := cmd.CombinedOutput(); err != nil {
return cli.NewExitError(fmt.Sprintf("failed to add packages to local Debian repository: %s", output), 1)
return cli.Exit(fmt.Sprintf("failed to add packages to local Debian repository: %s", output), 1)
}
return nil

View File

@@ -0,0 +1,22 @@
package packaging_test
import (
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/packaging"
"github.com/stretchr/testify/assert"
)
func TestPackageRegexp(t *testing.T) {
t.Run("It should match enterprise2 packages", func(t *testing.T) {
rgx := packaging.PackageRegexp(config.EditionEnterprise2)
matches := []string{
"grafana-enterprise2-1.2.3-4567pre.linux-amd64.tar.gz",
"grafana-enterprise2-1.2.3-4567pre.linux-amd64.tar.gz.sha256",
}
for _, v := range matches {
assert.Truef(t, rgx.MatchString(v), "'%s' should match regex '%s'", v, rgx.String())
}
})
}

View File

@@ -10,12 +10,15 @@ import (
"path/filepath"
"strings"
// Consider switching this over to a community fork unless there is
// an option to move us away from OpenPGP.
"golang.org/x/crypto/openpgp" //nolint:staticcheck
"golang.org/x/crypto/openpgp/armor" //nolint:staticcheck
"golang.org/x/crypto/openpgp/packet" //nolint:staticcheck
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/fsutil"
"github.com/grafana/grafana/pkg/infra/fs"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
// UpdateRPMRepo updates the RPM repository with the new release.

View File

@@ -0,0 +1,10 @@
package stringutil
func Contains(arr []string, s string) bool {
for _, e := range arr {
if e == s {
return true
}
}
return false
}

View File

@@ -0,0 +1,160 @@
package versions
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"regexp"
"strconv"
"github.com/Masterminds/semver/v3"
)
var (
reGrafanaTag = regexp.MustCompile(`^v(\d+\.\d+\.\d+$)`)
reGrafanaTagBeta = regexp.MustCompile(`^v(\d+\.\d+\.\d+-beta)`)
reGrafanaTagCustom = regexp.MustCompile(`^v(\d+\.\d+\.\d+-\w+)`)
)
const (
Latest = "latest"
Next = "next"
Test = "test"
)
type Version struct {
Version string
Channel string
}
type VersionFromAPI struct {
Version string `json:"version"`
}
type LatestGcomAPI = string
const (
LatestStableVersionURL LatestGcomAPI = "https://grafana.com/api/grafana/versions/stable"
LatestBetaVersionURL LatestGcomAPI = "https://grafana.com/api/grafana/versions/beta"
)
func GetLatestVersion(url LatestGcomAPI) (string, error) {
// nolint:gosec
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Printf("Failed to close body: %s", err.Error())
}
}()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("server returned non 200 status code: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var apiResponse VersionFromAPI
err = json.Unmarshal(body, &apiResponse)
if err != nil {
return "", err
}
return apiResponse.Version, nil
}
// IsGreaterThanOrEqual semantically checks whether newVersion is greater than or equal to stableVersion.
func IsGreaterThanOrEqual(newVersion, stableVersion string) (bool, error) {
v1SemVer, err := semver.NewVersion(newVersion)
if err != nil {
return isGreaterThanOrEqualFourDigit(newVersion, stableVersion)
}
v2SemVer, err := semver.NewVersion(stableVersion)
if err != nil {
return isGreaterThanOrEqualFourDigit(newVersion, stableVersion)
}
comp := v1SemVer.Compare(v2SemVer)
switch comp {
case -1:
return false, nil
case 1, 0:
return true, nil
default:
return true, fmt.Errorf("unknown comparison value between scemantic versions, err: %q", err)
}
}
var fourDigitRe = regexp.MustCompile(`(\d+\.\d+\.\d+)\.(\d+)`)
func parseFourDigit(version string) (*semver.Version, int, error) {
matches := fourDigitRe.FindStringSubmatch(version)
if len(matches) < 2 {
semVer, err := semver.NewVersion(version)
if err != nil {
return nil, 0, err
}
return semVer, 0, nil
}
semVer, err := semver.NewVersion(matches[1])
if err != nil {
return nil, 0, err
}
i, err := strconv.Atoi(matches[2])
if err != nil {
return nil, 0, err
}
return semVer, i, nil
}
func isGreaterThanOrEqualFourDigit(newVersion, stableVersion string) (bool, error) {
newVersionSemVer, newVersionSemVerNo, err := parseFourDigit(newVersion)
if err != nil {
return false, err
}
stableVersionSemVer, stableVersionSemVerNo, err := parseFourDigit(stableVersion)
if err != nil {
return false, err
}
if stableVersionSemVer.Original() != newVersionSemVer.Original() {
return IsGreaterThanOrEqual(newVersionSemVer.Original(), stableVersionSemVer.Original())
}
return newVersionSemVerNo >= stableVersionSemVerNo, nil
}
func GetVersion(tag string) (*Version, error) {
var version Version
switch {
case reGrafanaTag.MatchString(tag):
version = Version{
Version: reGrafanaTag.FindStringSubmatch(tag)[1],
Channel: Latest,
}
case reGrafanaTagBeta.MatchString(tag):
version = Version{
Version: reGrafanaTagBeta.FindStringSubmatch(tag)[1],
Channel: Next,
}
case reGrafanaTagCustom.MatchString(tag):
version = Version{
Version: reGrafanaTagCustom.FindStringSubmatch(tag)[1],
Channel: Test,
}
default:
return nil, fmt.Errorf("%s not a supported Grafana version, exitting", tag)
}
return &version, nil
}

View File

@@ -0,0 +1,69 @@
package versions
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func TestIsGreaterThanOrEqual(t *testing.T) {
testCases := []struct {
newVersion string
stableVersion string
expected bool
}{
{newVersion: "9.0.0", stableVersion: "8.0.0", expected: true},
{newVersion: "6.0.0", stableVersion: "6.0.0", expected: true},
{newVersion: "7.0.0", stableVersion: "8.0.0", expected: false},
{newVersion: "8.5.0-beta1", stableVersion: "8.0.0", expected: true},
{newVersion: "8.5.0", stableVersion: "8.5.0-beta1", expected: true},
{newVersion: "9.0.0.1", stableVersion: "9.0.0", expected: true},
{newVersion: "9.0.0.2", stableVersion: "9.0.0.1", expected: true},
{newVersion: "9.1.0", stableVersion: "9.0.0.1", expected: true},
{newVersion: "9.1-0-beta1", stableVersion: "9.0.0.1", expected: true},
{newVersion: "9.0.0.1", stableVersion: "9.0.1.1", expected: false},
{newVersion: "9.0.1.1", stableVersion: "9.0.0.1", expected: true},
{newVersion: "9.0.0.1", stableVersion: "9.0.0.1", expected: true},
{newVersion: "7.0.0.1", stableVersion: "8.0.0", expected: false},
{newVersion: "9.1-0-beta1", stableVersion: "9.1-0-beta2", expected: false},
{newVersion: "9.1-0-beta3", stableVersion: "9.1-0-beta2", expected: true},
}
for _, tc := range testCases {
name := fmt.Sprintf("newVersion %s greater than or equal stableVersion %s = %v", tc.newVersion, tc.stableVersion, tc.expected)
t.Run(name, func(t *testing.T) {
result, err := IsGreaterThanOrEqual(tc.newVersion, tc.stableVersion)
require.NoError(t, err)
require.Equal(t, tc.expected, result)
})
}
}
func TestGetLatestVersion(t *testing.T) {
t.Run("it returns a version", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
response := VersionFromAPI{
Version: "8.4.0",
}
jsonRes, err := json.Marshal(&response)
require.NoError(t, err)
_, err = w.Write(jsonRes)
require.NoError(t, err)
}))
version, err := GetLatestVersion(server.URL)
require.NoError(t, err)
require.Equal(t, "8.4.0", version)
})
t.Run("it handles non 200 responses", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
_, err := GetLatestVersion(server.URL)
require.Error(t, err)
})
}

View File

@@ -75,6 +75,7 @@ beforeEach(() => {
mockPanel = new PanelModel({
id: 'mockPanelId',
timezone: 'utc',
});
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
@@ -145,7 +146,7 @@ describe('SharePublic', () => {
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
await screen.findByText('Welcome to Grafana public dashboards alpha!');
expect(screen.getByText('2022-08-30 00:00:00 to 2022-09-04 01:59:59')).toBeInTheDocument();
expect(screen.getByText('2022-08-30 00:00:00 to 2022-09-04 00:59:59')).toBeInTheDocument();
});
it('when modal is opened, then loader spinner appears and inputs are disabled', async () => {
mockDashboard.meta.hasPublicDashboard = true;

628
scripts/drone/TAGS Normal file
View File

@@ -0,0 +1,628 @@
events/release.star,6652
ver_mode =ver_mode64,1602
release_trigger =release_trigger65,1623
def store_npm_packages_step():store_npm_packages_step74,1752
def retrieve_npm_packages_step():retrieve_npm_packages_step90,2193
def release_npm_packages_step():release_npm_packages_step107,2663
def oss_pipelines(ver_mode = ver_mode, trigger = release_trigger):oss_pipelines123,3076
environment =environment135,3492
edition =edition136,3529
services =services137,3549
volumes =volumes138,3609
package_steps =package_steps139,3659
publish_steps =publish_steps140,3682
should_publish =should_publish141,3705
should_upload =should_upload142,3748
init_steps =init_steps143,3818
build_steps =build_steps152,4033
integration_test_steps =integration_test_steps159,4342
build_storybook =build_storybook182,5254
publish_step =publish_step190,5674
store_npm_step =store_npm_step191,5758
windows_package_steps =windows_package_steps196,5957
windows_pipeline =windows_pipeline198,6044
name =name199,6077
edition =edition200,6127
trigger =trigger201,6154
steps =steps202,6181
platform =platform203,6256
depends_on =depends_on204,6286
environment =environment207,6393
pipelines =pipelines209,6434
name =name211,6470
edition =edition212,6550
trigger =trigger213,6581
services =services214,6612
steps =steps215,6639
environment =environment216,6717
volumes =volumes217,6756
name =name225,6970
edition =edition226,7038
trigger =trigger227,7073
services =services228,7108
steps =steps229,7145
environment =environment230,7329
volumes =volumes231,7372
deps =deps234,7433
def enterprise_pipelines(ver_mode = ver_mode, trigger = release_trigger):enterprise_pipelines247,7856
environment =environment259,8284
edition =edition260,8328
services =services261,8355
volumes =volumes262,8415
package_steps =package_steps263,8465
publish_steps =publish_steps264,8488
should_publish =should_publish265,8511
should_upload =should_upload266,8554
include_enterprise =include_enterprise267,8624
edition2 =edition2268,8673
init_steps =init_steps269,8702
build_steps =build_steps277,8909
integration_test_steps =integration_test_steps284,9218
build_storybook =build_storybook312,10299
publish_step =publish_step324,10892
store_npm_step =store_npm_step325,10976
windows_package_steps =windows_package_steps330,11175
step =step333,11284
deps_on_clone_enterprise_step =deps_on_clone_enterprise_step337,11418
windows_pipeline =windows_pipeline347,11746
name =name348,11779
edition =edition349,11836
trigger =trigger350,11863
steps =steps351,11890
platform =platform352,11965
depends_on =depends_on353,11995
environment =environment356,12109
pipelines =pipelines358,12150
name =name360,12186
edition =edition361,12273
trigger =trigger362,12304
services =services363,12335
steps =steps364,12362
environment =environment365,12440
volumes =volumes366,12479
name =name374,12711
edition =edition375,12786
trigger =trigger376,12821
services =services377,12856
steps =steps378,12893
environment =environment379,13213
volumes =volumes380,13256
deps =deps383,13317
def enterprise2_pipelines(prefix = "", ver_mode = ver_mode, trigger = release_trigger):enterprise2_pipelines397,13769
environment =environment412,14364
edition =edition415,14424
volumes =volumes416,14451
package_steps =package_steps417,14501
publish_steps =publish_steps418,14524
should_publish =should_publish419,14547
should_upload =should_upload420,14590
include_enterprise =include_enterprise421,14660
edition2 =edition2422,14709
init_steps =init_steps423,14738
build_steps =build_steps431,14945
fetch_images =fetch_images442,15355
upload_cdn =upload_cdn444,15497
step =step458,16187
deps_on_clone_enterprise_step =deps_on_clone_enterprise_step462,16321
pipelines =pipelines472,16608
name =name474,16644
edition =edition475,16742
trigger =trigger476,16773
services =services477,16804
steps =steps478,16831
volumes =volumes479,16909
environment =environment480,16940
def publish_artifacts_step(mode):publish_artifacts_step486,17019
security =security487,17053
security =security489,17098
def publish_artifacts_pipelines(mode):publish_artifacts_pipelines501,17538
trigger =trigger502,17577
steps =steps506,17655
name =name512,17768
trigger =trigger513,17820
steps =steps514,17847
edition =edition515,17870
environment =environment516,17895
def publish_packages_pipeline():publish_packages_pipeline519,17945
trigger =trigger526,18162
oss_steps =oss_steps530,18244
enterprise_steps =enterprise_steps538,18560
deps =deps545,18903
name =name552,19062
trigger =trigger553,19101
steps =steps554,19128
edition =edition555,19155
depends_on =depends_on556,19180
environment =environment557,19207
name =name559,19266
trigger =trigger560,19312
steps =steps561,19339
edition =edition562,19373
depends_on =depends_on563,19398
environment =environment564,19425
def publish_npm_pipelines(mode):publish_npm_pipelines567,19482
trigger =trigger568,19515
steps =steps572,19593
name =name580,19772
trigger =trigger581,19827
steps =steps582,19854
edition =edition583,19877
environment =environment584,19902
def artifacts_page_pipeline():artifacts_page_pipeline587,19952
trigger =trigger588,19983
name =name593,20087
trigger =trigger594,20128
steps =steps595,20155
edition =edition596,20220
environment =environment597,20245
def get_e2e_suffix():get_e2e_suffix600,20295
events/cron.star,1016
aquasec_trivy_image =aquasec_trivy_image8,209
def cronjobs(edition):cronjobs10,255
grafana_com_nightly_pipeline =grafana_com_nightly_pipeline11,278
cronName =cronName12,332
name =name13,374
steps =steps14,412
def cron_job_pipeline(cronName, name, steps):cron_job_pipeline24,773
def scan_docker_image_pipeline(edition, tag):scan_docker_image_pipeline43,1175
edition =edition55,1530
edition =edition57,1579
docker_image =docker_image59,1608
cronName =cronName62,1695
name =name63,1725
steps =steps64,1775
def scan_docker_image_unkown_low_medium_vulnerabilities_step(docker_image):scan_docker_image_unkown_low_medium_vulnerabilities_step71,2047
def scan_docker_image_high_critical_vulnerabilities_step(docker_image):scan_docker_image_high_critical_vulnerabilities_step80,2353
def slack_job_failed_step(channel, image):slack_job_failed_step89,2646
def post_to_grafana_com_step():post_to_grafana_com_step103,3069
events/main.star,633
ver_mode =ver_mode49,966
trigger =trigger50,984
def main_pipelines(edition):main_pipelines62,1168
drone_change_trigger =drone_change_trigger63,1197
pipelines =pipelines79,1513
name =name89,1951
slack_channel =slack_channel90,1994
trigger =trigger91,2045
template =template92,2089
secret =secret93,2135
name =name97,2276
slack_channel =slack_channel98,2310
trigger =trigger99,2366
depends_on =depends_on100,2425
template =template101,2563
secret =secret102,2604
events/pr.star,252
ver_mode =ver_mode48,997
trigger =trigger49,1013
def pr_pipelines(edition):pr_pipelines62,1198
def get_pr_trigger(include_paths = None, exclude_paths = None):get_pr_trigger76,2396
paths_ex =paths_ex91,3080
paths_in =paths_in92,3115
services/services.star,225
def integration_test_services_volumes():integration_test_services_volumes5,79
def integration_test_services(edition):integration_test_services14,292
services =services15,332
def ldap_service():ldap_service59,1616
utils/utils.star,561
failure_template =failure_template11,191
drone_change_template =drone_change_template12,509
services =services19,932
platform =platform20,955
depends_on =depends_on21,983
environment =environment22,1008
volumes =volumes23,1036
platform_conf =platform_conf50,2166
platform_conf =platform_conf62,2534
pipeline =pipeline70,2713
def notify_pipeline(name, slack_channel, trigger, depends_on = [], template = None, secret = None):notify_pipeline105,3545
trigger =trigger106,3645
pipelines/trigger_downstream.star,440
trigger =trigger14,249
def enterprise_downstream_pipeline(edition, ver_mode):enterprise_downstream_pipeline26,433
environment =environment27,488
steps =steps28,527
deps =deps29,587
name =name31,672
edition =edition32,714
trigger =trigger33,741
services =services34,768
steps =steps35,791
depends_on =depends_on36,814
environment =environment37,841
pipelines/verify_starlark.star,323
def verify_starlark(trigger, ver_mode):verify_starlark17,305
environment =environment18,345
steps =steps19,382
name =name26,546
edition =edition27,600
trigger =trigger28,625
services =services29,652
steps =steps30,675
environment =environment31,698
pipelines/build.star,508
def build_e2e(trigger, ver_mode, edition):build_e2e39,936
environment =environment50,1096
variants =variants51,1135
init_steps =init_steps52,1219
build_steps =build_steps61,1491
publish_suffix =publish_suffix107,4049
publish_suffix =publish_suffix109,4100
name =name112,4158
edition =edition113,4224
environment =environment114,4249
services =services115,4284
steps =steps116,4307
trigger =trigger117,4349
pipelines/shellcheck.star,386
trigger =trigger15,235
def shellcheck_step():shellcheck_step31,483
def shellcheck_pipeline():shellcheck_pipeline43,725
environment =environment44,752
steps =steps45,789
name =name50,886
edition =edition51,918
trigger =trigger52,943
services =services53,970
steps =steps54,993
environment =environment55,1016
pipelines/verify_drone.star,317
def verify_drone(trigger, ver_mode):verify_drone17,293
environment =environment18,330
steps =steps19,367
name =name26,528
edition =edition27,579
trigger =trigger28,604
services =services29,631
steps =steps30,654
environment =environment31,677
pipelines/test_backend.star,474
def test_backend(trigger, ver_mode, edition = "oss"):test_backend23,463
environment =environment35,882
init_steps =init_steps36,921
test_steps =test_steps46,1291
pipeline_name =pipeline_name51,1387
pipeline_name =pipeline_name53,1492
name =name55,1584
edition =edition56,1614
trigger =trigger57,1641
services =services58,1668
steps =steps59,1691
environment =environment60,1732
pipelines/lint_frontend.star,415
def lint_frontend_pipeline(trigger, ver_mode):lint_frontend_pipeline16,260
environment =environment26,546
yarn_step =yarn_step27,583
init_steps =init_steps29,660
test_steps =test_steps33,736
name =name37,812
edition =edition38,864
trigger =trigger39,889
services =services40,916
steps =steps41,939
environment =environment42,980
pipelines/docs.star,494
docs_paths =docs_paths19,383
def docs_pipelines(edition, ver_mode, trigger):docs_pipelines28,511
environment =environment29,559
steps =steps30,598
name =name40,815
edition =edition41,858
trigger =trigger42,885
services =services43,912
steps =steps44,935
environment =environment45,958
def lint_docs():lint_docs48,1000
def trigger_docs_main():trigger_docs_main63,1328
def trigger_docs_pr():trigger_docs_pr72,1478
pipelines/test_frontend.star,476
def test_frontend(trigger, ver_mode, edition = "oss"):test_frontend20,374
environment =environment32,794
init_steps =init_steps33,833
test_steps =test_steps41,1102
pipeline_name =pipeline_name45,1205
pipeline_name =pipeline_name47,1311
name =name49,1404
edition =edition50,1434
trigger =trigger51,1461
services =services52,1488
steps =steps53,1511
environment =environment54,1552
pipelines/integration_tests.star,483
def integration_tests(trigger, ver_mode, edition):integration_tests26,542
environment =environment37,900
services =services38,939
volumes =volumes39,989
init_steps =init_steps40,1039
test_steps =test_steps48,1282
name =name54,1412
edition =edition55,1468
trigger =trigger56,1493
services =services57,1520
steps =steps58,1549
environment =environment59,1590
volumes =volumes60,1625
pipelines/windows.star,954
def windows(trigger, edition, ver_mode):windows17,339
environment =environment29,798
init_cmds =init_cmds30,837
steps =steps38,1205
bucket =bucket49,1497
ver_part =ver_part51,1590
dir =dir52,1628
dir =dir54,1670
bucket =bucket55,1695
build_no =build_no56,1736
ver_part =ver_part57,1780
installer_commands =installer_commands58,1842
committish =committish100,3763
committish =committish102,3846
committish =committish104,3906
download_grabpl_step_cmds =download_grabpl_step_cmds107,4057
clone_cmds =clone_cmds113,4363
name =name146,5711
edition =edition147,5742
trigger =trigger148,5769
steps =steps149,5830
depends_on =depends_on150,5889
platform =platform151,6007
environment =environment152,6037
pipelines/lint_backend.star,418
def lint_backend_pipeline(trigger, ver_mode):lint_backend_pipeline18,306
environment =environment28,590
wire_step =wire_step29,627
init_steps =init_steps31,704
test_steps =test_steps36,809
name =name43,959
edition =edition44,1010
trigger =trigger45,1035
services =services46,1062
steps =steps47,1085
environment =environment48,1126
pipelines/publish_images.star,998
def publish_image_steps(edition, mode, docker_repo):publish_image_steps17,303
additional_docker_repo =additional_docker_repo31,922
additional_docker_repo =additional_docker_repo33,979
steps =steps34,1034
def publish_image_pipelines_public():publish_image_pipelines_public45,1369
mode =mode51,1521
trigger =trigger52,1541
name =name57,1641
trigger =trigger58,1694
steps =steps59,1721
edition =edition60,1813
environment =environment61,1835
name =name63,1894
trigger =trigger64,1954
steps =steps65,1981
edition =edition66,2091
environment =environment67,2113
def publish_image_pipelines_security():publish_image_pipelines_security70,2170
mode =mode71,2210
trigger =trigger72,2232
name =name77,2332
trigger =trigger78,2392
steps =steps79,2419
edition =edition80,2529
environment =environment81,2551
steps/lib.star,8579
grabpl_version =grabpl_version7,181
build_image =build_image8,208
publish_image =publish_image9,254
deploy_docker_image =deploy_docker_image10,304
alpine_image =alpine_image11,380
curl_image =curl_image12,411
windows_image =windows_image13,452
wix_image =wix_image14,501
go_image =go_image15,536
disable_tests =disable_tests17,564
trigger_oss =trigger_oss18,586
def slack_step(channel, template, secret):slack_step24,653
def yarn_install_step(edition = "oss"):yarn_install_step35,918
deps =deps36,958
deps =deps38,1004
def wire_install_step():wire_install_step48,1222
def identify_runner_step(platform = "linux"):identify_runner_step60,1454
def clone_enterprise_step(ver_mode):clone_enterprise_step78,1916
committish =committish87,2193
committish =committish89,2268
committish =committish91,2317
def init_enterprise_step(ver_mode):init_enterprise_step105,2747
source_commit =source_commit115,3098
source_commit =source_commit117,3151
environment =environment118,3191
token =token121,3280
environment =environment123,3369
token =token126,3458
environment =environment128,3518
token =token129,3543
def download_grabpl_step(platform = "linux"):download_grabpl_step148,4147
def lint_drone_step():lint_drone_step173,4973
def lint_starlark_step():lint_starlark_step185,5216
def enterprise_downstream_step(edition, ver_mode):enterprise_downstream_step206,6000
repo =repo219,6482
step =step225,6623
def lint_backend_step():lint_backend_step247,7248
def benchmark_ldap_step():benchmark_ldap_step265,7713
def build_storybook_step(edition, ver_mode):build_storybook_step278,8087
def store_storybook_step(edition, ver_mode, trigger = None):store_storybook_step300,8743
commands =commands314,9202
commands =commands323,9521
step =step325,9593
when_cond =when_cond338,10125
step =step346,10330
def e2e_tests_artifacts(edition):e2e_tests_artifacts349,10391
def upload_cdn_step(edition, ver_mode, trigger = None):upload_cdn_step386,12378
deps =deps397,12763
step =step407,12970
step =step420,13423
def build_backend_step(edition, ver_mode, variants = None):build_backend_step423,13482
variants_str =variants_str437,14070
variants_str =variants_str439,14109
cmds =cmds443,14256
build_no =build_no449,14418
cmds =cmds450,14461
def build_frontend_step(edition, ver_mode):build_frontend_step468,14906
build_no =build_no478,15246
cmds =cmds482,15356
cmds =cmds487,15505
def build_frontend_package_step(edition, ver_mode):build_frontend_package_step505,15960
build_no =build_no515,16312
cmds =cmds519,16422
cmds =cmds524,16580
def build_plugins_step(edition, ver_mode):build_plugins_step542,17053
env =env544,17121
env =env548,17220
def test_backend_step():test_backend_step563,17607
def test_backend_integration_step():test_backend_integration_step575,17880
def betterer_frontend_step(edition = "oss"):betterer_frontend_step587,18187
deps =deps596,18427
def test_frontend_step(edition = "oss"):test_frontend_step609,18728
deps =deps618,18962
def lint_frontend_step():lint_frontend_step634,19343
def test_a11y_frontend_step(ver_mode, edition, port = 3001):test_a11y_frontend_step652,19793
commands =commands664,20279
failure =failure667,20345
failure =failure672,20483
def frontend_metrics_step(edition, trigger = None):frontend_metrics_step693,21146
step =step706,21507
step =step721,22007
def codespell_step():codespell_step724,22066
def package_step(edition, ver_mode, variants = None):package_step736,22468
deps =deps750,23006
variants_str =variants_str757,23167
variants_str =variants_str759,23206
sign_args =sign_args762,23332
env =env763,23362
test_args =test_args769,23628
sign_args =sign_args771,23661
env =env772,23684
test_args =test_args773,23703
cmds =cmds777,23829
build_no =build_no784,24036
cmds =cmds785,24079
def grafana_server_step(edition, port = 3001):grafana_server_step798,24459
package_file_pfx =package_file_pfx808,24729
package_file_pfx =package_file_pfx810,24788
package_file_pfx =package_file_pfx812,24889
environment =environment814,24938
def e2e_tests_step(suite, edition, port = 3001, tries = None):e2e_tests_step837,25554
cmd =cmd838,25617
def cloud_plugins_e2e_tests_step(suite, edition, cloud, trigger = None):cloud_plugins_e2e_tests_step856,26186
environment =environment869,26649
when =when870,26670
when =when872,26700
environment =environment874,26748
when =when882,27129
branch =branch888,27345
step =step889,27401
step =step901,27822
def build_docs_website_step():build_docs_website_step904,27874
def copy_packages_for_docker_step(edition = None):copy_packages_for_docker_step916,28272
def build_docker_images_step(edition, archs = None, ubuntu = False, publish = False):build_docker_images_step929,28622
cmd =cmd943,29193
ubuntu_sfx =ubuntu_sfx947,29307
ubuntu_sfx =ubuntu_sfx949,29342
environment =environment955,29468
def fetch_images_step(edition):fetch_images_step979,30079
def publish_images_step(edition, ver_mode, mode, docker_repo, trigger = None):publish_images_step997,30745
name =name1013,31562
docker_repo =docker_repo1014,31585
mode =mode1016,31663
mode =mode1018,31709
environment =environment1020,31728
cmd =cmd1026,31912
deps =deps1029,32041
deps =deps1032,32147
name =name1035,32250
docker_repo =docker_repo1036,32273
cmd =cmd1038,32459
step =step1040,32565
step =step1052,32929
def postgres_integration_tests_step():postgres_integration_tests_step1056,32989
cmds =cmds1057,33028
def mysql_integration_tests_step():mysql_integration_tests_step1079,33850
cmds =cmds1080,33886
def redis_integration_tests_step():redis_integration_tests_step1100,34629
def memcached_integration_tests_step():memcached_integration_tests_step1114,35026
def release_canary_npm_packages_step(edition, trigger = None):release_canary_npm_packages_step1128,35435
step =step1141,35805
step =step1153,36143
def enterprise2_suffix(edition):enterprise2_suffix1156,36202
def upload_packages_step(edition, ver_mode, trigger = None):upload_packages_step1161,36320
deps =deps1176,36816
step =step1184,37036
step =step1195,37471
def publish_grafanacom_step(edition, ver_mode):publish_grafanacom_step1198,37530
cmd =cmd1211,38044
build_no =build_no1215,38188
cmd =cmd1216,38231
def publish_linux_packages_step(edition, package_manager = "deb"):publish_linux_packages_step1239,38866
def get_windows_steps(edition, ver_mode):get_windows_steps1261,39989
init_cmds =init_cmds1270,40281
steps =steps1278,40649
bucket =bucket1289,40941
ver_part =ver_part1291,41034
dir =dir1292,41072
dir =dir1294,41114
bucket =bucket1295,41139
build_no =build_no1296,41180
ver_part =ver_part1297,41224
installer_commands =installer_commands1298,41286
committish =committish1340,43207
committish =committish1342,43290
committish =committish1344,43350
download_grabpl_step_cmds =download_grabpl_step_cmds1347,43501
clone_cmds =clone_cmds1353,43807
def verify_gen_cue_step(edition):verify_gen_cue_step1387,45152
deps =deps1388,45186
def verify_gen_jsonnet_step(edition):verify_gen_jsonnet_step1402,45694
deps =deps1403,45732
def trigger_test_release():trigger_test_release1417,46236
def artifacts_page_step():artifacts_page_step1451,47731
def end_to_end_tests_deps():end_to_end_tests_deps1466,48058
def compile_build_cmd(edition = "oss"):compile_build_cmd1476,48321
dependencies =dependencies1477,48361
dependencies =dependencies1479,48432
def get_trigger_storybook(ver_mode):get_trigger_storybook1492,48780
trigger_storybook =trigger_storybook1500,49031
trigger_storybook =trigger_storybook1502,49088
trigger_storybook =trigger_storybook1506,49168
vault.star,444
pull_secret =pull_secret4,87
github_token =github_token5,120
drone_token =drone_token6,150
prerelease_bucket =prerelease_bucket7,178
gcp_upload_artifacts_key =gcp_upload_artifacts_key8,218
azure_sp_app_id =azure_sp_app_id9,272
azure_sp_app_pw =azure_sp_app_pw10,308
azure_tenant =azure_tenant11,344
def from_secret(secret):from_secret13,375
def vault_secret(name, path, key):vault_secret18,451
def secrets():secrets28,633
version.star,116
ver_mode =ver_mode12,197
trigger =trigger13,225
def version_branch_pipelines():version_branch_pipelines15,268

View File

@@ -1,99 +1,115 @@
load('scripts/drone/vault.star', 'from_secret', 'pull_secret')
load('scripts/drone/steps/lib.star', 'publish_image', 'compile_build_cmd')
"""
This module provides functions for cronjob pipelines and steps used within.
"""
aquasec_trivy_image = 'aquasec/trivy:0.21.0'
load("scripts/drone/vault.star", "from_secret")
load(
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"publish_image",
)
def cronjobs(edition):
grafana_com_nightly_pipeline = cron_job_pipeline(
cronName='grafana-com-nightly',
name='grafana-com-nightly',
steps=[compile_build_cmd(),post_to_grafana_com_step()]
)
aquasec_trivy_image = "aquasec/trivy:0.21.0"
def cronjobs():
return [
scan_docker_image_pipeline(edition, 'latest'),
scan_docker_image_pipeline(edition, 'main'),
scan_docker_image_pipeline(edition, 'latest-ubuntu'),
scan_docker_image_pipeline(edition, 'main-ubuntu'),
grafana_com_nightly_pipeline,
scan_docker_image_pipeline("latest"),
scan_docker_image_pipeline("main"),
scan_docker_image_pipeline("latest-ubuntu"),
scan_docker_image_pipeline("main-ubuntu"),
grafana_com_nightly_pipeline(),
]
def cron_job_pipeline(cronName, name, steps):
return {
'kind': 'pipeline',
'type': 'docker',
'platform': {
'os': 'linux',
'arch': 'amd64',
"kind": "pipeline",
"type": "docker",
"platform": {
"os": "linux",
"arch": "amd64",
},
'name': name,
'trigger': {
'event': 'cron',
'cron': cronName,
"name": name,
"trigger": {
"event": "cron",
"cron": cronName,
},
'clone': {
'retries': 3,
"clone": {
"retries": 3,
},
'steps': steps,
"steps": steps,
}
def scan_docker_image_pipeline(edition, tag):
if edition != 'oss':
edition='grafana-enterprise'
else:
edition='grafana'
def scan_docker_image_pipeline(tag):
"""Generates a cronjob pipeline for nightly scans of grafana Docker images.
dockerImage='grafana/{}:{}'.format(edition, tag)
Args:
tag: determines which image tag is scanned.
Returns:
Drone cronjob pipeline.
"""
docker_image = "grafana/grafana:{}".format(tag)
return cron_job_pipeline(
cronName='nightly',
name='scan-' + dockerImage + '-image',
steps=[
scan_docker_image_unkown_low_medium_vulnerabilities_step(dockerImage),
scan_docker_image_high_critical_vulnerabilities_step(dockerImage),
slack_job_failed_step('grafana-backend-ops', dockerImage),
])
cronName = "nightly",
name = "scan-" + docker_image + "-image",
steps = [
scan_docker_image_unkown_low_medium_vulnerabilities_step(docker_image),
scan_docker_image_high_critical_vulnerabilities_step(docker_image),
slack_job_failed_step("grafana-backend-ops", docker_image),
],
)
def scan_docker_image_unkown_low_medium_vulnerabilities_step(dockerImage):
def scan_docker_image_unkown_low_medium_vulnerabilities_step(docker_image):
return {
'name': 'scan-unkown-low-medium-vulnerabilities',
'image': aquasec_trivy_image,
'commands': [
'trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM ' + dockerImage,
"name": "scan-unkown-low-medium-vulnerabilities",
"image": aquasec_trivy_image,
"commands": [
"trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM " + docker_image,
],
}
def scan_docker_image_high_critical_vulnerabilities_step(dockerImage):
def scan_docker_image_high_critical_vulnerabilities_step(docker_image):
return {
'name': 'scan-high-critical-vulnerabilities',
'image': aquasec_trivy_image,
'commands': [
'trivy --exit-code 1 --severity HIGH,CRITICAL ' + dockerImage,
"name": "scan-high-critical-vulnerabilities",
"image": aquasec_trivy_image,
"commands": [
"trivy --exit-code 1 --severity HIGH,CRITICAL " + docker_image,
],
}
def slack_job_failed_step(channel, image):
return {
'name': 'slack-notify-failure',
'image': 'plugins/slack',
'settings': {
'webhook': from_secret('slack_webhook_backend'),
'channel': channel,
'template': 'Nightly docker image scan job for ' + image + ' failed: {{build.link}}',
"name": "slack-notify-failure",
"image": "plugins/slack",
"settings": {
"webhook": from_secret("slack_webhook_backend"),
"channel": channel,
"template": "Nightly docker image scan job for " +
image +
" failed: {{build.link}}",
},
'when': {
'status': 'failure'
}
"when": {"status": "failure"},
}
def post_to_grafana_com_step():
return {
'name': 'post-to-grafana-com',
'image': publish_image,
'environment': {
'GRAFANA_COM_API_KEY': from_secret('grafana_api_key'),
'GCP_KEY': from_secret('gcp_key'),
},
'depends_on': ['compile-build-cmd'],
'commands': ['./bin/build publish grafana-com --edition oss'],
}
"name": "post-to-grafana-com",
"image": publish_image,
"environment": {
"GRAFANA_COM_API_KEY": from_secret("grafana_api_key"),
"GCP_KEY": from_secret("gcp_key"),
},
"depends_on": ["compile-build-cmd"],
"commands": ["./bin/build publish grafana-com --edition oss"],
}
def grafana_com_nightly_pipeline():
return cron_job_pipeline(
cronName = "grafana-com-nightly",
name = "grafana-com-nightly",
steps = [
compile_build_cmd(),
post_to_grafana_com_step(),
],
)

View File

@@ -1,108 +1,116 @@
load(
'scripts/drone/utils/utils.star',
'pipeline',
'notify_pipeline',
'failure_template',
'drone_change_template',
)
"""
This module returns all the pipelines used in the event of pushes to the main branch.
"""
load(
'scripts/drone/pipelines/docs.star',
'docs_pipelines',
'trigger_docs_main',
"scripts/drone/utils/utils.star",
"drone_change_template",
"failure_template",
"notify_pipeline",
)
load(
'scripts/drone/pipelines/test_frontend.star',
'test_frontend',
"scripts/drone/pipelines/docs.star",
"docs_pipelines",
"trigger_docs_main",
)
load(
'scripts/drone/pipelines/test_backend.star',
'test_backend',
"scripts/drone/pipelines/test_frontend.star",
"test_frontend",
)
load(
'scripts/drone/pipelines/integration_tests.star',
'integration_tests',
"scripts/drone/pipelines/test_backend.star",
"test_backend",
)
load(
'scripts/drone/pipelines/build.star',
'build_e2e',
"scripts/drone/pipelines/integration_tests.star",
"integration_tests",
)
load(
'scripts/drone/pipelines/windows.star',
'windows',
"scripts/drone/pipelines/build.star",
"build_e2e",
)
load(
'scripts/drone/pipelines/trigger_downstream.star',
'enterprise_downstream_pipeline',
"scripts/drone/pipelines/windows.star",
"windows",
)
load(
'scripts/drone/pipelines/lint_backend.star',
'lint_backend_pipeline',
"scripts/drone/pipelines/trigger_downstream.star",
"enterprise_downstream_pipeline",
)
load(
'scripts/drone/pipelines/lint_frontend.star',
'lint_frontend_pipeline',
"scripts/drone/pipelines/lint_backend.star",
"lint_backend_pipeline",
)
load(
"scripts/drone/pipelines/lint_frontend.star",
"lint_frontend_pipeline",
)
load('scripts/drone/vault.star', 'from_secret')
ver_mode = 'main'
ver_mode = "main"
trigger = {
'event': ['push',],
'branch': 'main',
'paths': {
'exclude': [
'*.md',
'docs/**',
'latest.json',
"event": [
"push",
],
"branch": "main",
"paths": {
"exclude": [
"*.md",
"docs/**",
"latest.json",
],
},
}
def main_pipelines(edition):
def main_pipelines():
drone_change_trigger = {
'event': ['push',],
'branch': 'main',
'repo': [
'grafana/grafana',
"event": [
"push",
],
'paths': {
'include': [
'.drone.yml',
"branch": "main",
"repo": [
"grafana/grafana",
],
"paths": {
"include": [
".drone.yml",
],
'exclude': [
'exclude',
"exclude": [
"exclude",
],
},
}
pipelines = [
docs_pipelines(edition, ver_mode, trigger_docs_main()),
docs_pipelines(ver_mode, trigger_docs_main()),
test_frontend(trigger, ver_mode),
lint_frontend_pipeline(trigger, ver_mode),
test_backend(trigger, ver_mode),
lint_backend_pipeline(trigger, ver_mode),
build_e2e(trigger, ver_mode, edition),
integration_tests(trigger, ver_mode, edition),
windows(trigger, edition, ver_mode),
notify_pipeline(
name='notify-drone-changes', slack_channel='slack-webhooks-test', trigger=drone_change_trigger,
template=drone_change_template, secret='drone-changes-webhook',
),
enterprise_downstream_pipeline(edition, ver_mode),
notify_pipeline(
name='main-notify', slack_channel='grafana-ci-notifications', trigger=dict(trigger, status=['failure']),
depends_on=['main-test-frontend', 'main-test-backend', 'main-build-e2e-publish', 'main-integration-tests', 'main-windows'],
template=failure_template, secret='slack_webhook'
)]
build_e2e(trigger, ver_mode),
integration_tests(trigger, prefix = ver_mode, ver_mode = ver_mode),
windows(trigger, edition = "oss", ver_mode = ver_mode),
notify_pipeline(
name = "notify-drone-changes",
slack_channel = "slack-webhooks-test",
trigger = drone_change_trigger,
template = drone_change_template,
secret = "drone-changes-webhook",
),
enterprise_downstream_pipeline(),
notify_pipeline(
name = "main-notify",
slack_channel = "grafana-ci-notifications",
trigger = dict(trigger, status = ["failure"]),
depends_on = [
"main-test-frontend",
"main-test-backend",
"main-build-e2e-publish",
"main-integration-tests",
"main-windows",
],
template = failure_template,
secret = "slack_webhook",
),
]
return pipelines

View File

@@ -1,85 +1,155 @@
load(
'scripts/drone/utils/utils.star',
'pipeline',
)
"""
This module returns all pipelines used in the event of a pull request.
It also includes a function generating a PR trigger from a list of included and excluded paths.
"""
load(
'scripts/drone/pipelines/test_frontend.star',
'test_frontend',
"scripts/drone/pipelines/test_frontend.star",
"test_frontend",
)
load(
'scripts/drone/pipelines/test_backend.star',
'test_backend',
"scripts/drone/pipelines/test_backend.star",
"test_backend",
)
load(
'scripts/drone/pipelines/integration_tests.star',
'integration_tests',
"scripts/drone/pipelines/integration_tests.star",
"integration_tests",
)
load(
'scripts/drone/pipelines/build.star',
'build_e2e',
"scripts/drone/pipelines/build.star",
"build_e2e",
)
load(
'scripts/drone/pipelines/verify_drone.star',
'verify_drone',
"scripts/drone/pipelines/verify_drone.star",
"verify_drone",
)
load(
'scripts/drone/pipelines/docs.star',
'docs_pipelines',
'trigger_docs_pr',
"scripts/drone/pipelines/verify_starlark.star",
"verify_starlark",
)
load(
'scripts/drone/pipelines/shellcheck.star',
'shellcheck_pipeline',
"scripts/drone/pipelines/docs.star",
"docs_pipelines",
"trigger_docs_pr",
)
load(
'scripts/drone/pipelines/lint_backend.star',
'lint_backend_pipeline',
"scripts/drone/pipelines/shellcheck.star",
"shellcheck_pipeline",
)
load(
'scripts/drone/pipelines/lint_frontend.star',
'lint_frontend_pipeline',
"scripts/drone/pipelines/lint_backend.star",
"lint_backend_pipeline",
)
load(
"scripts/drone/pipelines/lint_frontend.star",
"lint_frontend_pipeline",
)
ver_mode = 'pr'
ver_mode = "pr"
trigger = {
'event': [
'pull_request',
"event": [
"pull_request",
],
'paths': {
'exclude': [
'*.md',
'docs/**',
'latest.json',
"paths": {
"exclude": [
"*.md",
"docs/**",
"latest.json",
],
},
}
def pr_pipelines(edition):
def pr_pipelines():
return [
verify_drone(get_pr_trigger(include_paths=['scripts/drone/**', '.drone.yml', '.drone.star']), ver_mode),
test_frontend(get_pr_trigger(exclude_paths=['pkg/**', 'packaging/**', 'go.sum', 'go.mod']), ver_mode),
lint_frontend_pipeline(get_pr_trigger(exclude_paths=['pkg/**', 'packaging/**', 'go.sum', 'go.mod']), ver_mode),
test_backend(get_pr_trigger(include_paths=['pkg/**', 'packaging/**', '.drone.yml', 'conf/**', 'go.sum', 'go.mod', 'public/app/plugins/**/plugin.json', 'devenv/**']), ver_mode),
lint_backend_pipeline(get_pr_trigger(include_paths=['pkg/**', 'packaging/**', 'conf/**', 'go.sum', 'go.mod', 'public/app/plugins/**/plugin.json', 'devenv/**']), ver_mode),
build_e2e(trigger, ver_mode, edition),
integration_tests(get_pr_trigger(include_paths=['pkg/**', 'packaging/**', '.drone.yml', 'conf/**', 'go.sum', 'go.mod', 'public/app/plugins/**/plugin.json']), ver_mode, edition),
docs_pipelines(edition, ver_mode, trigger_docs_pr()),
verify_drone(
get_pr_trigger(
include_paths = ["scripts/drone/**", ".drone.yml", ".drone.star"],
),
ver_mode,
),
verify_starlark(
get_pr_trigger(
include_paths = ["scripts/drone/**", ".drone.star"],
),
ver_mode,
),
test_frontend(
get_pr_trigger(
exclude_paths = ["pkg/**", "packaging/**", "go.sum", "go.mod"],
),
ver_mode,
),
lint_frontend_pipeline(
get_pr_trigger(
exclude_paths = ["pkg/**", "packaging/**", "go.sum", "go.mod"],
),
ver_mode,
),
test_backend(
get_pr_trigger(
include_paths = [
"pkg/**",
"packaging/**",
".drone.yml",
"conf/**",
"go.sum",
"go.mod",
"public/app/plugins/**/plugin.json",
"devenv/**",
],
),
ver_mode,
),
lint_backend_pipeline(
get_pr_trigger(
include_paths = [
"pkg/**",
"packaging/**",
"conf/**",
"go.sum",
"go.mod",
"public/app/plugins/**/plugin.json",
"devenv/**",
".bingo/**",
],
),
ver_mode,
),
build_e2e(trigger, ver_mode),
integration_tests(
get_pr_trigger(
include_paths = [
"pkg/**",
"packaging/**",
".drone.yml",
"conf/**",
"go.sum",
"go.mod",
"public/app/plugins/**/plugin.json",
],
),
prefix = ver_mode,
),
docs_pipelines(ver_mode, trigger_docs_pr()),
shellcheck_pipeline(),
]
def get_pr_trigger(include_paths = None, exclude_paths = None):
"""Generates a trigger filter from the lists of included and excluded path patterns.
def get_pr_trigger(include_paths=None, exclude_paths=None):
paths_ex = ['docs/**', '*.md']
This function is primarily intended to generate a trigger for code changes
as the patterns 'docs/**' and '*.md' are always excluded.
Args:
include_paths: a list of path patterns using the same syntax as gitignore.
Changes affecting files matching these path patterns trigger the pipeline.
exclude_paths: a list of path patterns using the same syntax as gitignore.
Changes affecting files matching these path patterns do not trigger the pipeline.
Returns:
Drone trigger.
"""
paths_ex = ["docs/**", "*.md"]
paths_in = []
if include_paths:
for path in include_paths:
@@ -88,12 +158,11 @@ def get_pr_trigger(include_paths=None, exclude_paths=None):
for path in exclude_paths:
paths_ex.extend([path])
return {
'event': [
'pull_request',
"event": [
"pull_request",
],
'paths': {
'exclude': paths_ex,
'include': paths_in,
"paths": {
"exclude": paths_ex,
"include": paths_in,
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
"""
This module contains steps and pipelines publishing to AWS Marketplace.
"""
load(
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"fetch_images_step",
"publish_image",
)
load("scripts/drone/vault.star", "from_secret")
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def publish_aws_marketplace_step():
return {
"name": "publish-aws-marketplace",
"image": publish_image,
"commands": ["./bin/build publish aws --image grafana/grafana-enterprise --repo grafana-labs/grafanaenterprise --product 422b46fb-bea6-4f27-8bcc-832117bd627e"],
"depends_on": ["fetch-images-enterprise"],
"environment": {
"AWS_REGION": from_secret("aws_region"),
"AWS_ACCESS_KEY_ID": from_secret("aws_access_key_id"),
"AWS_SECRET_ACCESS_KEY": from_secret("aws_secret_access_key"),
},
"volumes": [{"name": "docker", "path": "/var/run/docker.sock"}],
}
def publish_aws_marketplace_pipeline(mode):
trigger = {
"event": ["promote"],
"target": [mode],
}
return [pipeline(
name = "publish-aws-marketplace-{}".format(mode),
trigger = trigger,
steps = [compile_build_cmd(), fetch_images_step("enterprise"), publish_aws_marketplace_step()],
edition = "",
depends_on = ["publish-docker-enterprise-public"],
environment = {"EDITION": "enterprise2"},
)]

View File

@@ -1,110 +1,170 @@
load(
'scripts/drone/steps/lib.star',
'download_grabpl_step',
'build_image',
'identify_runner_step',
'wire_install_step',
'yarn_install_step',
'build_backend_step',
'build_frontend_step',
'build_frontend_package_step',
'build_plugins_step',
'package_step',
'grafana_server_step',
'e2e_tests_step',
'e2e_tests_artifacts',
'build_storybook_step',
'copy_packages_for_docker_step',
'build_docker_images_step',
'publish_images_step',
'postgres_integration_tests_step',
'mysql_integration_tests_step',
'redis_integration_tests_step',
'memcached_integration_tests_step',
'get_windows_steps',
'benchmark_ldap_step',
'enterprise_downstream_step',
'frontend_metrics_step',
'store_storybook_step',
'release_canary_npm_packages_step',
'upload_packages_step',
'upload_cdn_step',
'verify_gen_cue_step',
'verify_gen_jsonnet_step',
'test_a11y_frontend_step',
'trigger_oss',
'betterer_frontend_step',
'trigger_test_release',
'compile_build_cmd',
'cloud_plugins_e2e_tests_step',
)
"""This module contains the comprehensive build pipeline."""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"build_backend_step",
"build_docker_images_step",
"build_frontend_package_step",
"build_frontend_step",
"build_plugins_step",
"build_storybook_step",
"cloud_plugins_e2e_tests_step",
"compile_build_cmd",
"copy_packages_for_docker_step",
"download_grabpl_step",
"e2e_tests_artifacts",
"e2e_tests_step",
"enterprise_downstream_step",
"frontend_metrics_step",
"grafana_server_step",
"identify_runner_step",
"package_step",
"publish_images_step",
"release_canary_npm_packages_step",
"store_storybook_step",
"test_a11y_frontend_step",
"trigger_oss",
"trigger_test_release",
"upload_cdn_step",
"upload_packages_step",
"verify_gen_cue_step",
"verify_gen_jsonnet_step",
"wire_install_step",
"yarn_install_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def build_e2e(trigger, ver_mode, edition):
variants = ['linux-amd64', 'linux-amd64-musl', 'darwin-amd64', 'windows-amd64',]
# @unused
def build_e2e(trigger, ver_mode):
"""Perform e2e building, testing, and publishing.
Args:
trigger: controls which events can trigger the pipeline execution.
ver_mode: used in the naming of the pipeline.
Returns:
Drone pipeline.
"""
edition = "oss"
environment = {"EDITION": edition}
init_steps = [
identify_runner_step(),
download_grabpl_step(),
compile_build_cmd(),
verify_gen_cue_step(edition="oss"),
verify_gen_jsonnet_step(edition="oss"),
verify_gen_cue_step(),
verify_gen_jsonnet_step(),
wire_install_step(),
yarn_install_step(),
]
build_steps = []
if ver_mode == 'pr':
build_steps.extend([trigger_test_release()])
build_steps.extend([enterprise_downstream_step(edition=edition, ver_mode=ver_mode)])
build_steps.extend([
build_backend_step(edition=edition, ver_mode=ver_mode),
build_frontend_step(edition=edition, ver_mode=ver_mode),
build_frontend_package_step(edition=edition, ver_mode=ver_mode),
build_plugins_step(edition=edition, ver_mode=ver_mode),
])
if ver_mode == 'main':
build_steps.extend([package_step(edition=edition, ver_mode=ver_mode)])
elif ver_mode == 'pr':
build_steps.extend([package_step(edition=edition, ver_mode=ver_mode, variants=variants)])
variants = None
build_steps.extend([
grafana_server_step(edition=edition),
e2e_tests_step('dashboards-suite', edition=edition),
e2e_tests_step('smoke-tests-suite', edition=edition),
e2e_tests_step('panels-suite', edition=edition),
e2e_tests_step('various-suite', edition=edition),
cloud_plugins_e2e_tests_step('cloud-plugins-suite', edition=edition, cloud='azure', trigger=trigger_oss),
e2e_tests_artifacts(edition=edition),
build_storybook_step(edition=edition, ver_mode=ver_mode),
copy_packages_for_docker_step(),
test_a11y_frontend_step(ver_mode=ver_mode, edition=edition),
])
if ver_mode == 'main':
build_steps.extend([
store_storybook_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss),
frontend_metrics_step(edition=edition, trigger=trigger_oss)
])
if ver_mode == "pr":
build_steps.extend(
[
trigger_test_release(),
enterprise_downstream_step(ver_mode = ver_mode),
],
)
if ver_mode == 'main':
build_steps.extend([
build_docker_images_step(edition=edition, ver_mode=ver_mode, publish=False),
build_docker_images_step(edition=edition, ver_mode=ver_mode, ubuntu=True, publish=False),
publish_images_step(edition=edition, ver_mode=ver_mode, mode='', docker_repo='grafana/grafana', trigger=trigger_oss),
publish_images_step(edition=edition, ver_mode=ver_mode, mode='', docker_repo='grafana/grafana-oss', trigger=trigger_oss),
release_canary_npm_packages_step(edition, trigger=trigger_oss),
upload_packages_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss),
upload_cdn_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss)
])
elif ver_mode == 'pr':
build_steps.extend([build_docker_images_step(edition=edition, ver_mode=ver_mode, archs=['amd64', ])])
variants = [
"linux-amd64",
"linux-amd64-musl",
"darwin-amd64",
"windows-amd64",
]
publish_suffix = ''
if ver_mode == 'main':
publish_suffix = '-publish'
build_steps.extend(
[
build_backend_step(edition = edition, ver_mode = ver_mode),
build_frontend_step(edition = edition, ver_mode = ver_mode),
build_frontend_package_step(edition = edition, ver_mode = ver_mode),
build_plugins_step(edition = edition, ver_mode = ver_mode),
package_step(edition = edition, variants = variants, ver_mode = ver_mode),
grafana_server_step(edition = edition),
e2e_tests_step("dashboards-suite"),
e2e_tests_step("smoke-tests-suite"),
e2e_tests_step("panels-suite"),
e2e_tests_step("various-suite"),
cloud_plugins_e2e_tests_step(
"cloud-plugins-suite",
cloud = "azure",
trigger = trigger_oss,
),
e2e_tests_artifacts(),
build_storybook_step(ver_mode = ver_mode),
copy_packages_for_docker_step(),
test_a11y_frontend_step(ver_mode = ver_mode),
],
)
if ver_mode == "main":
build_steps.extend(
[
store_storybook_step(trigger = trigger_oss, ver_mode = ver_mode),
frontend_metrics_step(trigger = trigger_oss),
build_docker_images_step(
edition = edition,
publish = False,
),
build_docker_images_step(
edition = edition,
publish = False,
ubuntu = True,
),
publish_images_step(
docker_repo = "grafana",
edition = edition,
mode = "",
trigger = trigger_oss,
ver_mode = ver_mode,
),
publish_images_step(
docker_repo = "grafana-oss",
edition = edition,
mode = "",
trigger = trigger_oss,
ver_mode = ver_mode,
),
release_canary_npm_packages_step(trigger = trigger_oss),
upload_packages_step(
edition = edition,
trigger = trigger_oss,
ver_mode = ver_mode,
),
upload_cdn_step(
edition = edition,
trigger = trigger_oss,
ver_mode = ver_mode,
),
],
)
elif ver_mode == "pr":
build_steps.extend(
[
build_docker_images_step(
archs = [
"amd64",
],
edition = edition,
),
],
)
publish_suffix = ""
if ver_mode == "main":
publish_suffix = "-publish"
return pipeline(
name='{}-build-e2e{}'.format(ver_mode, publish_suffix), edition="oss", trigger=trigger, services=[], steps=init_steps + build_steps,
name = "{}-build-e2e{}".format(ver_mode, publish_suffix),
edition = "oss",
environment = environment,
services = [],
steps = init_steps + build_steps,
trigger = trigger,
)

View File

@@ -1,37 +1,32 @@
load(
'scripts/drone/steps/lib.star',
'build_image',
'yarn_install_step',
'identify_runner_step',
'download_grabpl_step',
'lint_frontend_step',
'codespell_step',
'test_frontend_step',
'build_storybook_step',
'build_docs_website_step',
)
"""
This module returns all the pipelines used in the event of documentation changes along with supporting functions.
"""
load(
'scripts/drone/services/services.star',
'integration_test_services',
'ldap_service',
"scripts/drone/steps/lib.star",
"build_docs_website_step",
"build_image",
"codespell_step",
"download_grabpl_step",
"identify_runner_step",
"yarn_install_step",
)
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/utils/utils.star",
"pipeline",
)
docs_paths = {
'include': [
'*.md',
'docs/**',
'packages/**/*.md',
'latest.json',
"include": [
"*.md",
"docs/**",
"packages/**/*.md",
"latest.json",
],
}
def docs_pipelines(edition, ver_mode, trigger):
def docs_pipelines(ver_mode, trigger):
environment = {"EDITION": "oss"}
steps = [
download_grabpl_step(),
identify_runner_step(),
@@ -42,38 +37,42 @@ def docs_pipelines(edition, ver_mode, trigger):
]
return pipeline(
name='{}-docs'.format(ver_mode), edition=edition, trigger=trigger, services=[], steps=steps,
name = "{}-docs".format(ver_mode),
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
environment = environment,
)
def lint_docs():
return {
'name': 'lint-docs',
'image': build_image,
'depends_on': [
'yarn-install',
"name": "lint-docs",
"image": build_image,
"depends_on": [
"yarn-install",
],
'environment': {
'NODE_OPTIONS': '--max_old_space_size=8192',
"environment": {
"NODE_OPTIONS": "--max_old_space_size=8192",
},
'commands': [
'yarn run prettier:checkDocs',
"commands": [
"yarn run prettier:checkDocs",
],
}
def trigger_docs_main():
return {
'branch': 'main',
'event': [
'push',
"branch": "main",
"event": [
"push",
],
'paths': docs_paths,
"paths": docs_paths,
}
def trigger_docs_pr():
return {
'event': [
'pull_request',
"event": [
"pull_request",
],
'paths': docs_paths,
"paths": docs_paths,
}

View File

@@ -1,36 +1,40 @@
"""
This module contains steps and pipelines relating to GitHub.
"""
load(
'scripts/drone/steps/lib.star',
'download_grabpl_step',
'publish_images_step',
'compile_build_cmd',
'fetch_images_step',
'publish_image',
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"fetch_images_step",
"publish_image",
)
load('scripts/drone/vault.star', 'from_secret')
load("scripts/drone/vault.star", "from_secret")
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/utils/utils.star",
"pipeline",
)
def publish_github_step():
return {
'name': 'publish-github',
'image': publish_image,
'commands': ['./bin/build publish github --repo $${GH_REGISTRY} --create'],
'depends_on': ['fetch-images-enterprise2'],
'environment': {
'GH_TOKEN': from_secret('github_token'),
'GH_REGISTRY': from_secret('gh_registry'),
"name": "publish-github",
"image": publish_image,
"commands": ["./bin/build publish github --repo $${GH_REGISTRY} --create"],
"depends_on": ["fetch-images-enterprise2"],
"environment": {
"GH_TOKEN": from_secret("github_token"),
"GH_REGISTRY": from_secret("gh_registry"),
},
}
def publish_github_pipeline(mode):
trigger = {
'event': ['promote'],
'target': [mode],
"event": ["promote"],
"target": [mode],
}
return [pipeline(
name='publish-github-{}'.format(mode), trigger=trigger, steps=[compile_build_cmd(), fetch_images_step('enterprise2'), publish_github_step()], edition="", environment = {'EDITION': 'enterprise2'}
),]
name = "publish-github-{}".format(mode),
trigger = trigger,
steps = [compile_build_cmd(), fetch_images_step("enterprise2"), publish_github_step()],
edition = "",
environment = {"EDITION": "enterprise2"},
)]

View File

@@ -1,44 +1,79 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'download_grabpl_step',
'verify_gen_cue_step',
'verify_gen_jsonnet_step',
'wire_install_step',
'postgres_integration_tests_step',
'mysql_integration_tests_step',
'compile_build_cmd',
)
"""
This module returns the pipeline used for integration tests.
"""
load(
'scripts/drone/services/services.star',
'integration_test_services',
'integration_test_services_volumes',
'ldap_service',
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"download_grabpl_step",
"enterprise_setup_step",
"identify_runner_step",
"mysql_integration_tests_step",
"postgres_integration_tests_step",
"verify_gen_cue_step",
"verify_gen_jsonnet_step",
"wire_install_step",
)
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/services/services.star",
"integration_test_services",
"integration_test_services_volumes",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def integration_tests(trigger, ver_mode, edition):
services = integration_test_services(edition)
def integration_tests(trigger, prefix, ver_mode = "pr"):
"""Generate a pipeline for integration tests.
Args:
trigger: controls which events can trigger the pipeline execution.
prefix: used in the naming of the pipeline.
ver_mode: defines the event / origin of this build. In this function, if it is set to pr, then it will attempt to clone grafana-enterprise. Otherwise it has no effect.
Returns:
Drone pipeline.
"""
environment = {"EDITION": "oss"}
services = integration_test_services(edition = "oss")
volumes = integration_test_services_volumes()
init_steps = [
init_steps = []
verify_step = verify_gen_cue_step()
verify_jsonnet_step = verify_gen_jsonnet_step()
if ver_mode == "pr":
# In pull requests, attempt to clone grafana enterprise.
init_steps.append(enterprise_setup_step())
# Ensure that verif_gen_cue happens after we clone enterprise
# At the time of writing this, very_gen_cue is depended on by the wire step which is what everything else depends on.
verify_step["depends_on"].append("clone-enterprise")
verify_jsonnet_step["depends_on"].append("clone-enterprise")
init_steps += [
download_grabpl_step(),
compile_build_cmd(),
identify_runner_step(),
verify_gen_cue_step(edition="oss"),
verify_gen_jsonnet_step(edition="oss"),
verify_step,
verify_jsonnet_step,
wire_install_step(),
]
test_steps = [
postgres_integration_tests_step(edition=edition, ver_mode=ver_mode),
mysql_integration_tests_step(edition=edition, ver_mode=ver_mode),
postgres_integration_tests_step(),
mysql_integration_tests_step(),
]
return pipeline(
name='{}-integration-tests'.format(ver_mode), edition="oss", trigger=trigger, services=services, steps=init_steps + test_steps,
volumes=volumes
name = "{}-integration-tests".format(prefix),
edition = "oss",
trigger = trigger,
environment = environment,
services = services,
volumes = volumes,
steps = init_steps + test_steps,
)

View File

@@ -1,31 +1,60 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'wire_install_step',
'lint_backend_step',
'lint_drone_step',
'compile_build_cmd',
)
"""
This module returns the pipeline used for linting backend code.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"enterprise_setup_step",
"identify_runner_step",
"lint_backend_step",
"lint_drone_step",
"wire_install_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def lint_backend_pipeline(trigger, ver_mode):
"""Generates the pipelines used linting backend code.
Args:
trigger: controls which events can trigger the pipeline execution.
ver_mode: used in the naming of the pipeline.
Returns:
Drone pipeline.
"""
environment = {"EDITION": "oss"}
wire_step = wire_install_step()
wire_step.update({ 'depends_on': [] })
wire_step.update({"depends_on": []})
init_steps = [
identify_runner_step(),
compile_build_cmd(),
wire_step,
]
if ver_mode == "pr":
# In pull requests, attempt to clone grafana enterprise.
init_steps.append(enterprise_setup_step())
wire_step["depends_on"].append("clone-enterprise")
init_steps.append(wire_step)
test_steps = [
lint_backend_step(edition="oss"),
lint_backend_step(),
]
if ver_mode == 'main':
test_steps.extend([lint_drone_step()])
if ver_mode == "main":
test_steps.append(lint_drone_step())
return pipeline(
name='{}-lint-backend'.format(ver_mode), edition="oss", trigger=trigger, services=[], steps=init_steps + test_steps,
name = "{}-lint-backend".format(ver_mode),
edition = "oss",
trigger = trigger,
services = [],
steps = init_steps + test_steps,
environment = environment,
)

View File

@@ -1,25 +1,54 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'yarn_install_step',
'lint_frontend_step',
)
"""
This module returns the pipeline used for linting frontend code.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"enterprise_setup_step",
"identify_runner_step",
"lint_frontend_step",
"yarn_install_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def lint_frontend_pipeline(trigger, ver_mode):
yarn_step = yarn_install_step()
yarn_step.update({ 'depends_on': [] })
init_steps = [
"""Generates the pipelines used linting frontend code.
Args:
trigger: controls which events can trigger the pipeline execution.
ver_mode: used in the naming of the pipeline.
Returns:
Drone pipeline.
"""
environment = {"EDITION": "oss"}
init_steps = []
lint_step = lint_frontend_step()
if ver_mode == "pr":
# In pull requests, attempt to clone grafana enterprise.
init_steps = [enterprise_setup_step()]
# Ensure the lint step happens after the clone-enterprise step
lint_step["depends_on"].append("clone-enterprise")
init_steps += [
identify_runner_step(),
yarn_step,
yarn_install_step(),
]
test_steps = [
lint_frontend_step(),
lint_step,
]
return pipeline(
name='{}-lint-frontend'.format(ver_mode), edition="oss", trigger=trigger, services=[], steps=init_steps + test_steps,
name = "{}-lint-frontend".format(ver_mode),
edition = "oss",
trigger = trigger,
services = [],
steps = init_steps + test_steps,
environment = environment,
)

View File

@@ -1,50 +1,97 @@
load(
'scripts/drone/steps/lib.star',
'download_grabpl_step',
'publish_images_step',
'compile_build_cmd',
'fetch_images_step',
)
"""
This module returns the pipeline used for publishing Docker images and its steps.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"download_grabpl_step",
"fetch_images_step",
"identify_runner_step",
"publish_images_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def publish_image_steps(edition, mode, docker_repo):
additional_docker_repo = ""
if edition == 'oss':
additional_docker_repo='grafana/grafana-oss'
"""Generates the steps used for publising Docker images using grabpl.
Args:
edition: controls which version of an image is fetched in the case of a release.
It also controls which publishing implementation is used.
If edition == 'oss', it additionally publishes the grafana/grafana-oss repository.
mode: uses to control the publishing of security images when mode == 'security'.
docker_repo: the Docker image name.
It is combined with the 'grafana/' library prefix.
Returns:
List of Drone steps.
"""
steps = [
identify_runner_step(),
download_grabpl_step(),
compile_build_cmd(),
fetch_images_step(edition),
publish_images_step(edition, 'release', mode, docker_repo),
publish_images_step(edition, "release", mode, docker_repo),
]
if additional_docker_repo != "":
steps.extend([publish_images_step(edition, 'release', mode, additional_docker_repo)])
if edition == "oss":
steps.append(
publish_images_step(edition, "release", mode, "grafana-oss"),
)
return steps
def publish_image_pipelines_public():
mode='public'
"""Generates the pipeline used for publising public Docker images.
Returns:
Drone pipeline
"""
mode = "public"
trigger = {
'event': ['promote'],
'target': [mode],
"event": ["promote"],
"target": [mode],
}
return [pipeline(
name='publish-docker-oss-{}'.format(mode), trigger=trigger, steps=publish_image_steps(edition='oss', mode=mode, docker_repo='grafana/grafana'), edition=""
), pipeline(
name='publish-docker-enterprise-{}'.format(mode), trigger=trigger, steps=publish_image_steps(edition='enterprise', mode=mode, docker_repo='grafana/grafana-enterprise'), edition=""
),]
return [
pipeline(
name = "publish-docker-oss-{}".format(mode),
trigger = trigger,
steps = publish_image_steps(edition = "oss", mode = mode, docker_repo = "grafana"),
edition = "",
environment = {"EDITION": "oss"},
),
pipeline(
name = "publish-docker-enterprise-{}".format(mode),
trigger = trigger,
steps = publish_image_steps(
edition = "enterprise",
mode = mode,
docker_repo = "grafana-enterprise",
),
edition = "",
environment = {"EDITION": "enterprise"},
),
]
def publish_image_pipelines_security():
mode='security'
mode = "security"
trigger = {
'event': ['promote'],
'target': [mode],
"event": ["promote"],
"target": [mode],
}
return [pipeline(
name='publish-docker-enterprise-{}'.format(mode), trigger=trigger, steps=publish_image_steps(edition='enterprise', mode=mode, docker_repo='grafana/grafana-enterprise'), edition=""
),]
return [
pipeline(
name = "publish-docker-enterprise-{}".format(mode),
trigger = trigger,
steps = publish_image_steps(
edition = "enterprise",
mode = mode,
docker_repo = "grafana-enterprise",
),
edition = "",
environment = {"EDITION": "enterprise"},
),
]

View File

@@ -1,48 +1,50 @@
load(
'scripts/drone/steps/lib.star',
'build_image',
'compile_build_cmd'
)
"""
This module returns a Drone step and pipeline for linting with shellcheck.
"""
load("scripts/drone/steps/lib.star", "build_image", "compile_build_cmd")
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/utils/utils.star",
"pipeline",
)
trigger = {
'event': [
'pull_request',
"event": [
"pull_request",
],
'paths': {
'exclude': [
'*.md',
'docs/**',
'latest.json',
],
'include': [
'scripts/**/*.sh'
"paths": {
"exclude": [
"*.md",
"docs/**",
"latest.json",
],
"include": ["scripts/**/*.sh"],
},
}
def shellcheck_step():
return {
'name': 'shellcheck',
'image': build_image,
'depends_on': [
'compile-build-cmd',
"name": "shellcheck",
"image": build_image,
"depends_on": [
"compile-build-cmd",
],
'commands': [
'./bin/build shellcheck',
"commands": [
"./bin/build shellcheck",
],
}
def shellcheck_pipeline():
environment = {"EDITION": "oss"}
steps = [
compile_build_cmd(),
shellcheck_step(),
]
return pipeline(
name='pr-shellcheck', edition="oss", trigger=trigger, services=[], steps=steps,
name = "pr-shellcheck",
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
environment = environment,
)

View File

@@ -1,42 +1,121 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'download_grabpl_step',
'wire_install_step',
'test_backend_step',
'test_backend_integration_step',
'verify_gen_cue_step',
'verify_gen_jsonnet_step',
'compile_build_cmd',
'clone_enterprise_step',
'init_enterprise_step',
)
"""
This module returns the pipeline used for testing backend code.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"clone_enterprise_step",
"compile_build_cmd",
"download_grabpl_step",
"enterprise_setup_step",
"identify_runner_step",
"init_enterprise_step",
"test_backend_integration_step",
"test_backend_step",
"verify_gen_cue_step",
"verify_gen_jsonnet_step",
"wire_install_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
"with_deps",
)
def test_backend(trigger, ver_mode, edition="oss"):
environment = {'EDITION': edition}
init_steps = []
if edition != 'oss':
init_steps.extend([clone_enterprise_step(ver_mode), download_grabpl_step(), init_enterprise_step(ver_mode),])
init_steps.extend([
def test_backend(trigger, ver_mode):
"""Generates the pipeline used for testing OSS backend code.
Args:
trigger: a Drone trigger for the pipeline.
ver_mode: affects the pipeline name.
Returns:
Drone pipeline.
"""
environment = {"EDITION": "oss"}
steps = []
verify_step = verify_gen_cue_step()
verify_jsonnet_step = verify_gen_jsonnet_step()
if ver_mode == "pr":
# In pull requests, attempt to clone grafana enterprise.
steps.append(enterprise_setup_step())
# Ensure that verif_gen_cue happens after we clone enterprise
# At the time of writing this, very_gen_cue is depended on by the wire step which is what everything else depends on.
verify_step["depends_on"].append("clone-enterprise")
verify_jsonnet_step["depends_on"].append("clone-enterprise")
steps += [
identify_runner_step(),
compile_build_cmd(edition),
verify_gen_cue_step(edition),
verify_gen_jsonnet_step(edition),
compile_build_cmd(edition = "oss"),
verify_step,
verify_jsonnet_step,
wire_install_step(),
])
test_steps = [
test_backend_step(edition),
test_backend_integration_step(edition),
test_backend_step(),
test_backend_integration_step(),
]
pipeline_name = '{}-test-backend'.format(ver_mode)
pipeline_name = "{}-test-backend".format(ver_mode)
if ver_mode in ("release-branch", "release"):
pipeline_name = '{}-{}-test-backend'.format(ver_mode, edition)
pipeline_name = "{}-{}-test-backend".format(ver_mode, "oss")
return pipeline(
name=pipeline_name, edition=edition, trigger=trigger, services=[], steps=init_steps + test_steps, environment=environment
name = pipeline_name,
edition = "oss",
trigger = trigger,
steps = steps,
environment = environment,
)
def test_backend_enterprise(trigger, ver_mode, source, edition = "enterprise"):
"""Generates the pipeline used for testing backend enterprise code.
Args:
trigger: a Drone trigger for the pipeline.
ver_mode: affects the pipeline name.
source: controls what revision of enterprise code to test with. The source of the PR, usually.
edition: affects the clone step in the pipeline and also affects the pipeline name.
Returns:
Drone pipeline.
"""
environment = {"EDITION": edition}
steps = (
[
clone_enterprise_step(source),
download_grabpl_step(),
init_enterprise_step(ver_mode),
identify_runner_step(),
compile_build_cmd(edition),
] +
with_deps(
[
verify_gen_cue_step(),
verify_gen_jsonnet_step(),
],
[
"init-enterprise",
],
) +
[
wire_install_step(),
test_backend_step(),
test_backend_integration_step(),
]
)
pipeline_name = "{}-test-backend".format(ver_mode)
if ver_mode in ("release-branch", "release"):
pipeline_name = "{}-{}-test-backend".format(ver_mode, edition)
return pipeline(
name = pipeline_name,
edition = edition,
trigger = trigger,
steps = steps,
environment = environment,
)

View File

@@ -1,36 +1,103 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'clone_enterprise_step',
'init_enterprise_step',
'download_grabpl_step',
'yarn_install_step',
'betterer_frontend_step',
'test_frontend_step',
)
"""
This module returns the pipeline used for testing backend code.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"betterer_frontend_step",
"clone_enterprise_step",
"download_grabpl_step",
"enterprise_setup_step",
"identify_runner_step",
"init_enterprise_step",
"test_frontend_step",
"yarn_install_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
"with_deps",
)
def test_frontend(trigger, ver_mode, edition="oss"):
environment = {'EDITION': edition}
init_steps = []
if edition != 'oss':
init_steps.extend([clone_enterprise_step(ver_mode), init_enterprise_step(ver_mode),])
init_steps.extend([
def test_frontend(trigger, ver_mode):
"""Generates the pipeline used for testing frontend code.
Args:
trigger: a Drone trigger for the pipeline
ver_mode: indirectly controls which revision of enterprise code to use.
Returns:
Drone pipeline.
"""
environment = {"EDITION": "oss"}
steps = [
identify_runner_step(),
download_grabpl_step(),
yarn_install_step(edition),
])
test_steps = [
betterer_frontend_step(edition),
test_frontend_step(edition),
yarn_install_step(),
betterer_frontend_step(edition = "oss"),
]
pipeline_name = '{}-test-frontend'.format(ver_mode)
pipeline_name = "{}-test-frontend".format(ver_mode)
test_step = test_frontend_step(edition = "oss")
if ver_mode == "pr":
# In pull requests, attempt to clone grafana enterprise.
steps.append(enterprise_setup_step())
# Also, make the test step depend on 'clone-enterprise
test_step["depends_on"].append("clone-enterprise")
steps.append(test_step)
pipeline_name = "{}-test-frontend".format(ver_mode)
if ver_mode in ("release-branch", "release"):
pipeline_name = '{}-{}-test-frontend'.format(ver_mode, edition)
pipeline_name = "{}-oss-test-frontend".format(ver_mode)
return pipeline(
name=pipeline_name, edition=edition, trigger=trigger, services=[], steps=init_steps + test_steps,
name = pipeline_name,
edition = "oss",
trigger = trigger,
steps = steps,
environment = environment,
)
def test_frontend_enterprise(trigger, ver_mode, source, edition = "enterprise"):
"""Generates the pipeline used for testing frontend enterprise code.
Args:
trigger: a Drone trigger for the pipeline.
ver_mode: affects the pipeline name.
source: controls what revision of Grafana code to test with.
edition: affects the clone step in the pipeline and also affects the pipeline name.
Returns:
Drone pipeline.
"""
environment = {"EDITION": edition}
steps = (
[
clone_enterprise_step(source),
init_enterprise_step(ver_mode),
identify_runner_step(),
download_grabpl_step(),
] +
with_deps([yarn_install_step()], ["init-enterprise"]) +
[
betterer_frontend_step(edition),
test_frontend_step(edition),
]
)
pipeline_name = "{}-test-frontend".format(ver_mode)
if ver_mode in ("release-branch", "release"):
pipeline_name = "{}-{}-test-frontend".format(ver_mode, edition)
return pipeline(
name = pipeline_name,
edition = edition,
trigger = trigger,
steps = steps,
environment = environment,
)

View File

@@ -1,28 +1,45 @@
load(
'scripts/drone/steps/lib.star',
'enterprise_downstream_step',
)
"""
This module returns the pipeline used for triggering a downstream pipeline for Grafana Enterprise.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"enterprise_downstream_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
trigger = {
'event': ['push',],
'branch': 'main',
'paths': {
'exclude': [
'*.md',
'docs/**',
'latest.json',
"event": [
"push",
],
"branch": "main",
"paths": {
"exclude": [
"*.md",
"docs/**",
"latest.json",
],
},
}
def enterprise_downstream_pipeline(edition, ver_mode):
steps = [enterprise_downstream_step(edition, ver_mode)]
deps = ['main-build-e2e-publish', 'main-integration-tests']
def enterprise_downstream_pipeline():
environment = {"EDITION": "oss"}
steps = [
enterprise_downstream_step(ver_mode = "main"),
]
deps = [
"main-build-e2e-publish",
"main-integration-tests",
]
return pipeline(
name='main-trigger-downstream', edition=edition, trigger=trigger, services=[], steps=steps, depends_on=deps
)
name = "main-trigger-downstream",
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
depends_on = deps,
environment = environment,
)

View File

@@ -1,17 +1,21 @@
load(
'scripts/drone/steps/lib.star',
'identify_runner_step',
'download_grabpl_step',
'lint_drone_step',
'compile_build_cmd',
)
"""
This module returns the pipeline used for verifying Drone configuration.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"download_grabpl_step",
"identify_runner_step",
"lint_drone_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def verify_drone(trigger, ver_mode):
environment = {"EDITION": "oss"}
steps = [
identify_runner_step(),
download_grabpl_step(),
@@ -19,5 +23,10 @@ def verify_drone(trigger, ver_mode):
lint_drone_step(),
]
return pipeline(
name='{}-verify-drone'.format(ver_mode), edition="oss", trigger=trigger, services=[], steps=steps,
name = "{}-verify-drone".format(ver_mode),
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
environment = environment,
)

View File

@@ -0,0 +1,32 @@
"""
This module returns a Drone pipeline that verifies all Starlark files are linted.
"""
load(
"scripts/drone/steps/lib.star",
"compile_build_cmd",
"download_grabpl_step",
"identify_runner_step",
"lint_starlark_step",
)
load(
"scripts/drone/utils/utils.star",
"pipeline",
)
def verify_starlark(trigger, ver_mode):
environment = {"EDITION": "oss"}
steps = [
identify_runner_step(),
download_grabpl_step(),
compile_build_cmd(),
lint_starlark_step(),
]
return pipeline(
name = "{}-verify-starlark".format(ver_mode),
edition = "oss",
trigger = trigger,
services = [],
steps = steps,
environment = environment,
)

View File

@@ -1,134 +1,41 @@
load(
'scripts/drone/steps/lib.star',
'grabpl_version',
'wix_image',
'identify_runner_step',
)
"""
This module returns the pipeline used for building Grafana on Windows.
"""
load(
'scripts/drone/utils/utils.star',
'pipeline',
"scripts/drone/utils/utils.star",
"pipeline",
)
load(
"scripts/drone/steps/lib.star",
"get_windows_steps",
)
load('scripts/drone/vault.star', 'from_secret', 'prerelease_bucket', 'github_token')
def windows(trigger, edition, ver_mode):
init_cmds = []
sfx = ''
if edition in ('enterprise', 'enterprise2'):
sfx = '-{}'.format(edition)
else:
init_cmds.extend([
'$$ProgressPreference = "SilentlyContinue"',
'Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/{}/windows/grabpl.exe -OutFile grabpl.exe'.format(
grabpl_version),
])
steps = [
{
'name': 'windows-init',
'image': wix_image,
'commands': init_cmds,
},
]
if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2'))) or ver_mode in (
'release', 'release-branch',
):
bucket = '%PRERELEASE_BUCKET%/artifacts/downloads'
if ver_mode == 'release':
ver_part = '${DRONE_TAG}'
dir = 'release'
else:
dir = 'main'
bucket = 'grafana-downloads'
build_no = 'DRONE_BUILD_NUMBER'
ver_part = '--build-id $$env:{}'.format(build_no)
installer_commands = [
'$$gcpKey = $$env:GCP_KEY',
'[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) > gcpkey.json',
# gcloud fails to read the file unless converted with dos2unix
'dos2unix gcpkey.json',
'gcloud auth activate-service-account --key-file=gcpkey.json',
'rm gcpkey.json',
'cp C:\\App\\nssm-2.24.zip .',
]
if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2'))) or ver_mode in (
'release',
):
installer_commands.extend([
'.\\grabpl.exe windows-installer --edition {} {}'.format(edition, ver_part),
'$$fname = ((Get-Childitem grafana*.msi -name) -split "`n")[0]',
])
if ver_mode == 'main':
installer_commands.extend([
'gsutil cp $$fname gs://{}/{}/{}/'.format(bucket, edition, dir),
'gsutil cp "$$fname.sha256" gs://{}/{}/{}/'.format(bucket, edition, dir),
])
else:
installer_commands.extend([
'gsutil cp $$fname gs://{}/{}/{}/{}/'.format(bucket, ver_part, edition, dir),
'gsutil cp "$$fname.sha256" gs://{}/{}/{}/{}/'.format(bucket, ver_part, edition, dir),
])
steps.append({
'name': 'build-windows-installer',
'image': wix_image,
'depends_on': [
'windows-init',
],
'environment': {
'GCP_KEY': from_secret('gcp_key'),
'PRERELEASE_BUCKET': from_secret(prerelease_bucket),
'GITHUB_TOKEN': from_secret('github_token')
},
'commands': installer_commands,
})
"""Generates the pipeline used for building Grafana on Windows.
if edition in ('enterprise', 'enterprise2'):
if ver_mode == 'release':
committish = '${DRONE_TAG}'
elif ver_mode == 'release-branch':
committish = '$$env:DRONE_BRANCH'
else:
committish = '$$env:DRONE_COMMIT'
# For enterprise, we have to clone both OSS and enterprise and merge the latter into the former
download_grabpl_step_cmds = [
'$$ProgressPreference = "SilentlyContinue"',
'Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/{}/windows/grabpl.exe -OutFile grabpl.exe'.format(
grabpl_version),
]
clone_cmds = [
'git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"',
]
clone_cmds.extend([
'cd grafana-enterprise',
'git checkout {}'.format(committish),
])
steps.insert(0, {
'name': 'clone',
'image': wix_image,
'environment': {
'GITHUB_TOKEN': from_secret(github_token),
},
'commands': download_grabpl_step_cmds + clone_cmds,
})
steps[1]['depends_on'] = [
'clone',
]
steps[1]['commands'].extend([
# Need to move grafana-enterprise out of the way, so directory is empty and can be cloned into
'cp -r grafana-enterprise C:\\App\\grafana-enterprise',
'rm -r -force grafana-enterprise',
'cp grabpl.exe C:\\App\\grabpl.exe',
'rm -force grabpl.exe',
'C:\\App\\grabpl.exe init-enterprise --github-token $$env:GITHUB_TOKEN C:\\App\\grafana-enterprise',
'cp C:\\App\\grabpl.exe grabpl.exe',
])
if 'environment' in steps[1]:
steps[1]['environment'] + {'GITHUB_TOKEN': from_secret(github_token)}
else:
steps[1]['environment'] = {'GITHUB_TOKEN': from_secret(github_token)}
Args:
trigger: a Drone trigger for the pipeline.
edition: controls whether enterprise code is included in the pipeline steps.
ver_mode: controls whether a pre-release or actual release pipeline is generated.
Also indirectly controls which version of enterprise code is used.
Returns:
Drone pipeline.
"""
environment = {"EDITION": edition}
return pipeline(
name='main-windows', edition=edition, trigger=dict(trigger, repo=['grafana/grafana']),
steps=[identify_runner_step('windows')] + steps,
depends_on=['main-test-frontend', 'main-test-backend', 'main-build-e2e-publish', 'main-integration-tests'], platform='windows',
name = "main-windows",
edition = edition,
trigger = dict(trigger, repo = ["grafana/grafana"]),
steps = get_windows_steps(edition, ver_mode),
depends_on = [
"main-test-frontend",
"main-test-backend",
"main-build-e2e-publish",
"main-integration-tests",
],
platform = "windows",
environment = environment,
)

View File

@@ -1,61 +1,66 @@
"""
This module has functions for Drone services to be used in pipelines.
"""
def integration_test_services_volumes():
return [
{ 'name': 'postgres', 'temp': { 'medium': 'memory' } },
{ 'name': 'mysql', 'temp': { 'medium': 'memory' }
}]
{"name": "postgres", "temp": {"medium": "memory"}},
{"name": "mysql", "temp": {"medium": "memory"}},
]
def integration_test_services(edition):
services = [
{
'name': 'postgres',
'image': 'postgres:12.3-alpine',
'environment': {
'POSTGRES_USER': 'grafanatest',
'POSTGRES_PASSWORD': 'grafanatest',
'POSTGRES_DB': 'grafanatest',
'PGDATA': '/var/lib/postgresql/data/pgdata',
"name": "postgres",
"image": "postgres:12.3-alpine",
"environment": {
"POSTGRES_USER": "grafanatest",
"POSTGRES_PASSWORD": "grafanatest",
"POSTGRES_DB": "grafanatest",
"PGDATA": "/var/lib/postgresql/data/pgdata",
},
'volumes': [{
'name': 'postgres',
'path': '/var/lib/postgresql/data/pgdata'
}],
"volumes": [
{"name": "postgres", "path": "/var/lib/postgresql/data/pgdata"},
],
},
{
'name': 'mysql',
'image': 'mysql:5.7.39',
'environment': {
'MYSQL_ROOT_PASSWORD': 'rootpass',
'MYSQL_DATABASE': 'grafana_tests',
'MYSQL_USER': 'grafana',
'MYSQL_PASSWORD': 'password',
"name": "mysql",
"image": "mysql:5.7.39",
"environment": {
"MYSQL_ROOT_PASSWORD": "rootpass",
"MYSQL_DATABASE": "grafana_tests",
"MYSQL_USER": "grafana",
"MYSQL_PASSWORD": "password",
},
'volumes': [{
'name': 'mysql',
'path': '/var/lib/mysql'
}]
"volumes": [{"name": "mysql", "path": "/var/lib/mysql"}],
},
]
if edition in ('enterprise', 'enterprise2'):
services.extend([{
'name': 'redis',
'image': 'redis:6.2.1-alpine',
'environment': {},
}, {
'name': 'memcached',
'image': 'memcached:1.6.9-alpine',
'environment': {},
}])
if edition in ("enterprise", "enterprise2"):
services.extend(
[
{
"name": "redis",
"image": "redis:6.2.1-alpine",
"environment": {},
},
{
"name": "memcached",
"image": "memcached:1.6.9-alpine",
"environment": {},
},
],
)
return services
def ldap_service():
return {
'name': 'ldap',
'image': 'osixia/openldap:1.4.0',
'environment': {
'LDAP_ADMIN_PASSWORD': 'grafana',
'LDAP_DOMAIN': 'grafana.org',
'SLAPD_ADDITIONAL_MODULES': 'memberof',
"name": "ldap",
"image": "osixia/openldap:1.4.0",
"environment": {
"LDAP_ADMIN_PASSWORD": "grafana",
"LDAP_DOMAIN": "grafana.org",
"SLAPD_ADDITIONAL_MODULES": "memberof",
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,136 @@
"""
This module contains utility functions for generating Drone pipelines.
"""
load(
'scripts/drone/steps/lib.star',
'download_grabpl_step',
'slack_step',
"scripts/drone/steps/lib.star",
"slack_step",
)
load("scripts/drone/vault.star", "pull_secret")
load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token')
failure_template = 'Build {{build.number}} failed for commit: <https://github.com/{{repo.owner}}/{{repo.name}}/commit/{{build.commit}}|{{ truncate build.commit 8 }}>: {{build.link}}\nBranch: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commits/{{ build.branch }}|{{ build.branch }}>\nAuthor: {{build.author}}'
drone_change_template = '`.drone.yml` and `starlark` files have been changed on the OSS repo, by: {{build.author}}. \nBranch: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commits/{{ build.branch }}|{{ build.branch }}>\nCommit hash: <https://github.com/{{repo.owner}}/{{repo.name}}/commit/{{build.commit}}|{{ truncate build.commit 8 }}>'
failure_template = "Build {{build.number}} failed for commit: <https://github.com/{{repo.owner}}/{{repo.name}}/commit/{{build.commit}}|{{ truncate build.commit 8 }}>: {{build.link}}\nBranch: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commits/{{ build.branch }}|{{ build.branch }}>\nAuthor: {{build.author}}"
drone_change_template = "`.drone.yml` and `starlark` files have been changed on the OSS repo, by: {{build.author}}. \nBranch: <https://github.com/{{ repo.owner }}/{{ repo.name }}/commits/{{ build.branch }}|{{ build.branch }}>\nCommit hash: <https://github.com/{{repo.owner}}/{{repo.name}}/commit/{{build.commit}}|{{ truncate build.commit 8 }}>"
def pipeline(
name, edition, trigger, steps, services=[], platform='linux', depends_on=[], environment=None, volumes=[],
):
if platform != 'windows':
name,
edition,
trigger,
steps,
services = [],
platform = "linux",
depends_on = [],
environment = None,
volumes = []):
"""Generate a Drone Docker pipeline with commonly used values.
In addition to the parameters provided, it configures:
- the use of an image pull secret
- a retry count for cloning
- a volume 'docker' that can be used to access the Docker socket
Args:
name: controls the pipeline name.
edition: used to differentiate the pipeline for enterprise builds.
trigger: a Drone trigger for the pipeline.
steps: the Drone steps for the pipeline.
services: auxilliary services used during the pipeline.
Defaults to [].
platform: abstracts platform specific configuration primarily for different Drone behavior on Windows.
Defaults to 'linux'.
depends_on: list of pipelines that must have succeeded before this pipeline can start.
Defaults to [].
environment: environment variables passed through to pipeline steps.
Defaults to None.
volumes: additional volumes available to be mounted by pipeline steps.
Defaults to [].
Returns:
Drone pipeline
"""
if platform != "windows":
platform_conf = {
'platform': {
'os': 'linux',
'arch': 'amd64'
},
"platform": {"os": "linux", "arch": "amd64"},
# A shared cache is used on the host
# To avoid issues with parallel builds, we run this repo on single build agents
'node': {
'type': 'no-parallel'
}
"node": {"type": "no-parallel"},
}
else:
platform_conf = {
'platform': {
'os': 'windows',
'arch': 'amd64',
'version': '1809',
}
"platform": {
"os": "windows",
"arch": "amd64",
"version": "1809",
},
}
pipeline = {
'kind': 'pipeline',
'type': 'docker',
'name': name,
'trigger': trigger,
'services': services,
'steps': steps,
'clone': {
'retries': 3,
"kind": "pipeline",
"type": "docker",
"name": name,
"trigger": trigger,
"services": services,
"steps": steps,
"clone": {
"retries": 3,
},
'volumes': [{
'name': 'docker',
'host': {
'path': '/var/run/docker.sock',
"volumes": [
{
"name": "docker",
"host": {
"path": "/var/run/docker.sock",
},
},
}],
'depends_on': depends_on,
'image_pull_secrets': [pull_secret],
],
"depends_on": depends_on,
"image_pull_secrets": [pull_secret],
}
if environment:
pipeline.update({
'environment': environment,
})
pipeline.update(
{
"environment": environment,
},
)
pipeline['volumes'].extend(volumes)
pipeline["volumes"].extend(volumes)
pipeline.update(platform_conf)
if edition in ('enterprise', 'enterprise2'):
if edition in ("enterprise", "enterprise2"):
# We have a custom clone step for enterprise
pipeline['clone'] = {
'disable': True,
pipeline["clone"] = {
"disable": True,
}
return pipeline
def notify_pipeline(name, slack_channel, trigger, depends_on=[], template=None, secret=None):
def notify_pipeline(
name,
slack_channel,
trigger,
depends_on = [],
template = None,
secret = None):
trigger = dict(trigger)
return {
'kind': 'pipeline',
'type': 'docker',
'platform': {
'os': 'linux',
'arch': 'amd64',
"kind": "pipeline",
"type": "docker",
"platform": {
"os": "linux",
"arch": "amd64",
},
'name': name,
'trigger': trigger,
'steps': [
"name": name,
"trigger": trigger,
"steps": [
slack_step(slack_channel, template, secret),
],
'clone': {
'retries': 3,
"clone": {
"retries": 3,
},
'depends_on': depends_on,
"depends_on": depends_on,
}
# TODO: this overrides any existing dependencies because we're following the existing logic
# it should append to any existing dependencies
def with_deps(steps, deps = []):
for step in steps:
step["depends_on"] = deps
return steps

View File

@@ -1,82 +1,97 @@
pull_secret = 'dockerconfigjson'
github_token = 'github_token'
drone_token = 'drone_token'
prerelease_bucket = 'prerelease_bucket'
gcp_upload_artifacts_key = 'gcp_upload_artifacts_key'
azure_sp_app_id = 'azure_sp_app_id'
azure_sp_app_pw = 'azure_sp_app_pw'
azure_tenant = 'azure_tenant'
"""
This module returns functions for generating Drone secrets fetched from Vault.
"""
pull_secret = "dockerconfigjson"
drone_token = "drone_token"
prerelease_bucket = "prerelease_bucket"
gcp_upload_artifacts_key = "gcp_upload_artifacts_key"
azure_sp_app_id = "azure_sp_app_id"
azure_sp_app_pw = "azure_sp_app_pw"
azure_tenant = "azure_tenant"
def from_secret(secret):
return {
'from_secret': secret
}
return {"from_secret": secret}
def vault_secret(name, path, key):
return {
'kind': 'secret',
'name': name,
'get': {
'path': path,
'name': key,
}
"kind": "secret",
"name": name,
"get": {
"path": path,
"name": key,
},
}
def secrets():
return [
vault_secret(pull_secret, 'secret/data/common/gcr', '.dockerconfigjson'),
vault_secret(github_token, 'infra/data/ci/github/grafanabot', 'pat'),
vault_secret(drone_token, 'infra/data/ci/drone', 'machine-user-token'),
vault_secret(prerelease_bucket, 'infra/data/ci/grafana/prerelease', 'bucket'),
vault_secret(gcp_upload_artifacts_key, 'infra/data/ci/grafana/releng/artifacts-uploader-service-account', 'credentials.json'),
vault_secret(azure_sp_app_id, 'infra/data/ci/datasources/cpp-azure-resourcemanager-credentials', 'application_id'),
vault_secret(azure_sp_app_pw, 'infra/data/ci/datasources/cpp-azure-resourcemanager-credentials', 'application_secret'),
vault_secret(azure_tenant, 'infra/data/ci/datasources/cpp-azure-resourcemanager-credentials', 'tenant_id'),
vault_secret(pull_secret, "secret/data/common/gcr", ".dockerconfigjson"),
vault_secret("github_token", "infra/data/ci/github/grafanabot", "pat"),
vault_secret(drone_token, "infra/data/ci/drone", "machine-user-token"),
vault_secret(prerelease_bucket, "infra/data/ci/grafana/prerelease", "bucket"),
vault_secret(
gcp_upload_artifacts_key,
"infra/data/ci/grafana/releng/artifacts-uploader-service-account",
"credentials.json",
),
vault_secret(
azure_sp_app_id,
"infra/data/ci/datasources/cpp-azure-resourcemanager-credentials",
"application_id",
),
vault_secret(
azure_sp_app_pw,
"infra/data/ci/datasources/cpp-azure-resourcemanager-credentials",
"application_secret",
),
vault_secret(
azure_tenant,
"infra/data/ci/datasources/cpp-azure-resourcemanager-credentials",
"tenant_id",
),
# Package publishing
vault_secret(
'packages_gpg_public_key',
'infra/data/ci/packages-publish/gpg',
'public-key-b64',
"packages_gpg_public_key",
"infra/data/ci/packages-publish/gpg",
"public-key-b64",
),
vault_secret(
'packages_gpg_private_key',
'infra/data/ci/packages-publish/gpg',
'private-key-b64',
"packages_gpg_private_key",
"infra/data/ci/packages-publish/gpg",
"private-key-b64",
),
vault_secret(
'packages_gpg_passphrase',
'infra/data/ci/packages-publish/gpg',
'passphrase',
"packages_gpg_passphrase",
"infra/data/ci/packages-publish/gpg",
"passphrase",
),
vault_secret(
'packages_service_account',
'infra/data/ci/packages-publish/service-account',
'credentials.json',
"packages_service_account",
"infra/data/ci/packages-publish/service-account",
"credentials.json",
),
vault_secret(
'packages_access_key_id',
'infra/data/ci/packages-publish/bucket-credentials',
'AccessID',
"packages_access_key_id",
"infra/data/ci/packages-publish/bucket-credentials",
"AccessID",
),
vault_secret(
'packages_secret_access_key',
'infra/data/ci/packages-publish/bucket-credentials',
'Secret',
"packages_secret_access_key",
"infra/data/ci/packages-publish/bucket-credentials",
"Secret",
),
vault_secret(
'aws_region',
'secret/data/common/aws-marketplace',
'aws_region',
"aws_region",
"secret/data/common/aws-marketplace",
"aws_region",
),
vault_secret(
'aws_access_key_id',
'secret/data/common/aws-marketplace',
'aws_access_key_id',
"aws_access_key_id",
"secret/data/common/aws-marketplace",
"aws_access_key_id",
),
vault_secret(
'aws_secret_access_key',
'secret/data/common/aws-marketplace',
'aws_secret_access_key',
"aws_secret_access_key",
"secret/data/common/aws-marketplace",
"aws_secret_access_key",
),
]

View File

@@ -1,11 +1,20 @@
"""
This module returns the pipeline used for version branches.
"""
load(
'scripts/drone/events/release.star',
'release_pipelines',
"scripts/drone/events/release.star",
"enterprise2_pipelines",
"enterprise_pipelines",
"oss_pipelines",
)
ver_mode = 'release-branch'
ver_mode = "release-branch"
trigger = {"ref": ["refs/heads/v[0-9]*"]}
def version_branch_pipelines():
return release_pipelines(ver_mode=ver_mode, trigger={
'ref': ['refs/heads/v[0-9]*'],
})
return (
oss_pipelines(ver_mode = ver_mode, trigger = trigger) +
enterprise_pipelines(ver_mode = ver_mode, trigger = trigger) +
enterprise2_pipelines(ver_mode = ver_mode, trigger = trigger)
)