Remove drone & dead code in pkg/build; update go modules (#109935)

* remove drone & dead code in pkg/build; update go modules

* remove .drone.star

* Remove drone scripts and drone references in Makefile

* make update-workspace

* remove deadcode tool

* Remove daggerbuild/scripts: deadcode

* Remove drone files / folders in CODEOWNERS

* make update-workspace

* remove more dead code
This commit is contained in:
Kevin Minehart
2025-08-20 14:27:58 -05:00
committed by GitHub
parent 196506a484
commit 7c2945bb54
175 changed files with 311 additions and 15727 deletions
-127
View File
@@ -1,127 +0,0 @@
package main
import (
"fmt"
"log"
"os/exec"
"path/filepath"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/docker"
"github.com/grafana/grafana/pkg/build/gcloud"
)
const (
alpine = "alpine"
ubuntu = "ubuntu"
)
// GetImageFiles returns the list of image (.img, but should be .tar because they are tar archives) files that are
// created in the 'tag' process and stored in the prerelease bucket, waiting to be released.
func GetImageFiles(grafana string, version string, architectures []config.Architecture) []string {
bases := []string{alpine, ubuntu}
images := []string{}
for _, base := range bases {
for _, arch := range architectures {
image := fmt.Sprintf("%s-%s-%s.img", grafana, version, arch)
if base == "ubuntu" {
image = fmt.Sprintf("%s-%s-ubuntu-%s.img", grafana, version, arch)
}
images = append(images, image)
}
}
return images
}
func FetchImages(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
}
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil {
return err
}
cfg := docker.Config{
Archs: buildConfig.Docker.Architectures,
Distribution: buildConfig.Docker.Distribution,
Bucket: buildConfig.Docker.PrereleaseBucket,
Edition: c.String("edition"),
Tag: metadata.GrafanaVersion,
}
grafana := "grafana"
if cfg.Edition == "enterprise" {
grafana = "grafana-enterprise"
}
if cfg.Edition == "enterprise2" {
grafana = "grafana-enterprise2"
}
if cfg.Edition == "grafana" || cfg.Edition == "oss" {
grafana = "grafana-oss"
}
baseURL := fmt.Sprintf("gs://%s/%s/", cfg.Bucket, cfg.Tag)
images := GetImageFiles(grafana, cfg.Tag, cfg.Archs)
log.Printf("Fetching images [%v]", images)
if err := gcloud.ActivateServiceAccount(); err != nil {
return err
}
if err := DownloadImages(baseURL, images, "."); err != nil {
return err
}
if err := LoadImages(images, "."); err != nil {
return err
}
return nil
}
// LoadImages uses the `docker load -i` command to load the image tar file into the docker daemon so that it can be
// tagged and pushed.
func LoadImages(images []string, source string) error {
p := filepath.Clean(source)
for _, image := range images {
image := filepath.Join(p, image)
log.Println("Loading image", image)
//nolint:gosec
cmd := exec.Command("docker", "load", "-i", image)
cmd.Dir = "."
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("out: %s\n", out)
return fmt.Errorf("error loading image: %q", err)
}
log.Println("Loaded image", image)
}
log.Println("Images successfully loaded!")
return nil
}
func DownloadImages(baseURL string, images []string, destination string) error {
for _, image := range images {
p := baseURL + image
log.Println("Downloading image", p)
//nolint:gosec
cmd := exec.Command("gsutil", "-m", "cp", "-r", p, destination)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to download: %w\n%s", err, out)
}
}
return nil
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"testing"
"github.com/grafana/grafana/pkg/build/config"
"github.com/stretchr/testify/require"
)
func TestGetImageFiles(t *testing.T) {
var (
architectures = []config.Architecture{
config.ArchAMD64,
config.ArchARM64,
config.ArchARMv7,
}
)
t.Run("1.2.3", func(t *testing.T) {
expect := []string{
"grafana-oss-1.2.3-amd64.img",
"grafana-oss-1.2.3-arm64.img",
"grafana-oss-1.2.3-armv7.img",
"grafana-oss-1.2.3-ubuntu-amd64.img",
"grafana-oss-1.2.3-ubuntu-arm64.img",
"grafana-oss-1.2.3-ubuntu-armv7.img",
}
res := GetImageFiles("grafana-oss", "1.2.3", architectures)
require.Equal(t, expect, res)
})
t.Run("1.2.3+example-01", func(t *testing.T) {
expect := []string{
"grafana-oss-1.2.3+example-01-amd64.img",
"grafana-oss-1.2.3+example-01-arm64.img",
"grafana-oss-1.2.3+example-01-armv7.img",
"grafana-oss-1.2.3+example-01-ubuntu-amd64.img",
"grafana-oss-1.2.3+example-01-ubuntu-arm64.img",
"grafana-oss-1.2.3+example-01-ubuntu-armv7.img",
}
res := GetImageFiles("grafana-oss", "1.2.3+example-01", architectures)
require.Equal(t, expect, res)
})
}
-46
View File
@@ -1,46 +0,0 @@
package main
import "github.com/urfave/cli/v2"
var (
jobsFlag = cli.IntFlag{
Name: "jobs",
Usage: "Number of parallel jobs",
}
buildIDFlag = cli.StringFlag{
Name: "build-id",
Usage: "Optionally supply a build ID to be part of the version",
}
editionFlag = cli.StringFlag{
Name: "edition",
Usage: "The edition of Grafana to build (oss or enterprise)",
Value: "oss",
}
triesFlag = cli.IntFlag{
Name: "tries",
Usage: "Specify number of tries before failing",
Value: 1,
}
tagFlag = cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
}
securityFlag = cli.BoolFlag{
Name: "security",
Usage: "Security release",
}
srcFlag = cli.StringFlag{
Name: "src-bucket",
Value: "grafana-prerelease",
Usage: "Google Cloud Storage bucket",
}
securityDestBucketFlag = cli.StringFlag{
Name: "security-dest-bucket",
Usage: "Google Cloud Storage bucket for security packages (or $SECURITY_DEST_BUCKET)",
}
destFlag = cli.StringFlag{
Name: "dest-bucket",
Value: "grafana-downloads",
Usage: "Google Cloud Storage bucket for published packages",
}
)
-406
View File
@@ -1,406 +0,0 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"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/gcom"
"github.com/grafana/grafana/pkg/build/packaging"
"github.com/grafana/grafana/pkg/build/versions"
)
const grafanaAPI = "https://grafana.com/api"
var httpClient = http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {
return dialer.DialContext
}(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
// GrafanaCom implements the sub-command "grafana-com".
func GrafanaCom(c *cli.Context) error {
bucketStr := c.String("src-bucket")
edition := config.Edition(c.String("edition"))
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
}
releaseMode, err := metadata.GetReleaseMode()
if err != nil {
return err
}
version := metadata.GrafanaVersion
semver := versions.ParseSemver(version)
if releaseMode.Mode == config.Cronjob {
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(bucketStr)
latestMainVersion, err := storage.GetLatestMainBuild(c.Context, bucket, filepath.Join(string(edition), "main"))
if err != nil {
return err
}
version = latestMainVersion
}
dryRun := c.Bool("dry-run")
simulateRelease := c.Bool("simulate-release")
// Test release mode and dryRun imply simulateRelease
if releaseMode.IsTest || dryRun {
simulateRelease = true
}
grafanaAPIKey := strings.TrimSpace(os.Getenv("GRAFANA_COM_API_KEY"))
if grafanaAPIKey == "" {
return cli.Exit("the environment variable GRAFANA_COM_API_KEY must be set", 1)
}
pkgjson, err := getPackageJSON()
if err != nil {
return cli.Exit(err.Error(), 1)
}
whatsNewURL, releaseNotesURL, err := getReleaseURLs(semver, pkgjson)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// TODO: Verify config values
cfg := packaging.PublishConfig{
Config: config.Config{
Version: version,
},
Edition: edition,
ReleaseMode: releaseMode,
GrafanaAPIKey: grafanaAPIKey,
WhatsNewURL: whatsNewURL,
ReleaseNotesURL: releaseNotesURL,
DryRun: dryRun,
TTL: c.String("ttl"),
SimulateRelease: simulateRelease,
}
if err := publishPackages(cfg); err != nil {
return cli.Exit(err.Error(), 1)
}
log.Println("Successfully published packages to grafana.com!")
return nil
}
type grafanaConf struct {
WhatsNewURL string `json:"whatsNewUrl"`
ReleaseNotesURL string `json:"releaseNotesUrl"`
}
type packageConf struct {
Grafana grafanaConf `json:"grafana"`
}
func getPackageJSON() (*packageConf, error) {
pkgB, err := os.ReadFile("package.json")
if err != nil {
return nil, fmt.Errorf("failed to read package.json: %w", err)
}
var pconf packageConf
if err := json.Unmarshal(pkgB, &pconf); err != nil {
return nil, fmt.Errorf("failed to decode package.json: %w", err)
}
return &pconf, nil
}
func getReleaseURLs(semver versions.Semver, pconf *packageConf) (string, string, error) {
u := fmt.Sprintf(pconf.Grafana.WhatsNewURL, semver.Major, semver.Minor, semver.Patch)
if _, err := url.ParseRequestURI(u); err != nil {
return "", "", fmt.Errorf("grafana.whatsNewUrl is invalid in package.json: %q", pconf.Grafana.WhatsNewURL)
}
if _, err := url.ParseRequestURI(pconf.Grafana.ReleaseNotesURL); err != nil {
return "", "", fmt.Errorf("grafana.releaseNotesUrl is invalid in package.json: %q",
pconf.Grafana.ReleaseNotesURL)
}
return u, pconf.Grafana.ReleaseNotesURL, nil
}
func Builds(baseURL *url.URL, grafana, version string, packages []packaging.BuildArtifact) ([]GCOMPackage, error) {
builds := make([]GCOMPackage, len(packages))
for i, v := range packages {
var (
os = v.Distro
arch = v.Arch
)
if v.Distro == "windows" {
os = "win"
if v.Ext == "msi" {
os = "win-installer"
}
}
if v.Distro == "rhel" {
if arch == "aarch64" {
arch = "arm64"
}
if arch == "x86_64" {
arch = "amd64"
}
}
if v.Distro == "deb" {
if arch == "armhf" {
arch = "armv7"
if v.RaspberryPi {
log.Println(v.Distro, arch, "raspberrypi == true")
arch = "armv6"
}
}
}
u := gcom.GetURL(baseURL, version, grafana, v.Distro, v.Arch, v.Ext, v.Musl, v.RaspberryPi)
builds[i] = GCOMPackage{
OS: os,
URL: u.String(),
Arch: arch,
}
}
return builds, nil
}
// publishPackages publishes packages to grafana.com.
func publishPackages(cfg packaging.PublishConfig) error {
log.Printf("Publishing Grafana packages, version %s, %s edition, %s mode, dryRun: %v, simulating: %v...\n",
cfg.Version, cfg.Edition, cfg.ReleaseMode.Mode, cfg.DryRun, cfg.SimulateRelease)
versionStr := fmt.Sprintf("v%s", cfg.Version)
log.Printf("Creating release %s at grafana.com...\n", versionStr)
var (
pth string
grafana = "grafana"
)
switch cfg.Edition {
case config.EditionOSS:
pth = "oss"
case config.EditionEnterprise:
grafana = "grafana-enterprise"
pth = "enterprise"
default:
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
}
switch cfg.ReleaseMode.Mode {
case config.MainMode, config.DownstreamMode, config.CronjobMode:
pth = path.Join(pth, packaging.MainFolder)
default:
pth = path.Join(pth, packaging.ReleaseFolder)
}
pth = path.Join(pth)
baseArchiveURL := &url.URL{
Scheme: "https",
Host: "dl.grafana.com",
Path: pth,
}
builds, err := Builds(baseArchiveURL, grafana, cfg.Version, packaging.ArtifactConfigs)
if err != nil {
return err
}
r := Release{
Version: cfg.Version,
ReleaseDate: time.Now().UTC(),
Builds: builds,
Stable: cfg.ReleaseMode.Mode == config.TagMode && !cfg.ReleaseMode.IsPreview && !cfg.ReleaseMode.IsTest,
Beta: cfg.ReleaseMode.IsPreview,
Nightly: cfg.ReleaseMode.Mode == config.CronjobMode,
}
if cfg.ReleaseMode.Mode == config.TagMode || r.Beta {
r.WhatsNewURL = cfg.WhatsNewURL
r.ReleaseNotesURL = cfg.ReleaseNotesURL
}
if err := postRequest(cfg, "versions", r, fmt.Sprintf("create release %s", r.Version)); err != nil {
return err
}
if err := postRequest(cfg, fmt.Sprintf("versions/%s", cfg.Version), r,
fmt.Sprintf("update release %s", cfg.Version)); err != nil {
return err
}
for i, v := range r.Builds {
sha, err := getSHA256(v.URL)
if err != nil {
return err
}
r.Builds[i].SHA256 = string(sha)
}
for _, b := range r.Builds {
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages", cfg.Version), b,
fmt.Sprintf("create build %s %s", b.OS, b.Arch)); err != nil {
return err
}
if err := postRequest(cfg, fmt.Sprintf("versions/%s/packages/%s/%s", cfg.Version, b.Arch, b.OS), b,
fmt.Sprintf("update build %s %s", b.OS, b.Arch)); err != nil {
return err
}
}
return nil
}
func getSHA256(u string) ([]byte, error) {
shaURL := fmt.Sprintf("%s.sha256", u)
// nolint:gosec
resp, err := http.Get(shaURL)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Println("failed to close response body, err: %w", err)
}
}()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("failed downloading %s: %s", u, resp.Status)
}
sha256, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return sha256, nil
}
func postRequest(cfg packaging.PublishConfig, pth string, body any, descr string) error {
var sfx string
switch cfg.Edition {
case config.EditionOSS:
case config.EditionEnterprise:
sfx = packaging.EnterpriseSfx
default:
return fmt.Errorf("unrecognized edition %q", cfg.Edition)
}
product := fmt.Sprintf("grafana%s", sfx)
jsonB, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("failed to JSON encode release: %w", err)
}
u, err := constructURL(product, pth)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(jsonB))
if err != nil {
return err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", cfg.GrafanaAPIKey))
req.Header.Add("Content-Type", "application/json")
log.Printf("Posting to grafana.com API, %s - JSON: %s\n", u, string(jsonB))
if cfg.SimulateRelease {
log.Println("Only simulating request")
return nil
}
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Println("failed to close response body, err: %w", err)
}
}()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") {
log.Printf("Already exists: %s\n", descr)
return nil
}
return fmt.Errorf("failed posting to %s (%s): %s", u, descr, resp.Status)
}
log.Printf("Successfully posted to grafana.com API, %s\n", u)
return nil
}
func constructURL(product string, pth string) (string, error) {
productPath := filepath.Clean(filepath.Join("/", product, pth))
u, err := url.Parse(grafanaAPI)
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, productPath)
return u.String(), err
}
type GCOMPackage struct {
OS string `json:"os"`
URL string `json:"url"`
SHA256 string `json:"sha256"`
Arch string `json:"arch"`
}
type Release struct {
Version string `json:"version"`
ReleaseDate time.Time `json:"releaseDate"`
Stable bool `json:"stable"`
Beta bool `json:"beta"`
Nightly bool `json:"nightly"`
WhatsNewURL string `json:"whatsNewUrl"`
ReleaseNotesURL string `json:"releaseNotesUrl"`
Builds []GCOMPackage `json:"-"`
}
-284
View File
@@ -1,284 +0,0 @@
package main
import (
"fmt"
"net/url"
"path"
"testing"
"github.com/grafana/grafana/pkg/build/packaging"
"github.com/grafana/grafana/pkg/build/versions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_constructURL(t *testing.T) {
type args struct {
product string
pth string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{name: "cleans .. sequence", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false},
{name: "doesn't clean anything - non malicious url", args: args{"foo", "bar"}, want: "https://grafana.com/api/foo/bar", wantErr: false},
{name: "doesn't clean anything - three dots", args: args{"...", "..."}, want: "https://grafana.com/api/.../...", wantErr: false},
{name: "cleans .", args: args{"..", ".."}, want: "https://grafana.com/api", wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := constructURL(tt.args.product, tt.args.pth)
if (err != nil) != tt.wantErr {
t.Errorf("constructURL() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("constructURL() got = %v, want %v", got, tt.want)
}
})
}
}
func TestBuilds(t *testing.T) {
baseURL := &url.URL{
Scheme: "https",
Host: "dl.example.com",
Path: path.Join("oss", "release"),
}
version := "1.2.3"
grafana := "grafana"
packages := []packaging.BuildArtifact{
{
Distro: "deb",
Arch: "arm64",
Ext: "deb",
},
{
Distro: "rhel",
Arch: "aarch64",
Ext: "rpm",
},
{
Distro: "linux",
Arch: "arm64",
Ext: "tar.gz",
},
{
Distro: "deb",
Arch: "armhf",
Ext: "deb",
RaspberryPi: true,
},
{
Distro: "deb",
Arch: "armhf",
Ext: "deb",
},
{
Distro: "linux",
Arch: "armv7",
Ext: "tar.gz",
},
{
Distro: "windows",
Arch: "amd64",
Ext: "zip",
},
{
Distro: "windows",
Arch: "amd64",
Ext: "msi",
},
}
expect := []GCOMPackage{
{
URL: "https://dl.example.com/oss/release/grafana_1.2.3_arm64.deb",
OS: "deb",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3-1.aarch64.rpm",
OS: "rhel",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-arm64.tar.gz",
OS: "linux",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3_armhf.deb",
OS: "deb",
Arch: "armv6",
},
{
URL: "https://dl.example.com/oss/release/grafana_1.2.3_armhf.deb",
OS: "deb",
Arch: "armv7",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3.linux-armv7.tar.gz",
OS: "linux",
Arch: "armv7",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.zip",
OS: "win",
Arch: "amd64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3.windows-amd64.msi",
OS: "win-installer",
Arch: "amd64",
},
}
builds, err := Builds(baseURL, grafana, version, packages)
require.NoError(t, err)
require.Equal(t, len(expect), len(builds))
for i := range builds {
t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) {
assert.Equal(t, expect[i].URL, builds[i].URL)
assert.Equal(t, expect[i].OS, builds[i].OS)
assert.Equal(t, expect[i].Arch, builds[i].Arch)
})
}
}
func TestBuildsWithPlus(t *testing.T) {
baseURL := &url.URL{
Scheme: "https",
Host: "dl.example.com",
Path: path.Join("oss", "release"),
}
version := "1.2.3+example-01"
grafana := "grafana"
packages := []packaging.BuildArtifact{
{
Distro: "deb",
Arch: "arm64",
Ext: "deb",
},
{
Distro: "rhel",
Arch: "aarch64",
Ext: "rpm",
},
{
Distro: "linux",
Arch: "arm64",
Ext: "tar.gz",
},
{
Distro: "deb",
Arch: "armhf",
Ext: "deb",
RaspberryPi: true,
},
{
Distro: "deb",
Arch: "armhf",
Ext: "deb",
},
{
Distro: "linux",
Arch: "armv7",
Ext: "tar.gz",
},
{
Distro: "windows",
Arch: "amd64",
Ext: "zip",
},
{
Distro: "windows",
Arch: "amd64",
Ext: "msi",
},
}
expect := []GCOMPackage{
{
URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_arm64.deb",
OS: "deb",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example~01-1.aarch64.rpm",
OS: "rhel",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-arm64.tar.gz",
OS: "linux",
Arch: "arm64",
},
{
URL: "https://dl.example.com/oss/release/grafana-rpi_1.2.3+example~01_armhf.deb",
OS: "deb",
Arch: "armv6",
},
{
URL: "https://dl.example.com/oss/release/grafana_1.2.3+example~01_armhf.deb",
OS: "deb",
Arch: "armv7",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.linux-armv7.tar.gz",
OS: "linux",
Arch: "armv7",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.zip",
OS: "win",
Arch: "amd64",
},
{
URL: "https://dl.example.com/oss/release/grafana-1.2.3+example-01.windows-amd64.msi",
OS: "win-installer",
Arch: "amd64",
},
}
builds, err := Builds(baseURL, grafana, version, packages)
require.NoError(t, err)
require.Equal(t, len(expect), len(builds))
for i := range builds {
t.Run(fmt.Sprintf("[%d/%d] %s", i+1, len(builds), expect[i].URL), func(t *testing.T) {
assert.Equal(t, expect[i].URL, builds[i].URL)
assert.Equal(t, expect[i].OS, builds[i].OS)
assert.Equal(t, expect[i].Arch, builds[i].Arch)
})
}
}
func TestReleaseURLs(t *testing.T) {
f := "https://grafana.com/whats-new-in-v%[1]s-%[2]s"
smv := versions.Semver{
Major: "1",
Minor: "2",
Patch: "3",
}
conf := packageConf{
Grafana: grafanaConf{
WhatsNewURL: f,
ReleaseNotesURL: "https://example.com",
},
}
expect := "https://grafana.com/whats-new-in-v1-2"
a, _, err := getReleaseURLs(smv, &conf)
require.NoError(t, err)
require.Equal(t, expect, a)
}
-275
View File
@@ -4,18 +4,9 @@ import (
"log"
"os"
"github.com/grafana/grafana/pkg/build/cmd/util"
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd"
"github.com/urfave/cli/v2"
)
var additionalCommands []*cli.Command = make([]*cli.Command, 0, 5)
//nolint:unused
func registerAppCommand(c *cli.Command) {
additionalCommands = append(additionalCommands, c)
}
func main() {
// TODO change the registerer if the user is running using a JSON file etc
for k, v := range cmd.Artifacts {
@@ -25,272 +16,6 @@ func main() {
}
app := cmd.GlobalCLI.App()
artifactsCommand := cmd.GlobalCLI.ArtifactsCommand()
artifactsCommand.Subcommands = cli.Commands{
{
Name: "storybook",
Usage: "[ARCHIVED] Publish Grafana storybook",
Action: PublishStorybookAction,
Flags: []cli.Flag{
&editionFlag,
&tagFlag,
&srcFlag,
&cli.StringFlag{
Name: "storybook-bucket",
Value: "grafana-storybook",
Usage: "Google Cloud Storage bucket for storybooks",
},
},
},
{
Name: "static-assets",
Usage: "[ARCHIVED] Publish Grafana static assets",
Action: PublishStaticAssetsAction,
Flags: []cli.Flag{
&editionFlag,
&securityFlag,
&securityDestBucketFlag,
&tagFlag,
&srcFlag,
&destFlag,
&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)",
},
},
},
{
Name: "packages",
Usage: "[ARCHIVED] Publish Grafana packages",
Action: PublishArtifactsAction,
Flags: []cli.Flag{
&editionFlag,
&securityFlag,
&securityDestBucketFlag,
&tagFlag,
&srcFlag,
&destFlag,
&cli.StringSliceFlag{
Name: "artifacts-editions",
Value: cli.NewStringSlice("oss", "enterprise", "enterprise2"),
Usage: "Editions for which the artifacts should be delivered (oss,enterprise,enterprise2), (or $ARTIFACTS_EDITIONS)",
},
&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)",
},
},
},
{
Name: "docker",
Usage: "[ARCHIVED] Handle Grafana Docker images",
Subcommands: cli.Commands{
{
Name: "fetch",
Usage: "Fetch Grafana Docker images",
ArgsUsage: "[version]",
Action: util.MaxArgCountWrapper(1, FetchImages),
Flags: []cli.Flag{
&editionFlag,
},
},
},
},
{
Name: "npm",
Usage: "[ARCHIVED] 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,
},
},
},
},
}
app.Commands = append(app.Commands, []*cli.Command{
{
Name: "e2e-tests",
Usage: "[ARCHIVED] Run Grafana e2e tests",
Action: EndToEndTests,
Flags: []cli.Flag{
&triesFlag,
&cli.IntFlag{
Name: "port",
Value: 3001,
Usage: "Specify the server port",
},
&cli.StringFlag{
Name: "suite",
Usage: "Specify the end-to-end tests suite to be used",
},
&cli.StringFlag{
Name: "host",
Value: "grafana-server",
Usage: "Specify the server host",
},
&cli.StringFlag{
Name: "video",
Value: "true",
Usage: "Specify if videos should be recorded",
},
},
},
{
Name: "upload-cdn",
Usage: "[ARCHIVED] Upload public/* to a cdn bucket",
Action: UploadCDN,
Flags: []cli.Flag{
&editionFlag,
},
},
{
Name: "publish-metrics",
Usage: "[ARCHIVED] Publish a set of metrics from stdin",
ArgsUsage: "<api-key>",
Action: util.MaxArgCountWrapper(1, PublishMetrics),
},
{
Name: "verify-drone",
Usage: "[ARCHIVED] Verify Drone configuration",
Action: VerifyDrone,
},
{
Name: "store-storybook",
Usage: "[ARCHIVED] Stores storybook to GCS buckets",
Action: StoreStorybook,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "deployment",
Usage: "Kind of deployment (e.g. canary/latest)",
},
},
},
{
Name: "verify-storybook",
Usage: "[ARCHIVED] Integrity check for storybook build",
Action: VerifyStorybook,
},
{
Name: "upload-packages",
Usage: "[ARCHIVED] Upload Grafana packages",
Action: UploadPackages,
Flags: []cli.Flag{
&jobsFlag,
&editionFlag,
&cli.BoolFlag{
Name: "enterprise2",
Usage: "Declare if the edition is enterprise2",
},
},
},
artifactsCommand,
{
Name: "publish",
Usage: "[ARCHIVED] Publish packages to Grafana com and repositories",
Subcommands: cli.Commands{
{
Name: "grafana-com",
Usage: "Publish packages to grafana.com",
Action: GrafanaCom,
Flags: []cli.Flag{
&editionFlag,
&buildIDFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "src-bucket",
Value: "grafana-downloads",
Usage: "Google Cloud Storage bucket",
},
},
},
{
Name: "github",
Usage: "Publish packages to GitHub releases",
Action: PublishGithub,
Flags: []cli.Flag{
&util.DryRunFlag,
&cli.StringFlag{
Name: "path",
Usage: "Path to the asset to be published",
},
&cli.StringFlag{
Name: "repo",
Required: true,
Usage: "GitHub repository",
},
&cli.StringFlag{
Name: "tag",
Usage: "Release tag (default from metadata)",
},
&cli.BoolFlag{
Name: "create",
Usage: "Create release if it doesn't exist",
},
},
},
{
Name: "aws",
Usage: "Publish image to AWS Marketplace releases",
Action: PublishAwsMarketplace,
Flags: []cli.Flag{
&util.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",
},
},
},
},
},
}...)
app.Commands = append(app.Commands, additionalCommands...)
if err := app.Run(os.Args); err != nil {
log.Fatalln(err)
-98
View File
@@ -1,98 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/npm"
)
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")
}
if strings.Contains(tag, "security") {
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
return nil
}
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")
}
if strings.Contains(tag, "security") {
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
return nil
}
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")
}
if strings.Contains(tag, "security") {
log.Printf("skipping npm publish because version '%s' has 'security'", tag)
return nil
}
err := npm.PublishNpmPackages(c.Context, tag)
if err != nil {
return err
}
return nil
}
-112
View File
@@ -1,112 +0,0 @@
package main
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/build/env"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/urfave/cli/v2"
)
// PublishArtifactsAction 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)
}
security := c.Bool("security")
var securityDestBucket, enterprise2SecurityPrefix string
artifactsEditions, err := env.RequireListWithEnvFallback(c, "artifacts-editions", "ARTIFACTS_EDITIONS")
if err != nil {
return err
}
if security {
securityDestBucket, err = env.RequireStringWithEnvFallback(c, "security-dest-bucket", "SECURITY_DEST_BUCKET")
if err != nil {
return err
}
enterprise2SecurityPrefix, err = env.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,
security: security,
tag: strings.TrimPrefix(c.String("tag"), "v"),
}
if cfg.security {
cfg.destBucket = securityDestBucket
}
gcs, err := storage.New()
if err != nil {
return err
}
for _, edition := range artifactsEditions {
switch edition {
case "oss", "enterprise":
err = copyArtifacts(c, gcs, cfg, edition)
if err != nil {
return err
}
case "enterprise2":
err = copyEnterprise2Artifacts(c, gcs, cfg)
if err != nil {
return err
}
default:
log.Printf("unrecognised artifacts edition: %s\n", edition)
}
}
return nil
}
func copyArtifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig, edition string) error {
bucket := gcs.Bucket(cfg.destBucket)
destURL := edition
if !cfg.security {
destURL = filepath.Join(destURL, "release")
}
log.Printf("Copying downloads for %s, from %s bucket to %s bucket", edition, cfg.srcBucket, destURL)
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads/v%s/%s/release", cfg.tag, edition), bucket, destURL); err != nil {
return err
}
log.Printf("Successfully copied downloads.")
return nil
}
func copyEnterprise2Artifacts(c *cli.Context, gcs *storage.Client, cfg publishConfig) error {
bucket := gcs.Bucket(cfg.enterprise2DestBucket)
var prefix string
if cfg.security {
prefix = cfg.enterprise2SecurityPrefix
}
destURL := fmt.Sprintf("enterprise2/%srelease", prefix)
log.Printf("Copying downloads for enterprise2, from %s bucket to %s bucket", cfg.srcBucket, destURL)
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/downloads-enterprise2/v%s/enterprise2/release", cfg.tag), bucket, destURL); err != nil {
return err
}
return nil
}
-310
View File
@@ -1,310 +0,0 @@
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/image"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/client"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
)
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 image.PullOptions) (io.ReadCloser, error)
ImageTag(ctx context.Context, source string, target string) error
ImagePush(ctx context.Context, image string, options image.PushOptions) (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(registry.AuthConfig{
Username: authString[0],
Password: authString[1],
})
s.auth = base64.StdEncoding.EncodeToString(authData)
return err
}
func (s *AwsMarketplacePublishingService) PullImage(ctx context.Context, img string, version string) error {
reader, err := s.docker.ImagePull(ctx, fmt.Sprintf("%s:%s", img, version), image.PullOptions{
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), image.PushOptions{
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)
}
-211
View File
@@ -1,211 +0,0 @@
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/image"
"github.com/grafana/grafana/pkg/build/cmd/util"
"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{
&util.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 image.PullOptions) (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 image.PushOptions) (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
}
-13
View File
@@ -1,13 +0,0 @@
package main
type publishConfig struct {
tag string
srcBucket string
destBucket string
enterprise2DestBucket string
enterprise2SecurityPrefix string
staticAssetsBucket string
staticAssetEditions []string
storybookBucket string
security bool
}
-191
View File
@@ -1,191 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/google/go-github/v70/github"
"github.com/urfave/cli/v2"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/build/config"
)
type githubRepositoryService interface {
GetReleaseByTag(ctx context.Context, owner string, repo string, tag string) (*github.RepositoryRelease, *github.Response, error)
CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error)
UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error)
DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error)
ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opt *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error)
}
type githubRepo struct {
owner string
name string
}
type publishGithubFlags struct {
create bool
dryRun bool
tag string
repo *githubRepo
artifactPath string
}
var (
newGithubClient = githubRepositoryClient
errTokenIsEmpty = errors.New("the environment variable GH_TOKEN must be set")
errTagIsEmpty = errors.New(`failed to retrieve release tag from metadata, use "--tag" to set it manually`)
errReleaseNotFound = errors.New(`release not found, use "--create" to create the release`)
)
func PublishGithub(c *cli.Context) error {
ctx := c.Context
token := os.Getenv("GH_TOKEN")
f, err := getPublishGithubFlags(c)
if err != nil {
return err
}
if f.tag == "" {
return errTagIsEmpty
}
if token == "" {
return errTokenIsEmpty
}
if f.dryRun {
return runPublishGithubDryRun(f, token, c)
}
client := newGithubClient(ctx, token)
release, res, err := client.GetReleaseByTag(ctx, f.repo.owner, f.repo.name, f.tag)
if err != nil && res.StatusCode != 404 {
return err
}
if release == nil {
if f.create {
release, _, err = client.CreateRelease(ctx, f.repo.owner, f.repo.name, &github.RepositoryRelease{TagName: &f.tag})
if err != nil {
return err
}
} else {
return errReleaseNotFound
}
}
artifactName := path.Base(f.artifactPath)
file, err := os.Open(f.artifactPath)
if err != nil {
return err
}
assetsPage := 1
foundAsset := false
for {
assets, resp, err := client.ListReleaseAssets(ctx, f.repo.owner, f.repo.name, *release.ID, &github.ListOptions{
Page: assetsPage,
})
if err != nil {
return err
}
for _, asset := range assets {
if asset.GetName() == artifactName {
fmt.Printf("Found existing artifact with the name '%s'. Deleting that now.\n", artifactName)
if _, err := client.DeleteReleaseAsset(ctx, f.repo.owner, f.repo.name, asset.GetID()); err != nil {
return fmt.Errorf("failed to delete already existing asset: %w", err)
}
foundAsset = true
break
}
}
if resp.NextPage <= assetsPage || foundAsset {
break
}
assetsPage = resp.NextPage
}
asset, _, err := client.UploadReleaseAsset(ctx, f.repo.owner, f.repo.name, *release.ID, &github.UploadOptions{Name: artifactName}, file)
if err != nil {
return err
}
fmt.Printf("Asset '%s' uploaded to release '%s' on repository '%s/%s'\nDownload: %s\n", *asset.Name, f.tag, f.repo.owner, f.repo.name, *asset.BrowserDownloadURL)
return nil
}
func githubRepositoryClient(ctx context.Context, token string) githubRepositoryService {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client := github.NewClient(tc)
return client.Repositories
}
func getPublishGithubFlags(c *cli.Context) (*publishGithubFlags, error) {
metadata, err := config.GenerateMetadata(c)
if err != nil {
return nil, err
}
tag := c.Value("tag").(string)
if tag == "" && metadata.GrafanaVersion != "" {
tag = fmt.Sprintf("v%s", metadata.GrafanaVersion)
}
fullRepo := c.Value("repo").(string)
dryRun := c.Value("dry-run").(bool)
owner := strings.Split(fullRepo, "/")[0]
name := strings.Split(fullRepo, "/")[1]
create := c.Value("create").(bool)
artifactPath := c.Value("path").(string)
if artifactPath == "" {
artifactPath = fmt.Sprintf("grafana-enterprise2-%s-amd64.img", metadata.GrafanaVersion)
fmt.Printf("path argument is not provided, resolving to default %s...\n", artifactPath)
}
return &publishGithubFlags{
artifactPath: artifactPath,
create: create,
dryRun: dryRun,
tag: tag,
repo: &githubRepo{
owner: owner,
name: name,
},
}, nil
}
func runPublishGithubDryRun(f *publishGithubFlags, token string, c *cli.Context) error {
client := newGithubClient(c.Context, token)
fmt.Println("Dry-Run: Retrieving release on repository by tag")
release, res, err := client.GetReleaseByTag(c.Context, f.repo.owner, f.repo.name, f.tag)
if err != nil && res.StatusCode != 404 {
fmt.Println("Dry-Run: Github communication error:\n", err)
return nil
}
if release == nil {
if f.create {
fmt.Println("Dry-Run: Release doesn't exist and --create is enabled, so it would try to create the release")
} else {
fmt.Println("Dry-Run: Release doesn't exist and --create is disabled, so it would fail with error")
return nil
}
}
artifactName := path.Base(f.artifactPath)
fmt.Printf("Dry-Run: Opening file for release: %s\n", f.artifactPath)
_, err = os.Open(f.artifactPath)
if err != nil {
fmt.Println("Dry-Run: Error opening file\n", err)
return nil
}
fmt.Printf("Dry-Run: Would upload asset '%s' to release '%s' on repo '%s/%s' and return download URL if successful\n", artifactName, f.tag, f.repo.owner, f.repo.name)
return nil
}
-240
View File
@@ -1,240 +0,0 @@
package main
import (
"context"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/google/go-github/v70/github"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/cmd/util"
)
type githubPublishTestCases struct {
name string
args []string
token string
expectedError error
errorContains string
expectedOutput string
mockedService *mockGithubRepositoryServiceImpl
}
var mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
func mockGithubRepositoryClient(context.Context, string) githubRepositoryService {
return mockGithubRepositoryService
}
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")
testCases := []githubPublishTestCases{
{
name: "try to publish without required flags",
errorContains: `Required flags "path, repo" not set`,
},
{
name: "try to publish without token",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
expectedError: errTokenIsEmpty,
},
{
name: "try to publish with invalid token",
token: "invalid",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
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},
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},
},
{
name: "try to publish with valid token and existing tag",
token: "valid",
args: []string{"--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
},
{
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",
},
{
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},
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},
expectedOutput: "Would upload asset",
},
{
name: "dry run with valid token and existing tag",
token: "valid",
args: []string{"--dry-run", "--path", testPath, "--repo", "test/test", "--tag", "v1.0.0"},
expectedOutput: "Would upload asset",
},
}
if os.Getenv("DRONE_COMMIT") == "" {
// this test only works locally due to Drone environment
testCases = append(testCases,
githubPublishTestCases{
name: "try to publish without tag",
args: []string{"--path", testPath, "--repo", "test/test"},
expectedError: errTagIsEmpty,
},
)
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
if test.token != "" {
t.Setenv("GH_TOKEN", test.token)
}
if test.mockedService != nil {
mockGithubRepositoryService = test.mockedService
} else {
mockGithubRepositoryService = &mockGithubRepositoryServiceImpl{}
}
args := []string{"run"}
args = append(args, test.args...)
out, err := captureStdout(t, func() error {
return testApp.Run(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 setupPublishGithubTests(t *testing.T) (*cli.App, string) {
t.Helper()
ex, err := os.Executable()
if err != nil {
panic(err)
}
testPath := filepath.Dir(ex)
newGithubClient = mockGithubRepositoryClient
testApp := cli.NewApp()
testApp.Action = PublishGithub
testApp.Flags = []cli.Flag{
&util.DryRunFlag,
&cli.StringFlag{
Name: "path",
Required: true,
Usage: "Path to the asset to be published",
},
&cli.StringFlag{
Name: "repo",
Required: true,
Usage: "Github repository",
},
&cli.StringFlag{
Name: "tag",
Usage: "Release tag (default from metadata)ß",
},
&cli.BoolFlag{
Name: "create",
Usage: "Create release if it doesn't exist",
},
}
return testApp, testPath
}
func captureStdout(t *testing.T, fn func() error) (string, error) {
t.Helper()
rescueStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err := fn()
werr := w.Close()
if werr != nil {
return "", err
}
out, _ := io.ReadAll(r)
os.Stdout = rescueStdout
return string(out), err
}
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) {
var release *github.RepositoryRelease
res := &github.Response{Response: &http.Response{}}
if m.tagErr == nil {
releaseID := int64(1)
release = &github.RepositoryRelease{ID: &releaseID}
} else if errors.Is(m.tagErr, errReleaseNotFound) {
res.StatusCode = 404
}
return release, res, m.tagErr
}
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) {
assetName := "test"
assetUrl := "testurl.com.br"
return &github.ReleaseAsset{Name: &assetName, BrowserDownloadURL: &assetUrl}, &github.Response{}, m.uploadErr
}
func (m *mockGithubRepositoryServiceImpl) DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) {
return nil, nil
}
func (m *mockGithubRepositoryServiceImpl) ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opts *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error) {
resp := github.Response{}
resp.LastPage = 1
resp.FirstPage = 1
resp.NextPage = 0
return []*github.ReleaseAsset{}, &resp, nil
}
-42
View File
@@ -1,42 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"regexp"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/metrics"
)
func PublishMetrics(c *cli.Context) error {
apiKey := c.Args().Get(0)
input, err := io.ReadAll(os.Stdin)
if err != nil {
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.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.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.Exit(fmt.Sprintf("publishing metrics failed: %s", err), 1)
}
return nil
}
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/build/env"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/urfave/cli/v2"
)
// PublishStaticAssetsAction Action implements the sub-command "artifacts static-assets".
func PublishStaticAssetsAction(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 := env.RequireListWithEnvFallback(c, "static-asset-editions", "STATIC_ASSET_EDITIONS")
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"),
staticAssetsBucket: c.String("static-assets-bucket"),
staticAssetEditions: staticAssetEditions,
tag: strings.TrimPrefix(c.String("tag"), "v"),
}
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(cfg.staticAssetsBucket)
for _, edition := range staticAssetEditions {
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/static-assets/%s/%s", edition, cfg.tag), bucket, fmt.Sprintf("%s/%s", edition, cfg.tag)); err != nil {
return err
}
}
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package main
import (
"fmt"
"log"
"strings"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
"github.com/grafana/grafana/pkg/build/versions"
"github.com/urfave/cli/v2"
)
// PublishStorybookAction Action implements the sub-command "publish-artifacts".
func PublishStorybookAction(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("error connecting to gcp, %q", err)
}
cfg := publishConfig{
srcBucket: c.String("src-bucket"),
storybookBucket: c.String("storybook-bucket"),
tag: strings.TrimPrefix(c.String("tag"), "v"),
}
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(cfg.storybookBucket)
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, cfg.tag); err != nil {
return err
}
if latest, err := isLatest(cfg); err == nil && latest {
log.Printf("Copying storybooks to latest...")
if err := gcs.CopyRemoteDir(c.Context, gcs.Bucket(cfg.srcBucket), fmt.Sprintf("artifacts/storybook/v%s", cfg.tag), bucket, "latest"); err != nil {
return err
}
} else {
return err
}
return nil
}
func isLatest(cfg publishConfig) (bool, error) {
stableVersion, err := versions.GetLatestVersion(versions.LatestStableVersionURL)
if err != nil {
return false, err
}
isLatest, err := versions.IsGreaterThanOrEqual(cfg.tag, stableVersion)
if err != nil {
return false, err
}
return isLatest, nil
}
-50
View File
@@ -1,50 +0,0 @@
package main
import (
"log"
"path/filepath"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
)
// StoreStorybook implements the sub-command "store-storybook".
func StoreStorybook(c *cli.Context) error {
deployment := c.String("deployment")
metadata, err := config.GenerateMetadata(c)
if err != nil {
return err
}
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode)
if err != nil {
return err
}
storybookBucket := buildConfig.Buckets.Storybook
srcPath := buildConfig.Buckets.StorybookSrcDir
srcPath = filepath.Join(srcPath, deployment)
gcs, err := storage.New()
if err != nil {
return err
}
bucket := gcs.Bucket(storybookBucket)
if err := gcs.DeleteDir(c.Context, bucket, srcPath); err != nil {
return err
}
log.Printf("Successfully cleaned source: %s/%s\n", storybookBucket, srcPath)
if err := gcs.CopyLocalDir(c.Context, "packages/grafana-ui/dist/storybook", bucket, srcPath, true); err != nil {
return err
}
log.Printf("Successfully stored storybook to: %s/%s!\n", storybookBucket, srcPath)
return nil
}
-76
View File
@@ -1,76 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/gcloud/storage"
)
// 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
}
-234
View File
@@ -1,234 +0,0 @@
package main
import (
"encoding/base64"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/urfave/cli/v2"
"github.com/grafana/grafana/pkg/build/config"
"github.com/grafana/grafana/pkg/build/droneutil"
"github.com/grafana/grafana/pkg/build/gcloud"
)
// PackageRegexp returns a regexp for matching packages corresponding to a certain Grafana edition.
func PackageRegexp(edition config.Edition) *regexp.Regexp {
var sfx string
switch edition {
case config.EditionOSS:
case config.EditionEnterprise:
sfx = "-enterprise"
case config.EditionEnterprise2:
sfx = "-enterprise2"
default:
panic(fmt.Sprintf("unrecognized edition %q", edition))
}
rePkg, err := regexp.Compile(fmt.Sprintf(`^grafana%s(?:-rpi)?[-_][^-_]+.*$`, sfx))
if err != nil {
panic(fmt.Sprintf("Failed to compile regexp: %s", err))
}
return rePkg
}
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, config.CloudMode:
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 := 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
}
-68
View File
@@ -1,68 +0,0 @@
package main
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/build/config"
)
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},
{"cloud mode", args{uploadConfig{versionMode: config.CloudMode}, "", 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)
})
}
}
-125
View File
@@ -1,125 +0,0 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"github.com/drone/drone-cli/drone/lint"
"github.com/drone/drone-cli/drone/starlark"
"github.com/google/go-cmp/cmp"
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 {
const yml = ".drone.yml"
const backup = ".drone.yml.bak"
if err := fsutil.CopyFile(yml, backup); err != nil {
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 {
log.Printf("Failed to rename %s to %s", backup, yml)
}
if err := os.Rename(backup, yml); err != nil {
log.Printf("Failed to rename %s to %s", backup, yml)
}
}()
flags := &flag.FlagSet{}
for _, flag := range starlark.Command.Flags {
flag.Apply(flags)
}
if err := flags.Set("format", "true"); err != nil {
return err
}
cStarlark := cliv1.NewContext(cliv1.NewApp(), flags, nil)
action := starlark.Command.Action.(func(*cliv1.Context))
action(cStarlark)
if err := verifyYAML(yml, backup); err != nil {
return err
}
flags = &flag.FlagSet{}
for _, flag := range lint.Command.Flags {
flag.Apply(flags)
}
err := flags.Set("trusted", "true")
if err != nil {
return err
}
cLint := cliv1.NewContext(cliv1.NewApp(), flags, nil)
actionE := lint.Command.Action.(func(*cliv1.Context) error)
if err := actionE(cLint); err != nil {
return err
}
log.Printf("%s is valid", yml)
return nil
}
func readConfig(fpath string) ([]map[string]any, error) {
//nolint:gosec
f, err := os.Open(fpath)
if err != nil {
return nil, cli.Exit(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
}
defer func() {
if err := f.Close(); err != nil {
log.Println("error closing file", err)
}
}()
// The YAML stream may contain multiple pipeline configurations, read them all
dec := yaml.NewDecoder(f)
var c []map[string]any
for {
var m map[string]any
if err := dec.Decode(&m); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, cli.Exit(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
}
if m["kind"] == "signature" {
log.Printf("Ignoring a signature")
continue
}
c = append(c, m)
}
return c, nil
}
func verifyYAML(yml, backup string) error {
log.Printf("Comparing %s and %s", yml, backup)
c1, err := readConfig(yml)
if err != nil {
return err
}
c2, err := readConfig(backup)
if err != nil {
return err
}
if !cmp.Equal(c1, c2) {
return cli.Exit(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
yml), 1)
}
return nil
}
-32
View File
@@ -1,32 +0,0 @@
// Package verifystorybook contains the sub-command "verify-storybook".
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/grafana/grafana/pkg/build/fsutil"
"github.com/urfave/cli/v2"
)
// 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 := fsutil.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
}