mirror of https://github.com/grafana/grafana
[v10.4.x] CI: Support more version formats in publishing (#94740)
CI: Support more version formats in publishing (#94575)
* cleanup dead code
* add tests and rewrite publish grafanacom steps to reuse
* add pkg/build tests; don't upload CDN assets on grafana releases
(cherry picked from commit 7a2edd35d5
)
pull/94926/head
parent
c66109b725
commit
6aa75962da
@ -1,68 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/compilers" |
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/errutil" |
||||
"github.com/grafana/grafana/pkg/build/grafana" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
func BuildBackend(ctx *cli.Context) error { |
||||
metadata, err := config.GenerateMetadata(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
version := metadata.GrafanaVersion |
||||
|
||||
var ( |
||||
edition = config.Edition(ctx.String("edition")) |
||||
cfg = config.Config{ |
||||
NumWorkers: ctx.Int("jobs"), |
||||
} |
||||
) |
||||
|
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) |
||||
if err != nil { |
||||
return fmt.Errorf("could not get version / package info for mode '%s': %w", metadata.ReleaseMode.Mode, err) |
||||
} |
||||
|
||||
const grafanaDir = "." |
||||
|
||||
log.Printf("Building Grafana back-end, version %q, %s edition, variants [%v]", |
||||
version, edition, buildConfig.Variants) |
||||
|
||||
p := syncutil.NewWorkerPool(cfg.NumWorkers) |
||||
defer p.Close() |
||||
|
||||
if err := compilers.Install(); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
g, _ := errutil.GroupWithContext(ctx.Context) |
||||
for _, variant := range buildConfig.Variants { |
||||
variant := variant |
||||
|
||||
opts := grafana.BuildVariantOpts{ |
||||
Variant: variant, |
||||
Edition: edition, |
||||
Version: version, |
||||
GrafanaDir: grafanaDir, |
||||
} |
||||
|
||||
p.Schedule(g.Wrap(func() error { |
||||
return grafana.BuildVariant(ctx.Context, opts) |
||||
})) |
||||
} |
||||
if err := g.Wait(); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
log.Println("Successfully built back-end binaries!") |
||||
return nil |
||||
} |
@ -1,51 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"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" |
||||
) |
||||
|
||||
func BuildDocker(c *cli.Context) error { |
||||
if err := docker.Init(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
useUbuntu := c.Bool("ubuntu") |
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
shouldSave := buildConfig.Docker.ShouldSave |
||||
if shouldSave { |
||||
if err := gcloud.ActivateServiceAccount(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
edition := config.Edition(c.String("edition")) |
||||
|
||||
version := metadata.GrafanaVersion |
||||
|
||||
log.Printf("Building Docker images, version %s, %s edition, Ubuntu based: %v...", version, edition, |
||||
useUbuntu) |
||||
|
||||
for _, arch := range buildConfig.Docker.Architectures { |
||||
if _, err := docker.BuildImage(version, arch, ".", useUbuntu, shouldSave, edition, metadata.ReleaseMode.Mode); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
} |
||||
|
||||
log.Println("Successfully built Docker images!") |
||||
return nil |
||||
} |
@ -1,39 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"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" |
||||
) |
||||
|
||||
func BuildFrontend(c *cli.Context) error { |
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
cfg, mode, err := frontend.GetConfig(c, metadata) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
p := syncutil.NewWorkerPool(cfg.NumWorkers) |
||||
defer p.Close() |
||||
|
||||
g, _ := errutil.GroupWithContext(c.Context) |
||||
if err := frontend.Build(mode, frontend.GrafanaDir, p, g); err != nil { |
||||
return err |
||||
} |
||||
if err := g.Wait(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
log.Println("Successfully built Grafana front-end!") |
||||
|
||||
return nil |
||||
} |
@ -1,38 +0,0 @@ |
||||
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" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
func BuildFrontendPackages(c *cli.Context) error { |
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
cfg, mode, err := frontend.GetConfig(c, metadata) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
p := syncutil.NewWorkerPool(cfg.NumWorkers) |
||||
defer p.Close() |
||||
|
||||
g, _ := errutil.GroupWithContext(c.Context) |
||||
if err := frontend.BuildFrontendPackages(cfg.PackageVersion, mode, frontend.GrafanaDir, p, g); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
if err := g.Wait(); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
log.Println("Successfully built Grafana front-end packages!") |
||||
|
||||
return nil |
||||
} |
@ -1,53 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/errutil" |
||||
"github.com/grafana/grafana/pkg/build/plugins" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
func BuildInternalPlugins(c *cli.Context) error { |
||||
cfg := config.Config{ |
||||
NumWorkers: c.Int("jobs"), |
||||
} |
||||
|
||||
const grafanaDir = "." |
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
buildConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
log.Println("Building internal Grafana plug-ins...") |
||||
|
||||
ctx := context.Background() |
||||
|
||||
p := syncutil.NewWorkerPool(cfg.NumWorkers) |
||||
defer p.Close() |
||||
|
||||
var g *errutil.Group |
||||
g, ctx = errutil.GroupWithContext(ctx) |
||||
if err := plugins.Build(ctx, grafanaDir, p, g, buildConfig); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
if err := g.Wait(); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
if err := plugins.Download(ctx, grafanaDir, p); err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
log.Println("Successfully built Grafana plug-ins!") |
||||
|
||||
return nil |
||||
} |
@ -1,133 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"strconv" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/env" |
||||
"github.com/grafana/grafana/pkg/build/git" |
||||
) |
||||
|
||||
// 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) |
||||
} |
@ -1,69 +0,0 @@ |
||||
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) |
||||
}) |
||||
} |
@ -1,32 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
) |
||||
|
||||
func ExportVersion(c *cli.Context) error { |
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
const distDir = "dist" |
||||
if err := os.RemoveAll(distDir); err != nil { |
||||
return err |
||||
} |
||||
if err := os.Mkdir(distDir, 0750); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// nolint:gosec
|
||||
if err := os.WriteFile(filepath.Join(distDir, "grafana.version"), []byte(metadata.GrafanaVersion), 0664); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,48 @@ |
||||
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) |
||||
}) |
||||
} |
@ -1,80 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"strings" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/gpg" |
||||
"github.com/grafana/grafana/pkg/build/packaging" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
func Package(c *cli.Context) error { |
||||
metadata, err := config.GenerateMetadata(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
edition := config.Edition(c.String("edition")) |
||||
|
||||
releaseMode, err := metadata.GetReleaseMode() |
||||
if err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
releaseModeConfig, err := config.GetBuildConfig(metadata.ReleaseMode.Mode) |
||||
if err != nil { |
||||
return cli.Exit(err.Error(), 1) |
||||
} |
||||
|
||||
cfg := config.Config{ |
||||
NumWorkers: c.Int("jobs"), |
||||
SignPackages: c.Bool("sign"), |
||||
} |
||||
|
||||
ctx := context.Background() |
||||
|
||||
variants := []config.Variant{} |
||||
variantStrs := strings.Split(c.String("variants"), ",") |
||||
if c.String("variants") != "" { |
||||
for _, varStr := range variantStrs { |
||||
if varStr == "" { |
||||
continue |
||||
} |
||||
variants = append(variants, config.Variant(varStr)) |
||||
} |
||||
} else { |
||||
variants = releaseModeConfig.Variants |
||||
} |
||||
|
||||
if len(variants) == 0 { |
||||
variants = config.AllVariants |
||||
} |
||||
|
||||
log.Printf("Packaging Grafana version %q, version mode %s, %s edition, variants %s", metadata.GrafanaVersion, releaseMode.Mode, |
||||
edition, strings.Join(variantStrs, ",")) |
||||
|
||||
if cfg.SignPackages { |
||||
if err := gpg.LoadGPGKeys(&cfg); err != nil { |
||||
return cli.Exit(err, 1) |
||||
} |
||||
defer gpg.RemoveGPGFiles(cfg) |
||||
if err := gpg.Import(cfg); err != nil { |
||||
return cli.Exit(err, 1) |
||||
} |
||||
} |
||||
|
||||
p := syncutil.NewWorkerPool(cfg.NumWorkers) |
||||
defer p.Close() |
||||
|
||||
if err := packaging.PackageGrafana(ctx, metadata.GrafanaVersion, ".", cfg, edition, variants, releaseModeConfig.PluginSignature.Sign, p); err != nil { |
||||
return cli.Exit(err, 1) |
||||
} |
||||
|
||||
log.Println("Successfully packaged Grafana!") |
||||
return nil |
||||
} |
@ -1,101 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
|
||||
"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" |
||||
) |
||||
|
||||
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 |
||||
} |
@ -1,42 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
func Shellcheck(c *cli.Context) error { |
||||
log.Println("Running shellcheck...") |
||||
|
||||
fpaths := []string{} |
||||
if err := filepath.Walk("scripts", func(path string, info os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if strings.HasSuffix(path, ".sh") { |
||||
fpaths = append(fpaths, path) |
||||
} |
||||
|
||||
return nil |
||||
}); err != nil { |
||||
return fmt.Errorf("couldn't traverse scripts/: %w", err) |
||||
} |
||||
|
||||
log.Printf("Running shellcheck on %s", strings.Join(fpaths, ",")) |
||||
args := append([]string{"-e", "SC1071", "-e", "SC2162"}, fpaths...) |
||||
//nolint:gosec
|
||||
cmd := exec.Command("shellcheck", args...) |
||||
if output, err := cmd.CombinedOutput(); err != nil { |
||||
return fmt.Errorf("shellcheck failed: %s", output) |
||||
} |
||||
|
||||
log.Println("Successfully ran shellcheck!") |
||||
return nil |
||||
} |
@ -1,142 +0,0 @@ |
||||
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 |
||||
} |
@ -1,137 +0,0 @@ |
||||
//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]) |
||||
} |
||||
}) |
||||
}) |
||||
} |
@ -1,50 +0,0 @@ |
||||
package compilers |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
) |
||||
|
||||
const ( |
||||
ArmV6 = "/opt/rpi-tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc" |
||||
Armv7 = "arm-linux-gnueabihf-gcc" |
||||
Armv7Musl = "/tmp/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-gcc" |
||||
Arm64 = "aarch64-linux-gnu-gcc" |
||||
Arm64Musl = "/tmp/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc" |
||||
Osx64 = "/tmp/osxcross/target/bin/o64-clang" |
||||
Win64 = "x86_64-w64-mingw32-gcc" |
||||
LinuxX64 = "/tmp/x86_64-centos6-linux-gnu/bin/x86_64-centos6-linux-gnu-gcc" |
||||
LinuxX64Musl = "/tmp/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc" |
||||
) |
||||
|
||||
func Install() error { |
||||
// From the os.TempDir documentation:
|
||||
// On Unix systems, it returns $TMPDIR if non-empty,
|
||||
// else /tmp. On Windows, it uses GetTempPath,
|
||||
// returning the first non-empty value from %TMP%, %TEMP%, %USERPROFILE%,
|
||||
// or the Windows directory. On Plan 9, it returns /tmp.
|
||||
tmp := os.TempDir() |
||||
|
||||
var ( |
||||
centosArchive = "x86_64-centos6-linux-gnu.tar.xz" |
||||
osxArchive = "osxcross.tar.xz" |
||||
) |
||||
|
||||
for _, fname := range []string{centosArchive, osxArchive} { |
||||
path := filepath.Join(tmp, fname) |
||||
if _, err := os.Stat(path); err != nil { |
||||
return fmt.Errorf("stat error: %w", err) |
||||
} |
||||
// Ignore gosec G204 as this function is only used in the build process.
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("tar", "xfJ", fname) |
||||
cmd.Dir = tmp |
||||
if output, err := cmd.CombinedOutput(); err != nil { |
||||
return fmt.Errorf("failed to unpack %q: %q, %w", fname, output, err) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,69 +0,0 @@ |
||||
package config |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"log" |
||||
"strconv" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/executil" |
||||
) |
||||
|
||||
type Revision struct { |
||||
Timestamp int64 |
||||
SHA256 string |
||||
EnterpriseCommit string |
||||
Branch string |
||||
} |
||||
|
||||
func GrafanaTimestamp(ctx context.Context, dir string) (int64, error) { |
||||
out, err := executil.OutputAt(ctx, dir, "git", "show", "-s", "--format=%ct") |
||||
if err != nil { |
||||
return time.Now().Unix(), nil |
||||
} |
||||
|
||||
stamp, err := strconv.ParseInt(out, 10, 64) |
||||
if err != nil { |
||||
return 0, fmt.Errorf("failed to parse output from git show: %q", out) |
||||
} |
||||
|
||||
return stamp, nil |
||||
} |
||||
|
||||
// GrafanaRevision uses git commands to get information about the checked out Grafana code located at 'grafanaDir'.
|
||||
// This could maybe be a more generic "Describe" function in the "git" package.
|
||||
func GrafanaRevision(ctx context.Context, grafanaDir string) (Revision, error) { |
||||
stamp, err := GrafanaTimestamp(ctx, grafanaDir) |
||||
if err != nil { |
||||
return Revision{}, err |
||||
} |
||||
|
||||
sha, err := executil.OutputAt(ctx, grafanaDir, "git", "rev-parse", "--short", "HEAD") |
||||
if err != nil { |
||||
return Revision{}, err |
||||
} |
||||
|
||||
enterpriseCommit, err := executil.OutputAt(ctx, grafanaDir, "git", "-C", "../grafana-enterprise", "rev-parse", "--short", "HEAD") |
||||
if err != nil { |
||||
enterpriseCommit, err = executil.OutputAt(ctx, grafanaDir, "git", "-C", "..", "rev-parse", "--short", "HEAD") |
||||
if err != nil { |
||||
enterpriseCommit, err = executil.OutputAt(ctx, grafanaDir, "git", "-C", "/tmp/grafana-enterprise", "rev-parse", "--short", "HEAD") |
||||
if err != nil { |
||||
log.Println("Could not get enterprise commit. Error:", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
branch, err := executil.OutputAt(ctx, grafanaDir, "git", "rev-parse", "--abbrev-ref", "HEAD") |
||||
if err != nil { |
||||
return Revision{}, err |
||||
} |
||||
|
||||
return Revision{ |
||||
SHA256: sha, |
||||
EnterpriseCommit: enterpriseCommit, |
||||
Branch: branch, |
||||
Timestamp: stamp, |
||||
}, nil |
||||
} |
@ -1,35 +0,0 @@ |
||||
package cryptoutil |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
) |
||||
|
||||
func MD5File(fpath string) error { |
||||
// Ignore gosec G304 as this function is only used in the build process.
|
||||
//nolint:gosec
|
||||
fd, err := os.Open(fpath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
if err := fd.Close(); err != nil { |
||||
log.Printf("error closing file at '%s': %s", fpath, err.Error()) |
||||
} |
||||
}() |
||||
|
||||
h := md5.New() // nolint:gosec
|
||||
if _, err = io.Copy(h, fd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// nolint:gosec
|
||||
if err := os.WriteFile(fpath+".md5", []byte(fmt.Sprintf("%x\n", h.Sum(nil))), 0664); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,182 +0,0 @@ |
||||
package docker |
||||
|
||||
import ( |
||||
"crypto/sha256" |
||||
"encoding/hex" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
) |
||||
|
||||
// verifyArchive verifies the integrity of an archive file.
|
||||
func verifyArchive(archive string) error { |
||||
log.Printf("Verifying checksum of %q", archive) |
||||
|
||||
//nolint:gosec
|
||||
shaB, err := os.ReadFile(archive + ".sha256") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
exp := strings.TrimSpace(string(shaB)) |
||||
|
||||
//nolint:gosec
|
||||
f, err := os.Open(archive) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer func() { |
||||
if err := f.Close(); err != nil { |
||||
log.Println("error closing file:", err) |
||||
} |
||||
}() |
||||
|
||||
h := sha256.New() |
||||
_, err = io.Copy(h, f) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
chksum := hex.EncodeToString(h.Sum(nil)) |
||||
if chksum != exp { |
||||
return fmt.Errorf("archive checksum is different than expected: %q", archive) |
||||
} |
||||
|
||||
log.Printf("Archive %q has expected checksum: %s", archive, exp) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// BuildImage builds a Docker image.
|
||||
// The image tag is returned.
|
||||
func BuildImage(version string, arch config.Architecture, grafanaDir string, useUbuntu, shouldSave bool, edition config.Edition, mode config.VersionMode) ([]string, error) { |
||||
var baseArch string |
||||
|
||||
switch arch { |
||||
case "amd64": |
||||
case "armv7": |
||||
baseArch = "arm32v7/" |
||||
case "arm64": |
||||
baseArch = "arm64v8/" |
||||
default: |
||||
return []string{}, fmt.Errorf("unrecognized architecture %q", arch) |
||||
} |
||||
|
||||
libc := "-musl" |
||||
baseImage := fmt.Sprintf("%salpine:3.18.5", baseArch) |
||||
tagSuffix := "" |
||||
if useUbuntu { |
||||
libc = "" |
||||
baseImage = fmt.Sprintf("%subuntu:22.04", baseArch) |
||||
tagSuffix = "-ubuntu" |
||||
} |
||||
|
||||
var editionStr string |
||||
var dockerRepo string |
||||
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" |
||||
additionalDockerRepo = "grafana/grafana-oss-image-tags" |
||||
imageFileBase = "grafana-oss" |
||||
case config.EditionEnterprise: |
||||
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) |
||||
} |
||||
|
||||
buildDir := filepath.Join(grafanaDir, "packaging/docker") |
||||
// For example: grafana-8.5.0-52819pre.linux-amd64-musl.tar.gz
|
||||
archive := fmt.Sprintf("grafana%s-%s.linux-%s%s.tar.gz", editionStr, version, arch, libc) |
||||
if err := verifyArchive(filepath.Join(buildDir, archive)); err != nil { |
||||
return []string{}, err |
||||
} |
||||
|
||||
tag := fmt.Sprintf("%s:%s%s-%s", dockerRepo, version, tagSuffix, arch) |
||||
tags = append(tags, tag) |
||||
|
||||
args := []string{ |
||||
"build", |
||||
"-q", |
||||
"--build-arg", fmt.Sprintf("BASE_IMAGE=%s", baseImage), |
||||
"--build-arg", fmt.Sprintf("GRAFANA_TGZ=%s", archive), |
||||
"--build-arg", "GO_SRC=tgz-builder", |
||||
"--build-arg", "JS_SRC=tgz-builder", |
||||
"--build-arg", "RUN_SH=./run.sh", |
||||
"--tag", tag, |
||||
"--no-cache", |
||||
"--file", "../../Dockerfile", |
||||
".", |
||||
"--label", fmt.Sprintf("mode=%s", string(mode)), |
||||
} |
||||
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("docker", args...) |
||||
cmd.Dir = buildDir |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Stderr = os.Stderr |
||||
cmd.Env = append(os.Environ(), "DOCKER_CLI_EXPERIMENTAL=enabled", "DOCKER_BUILDKIT=1") |
||||
log.Printf("Running Docker: DOCKER_CLI_EXPERIMENTAL=enabled DOCKER_BUILDKIT=1 %s", cmd) |
||||
if err := cmd.Run(); err != nil { |
||||
return []string{}, fmt.Errorf("building Docker image failed: %w", err) |
||||
} |
||||
if shouldSave { |
||||
imageFile := fmt.Sprintf("%s-%s%s-%s.img", imageFileBase, version, tagSuffix, arch) |
||||
//nolint:gosec
|
||||
cmd = exec.Command("docker", "save", tag, "-o", imageFile) |
||||
cmd.Dir = buildDir |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Stderr = os.Stderr |
||||
log.Printf("Running Docker: %s", cmd) |
||||
if err := cmd.Run(); err != nil { |
||||
return []string{}, fmt.Errorf("saving Docker image failed: %w", err) |
||||
} |
||||
gcsURL := fmt.Sprintf("gs://grafana-prerelease/artifacts/docker/%s/%s", version, imageFile) |
||||
//nolint:gosec
|
||||
cmd = exec.Command("gsutil", "-q", "cp", imageFile, gcsURL) |
||||
cmd.Dir = buildDir |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Stderr = os.Stderr |
||||
log.Printf("Running gsutil: %s", cmd) |
||||
if err := cmd.Run(); err != nil { |
||||
return []string{}, fmt.Errorf("storing Docker image failed: %w", err) |
||||
} |
||||
log.Printf("Docker image %s stored to grafana-prerelease GCS bucket", imageFile) |
||||
} |
||||
if additionalDockerRepo != "" { |
||||
additionalTag := fmt.Sprintf("%s:%s%s-%s", additionalDockerRepo, version, tagSuffix, arch) |
||||
|
||||
//nolint:gosec
|
||||
cmd = exec.Command("docker", "tag", tag, additionalTag) |
||||
cmd.Dir = buildDir |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Stderr = os.Stderr |
||||
log.Printf("Running Docker: %s", cmd) |
||||
if err := cmd.Run(); err != nil { |
||||
return []string{}, fmt.Errorf("tagging Docker image failed: %w", err) |
||||
} |
||||
tags = append(tags, additionalTag) |
||||
} |
||||
|
||||
return tags, nil |
||||
} |
@ -1,34 +0,0 @@ |
||||
package docker |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
) |
||||
|
||||
// AllArchs is a list of all supported Docker image architectures.
|
||||
var AllArchs = []string{"amd64", "arm64"} |
||||
|
||||
// emulatorImage is the docker image used as the cross-platform emulator
|
||||
var emulatorImage = "tonistiigi/binfmt:qemu-v7.0.0" |
||||
|
||||
// Init initializes the OS for Docker image building.
|
||||
func Init() error { |
||||
// Necessary for cross-platform builds
|
||||
if err := os.Setenv("DOCKER_BUILDKIT", "1"); err != nil { |
||||
log.Println("error setting DOCKER_BUILDKIT environment variable:", err) |
||||
} |
||||
|
||||
// Enable execution of Docker images for other architectures
|
||||
//nolint:gosec
|
||||
cmd := exec.Command("docker", "run", "--privileged", "--rm", |
||||
emulatorImage, "--install", "all") |
||||
output, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
return fmt.Errorf("failed to enable execution of cross-platform Docker images: %w\n%s", err, output) |
||||
} |
||||
log.Println("emulators have been installed successfully!") |
||||
|
||||
return nil |
||||
} |
@ -1,62 +0,0 @@ |
||||
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 |
||||
} |
@ -1,18 +0,0 @@ |
||||
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 |
||||
} |
@ -1,43 +0,0 @@ |
||||
package env_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/env" |
||||
) |
||||
|
||||
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) |
||||
} |
||||
} |
@ -1,61 +0,0 @@ |
||||
package errutil |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"sync" |
||||
) |
||||
|
||||
type Group struct { |
||||
cancel func() |
||||
wg sync.WaitGroup |
||||
errOnce sync.Once |
||||
err error |
||||
} |
||||
|
||||
func GroupWithContext(ctx context.Context) (*Group, context.Context) { |
||||
ctx, cancel := context.WithCancel(ctx) |
||||
return &Group{cancel: cancel}, ctx |
||||
} |
||||
|
||||
// Wait waits for any wrapped goroutines to finish and returns any error having occurred in one of them.
|
||||
func (g *Group) Wait() error { |
||||
log.Println("Waiting on Group") |
||||
g.wg.Wait() |
||||
if g.cancel != nil { |
||||
log.Println("Group canceling its context after waiting") |
||||
g.cancel() |
||||
} |
||||
return g.err |
||||
} |
||||
|
||||
// Cancel cancels the associated context.
|
||||
func (g *Group) Cancel() { |
||||
log.Println("Group's Cancel method being called") |
||||
g.cancel() |
||||
} |
||||
|
||||
// Wrap wraps a function to be executed in a goroutine.
|
||||
func (g *Group) Wrap(f func() error) func() { |
||||
g.wg.Add(1) |
||||
return func() { |
||||
defer g.wg.Done() |
||||
|
||||
if err := f(); err != nil { |
||||
g.errOnce.Do(func() { |
||||
log.Printf("An error occurred in Group: %s", err) |
||||
g.err = err |
||||
if g.cancel != nil { |
||||
log.Println("Group canceling its context due to error") |
||||
g.cancel() |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Go wraps the provided function and executes it in a goroutine.
|
||||
func (g *Group) Go(f func() error) { |
||||
wrapped := g.Wrap(f) |
||||
go wrapped() |
||||
} |
@ -1,46 +0,0 @@ |
||||
package executil |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"os/exec" |
||||
"strings" |
||||
) |
||||
|
||||
func RunAt(ctx context.Context, dir, cmd string, args ...string) error { |
||||
// Ignore gosec G204 as this function is only used in the build process.
|
||||
//nolint:gosec
|
||||
c := exec.CommandContext(ctx, cmd, args...) |
||||
c.Dir = dir |
||||
|
||||
b, err := c.CombinedOutput() |
||||
|
||||
if err != nil { |
||||
return fmt.Errorf("%w. '%s %v': %s", err, cmd, args, string(b)) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func Run(ctx context.Context, cmd string, args ...string) error { |
||||
return RunAt(ctx, ".", cmd, args...) |
||||
} |
||||
|
||||
func OutputAt(ctx context.Context, dir, cmd string, args ...string) (string, error) { |
||||
// Ignore gosec G204 as this function is only used in the build process.
|
||||
//nolint:gosec
|
||||
c := exec.CommandContext(ctx, cmd, args...) |
||||
c.Dir = dir |
||||
|
||||
b, err := c.CombinedOutput() |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return strings.TrimSpace(string(b)), nil |
||||
} |
||||
|
||||
func Output(ctx context.Context, cmd string, args ...string) (string, error) { |
||||
return OutputAt(ctx, ".", cmd, args...) |
||||
} |
@ -1,56 +0,0 @@ |
||||
package frontend |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/errutil" |
||||
"github.com/grafana/grafana/pkg/build/lerna" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
func BuildFrontendPackages(version string, edition config.Edition, grafanaDir string, p syncutil.WorkerPool, g *errutil.Group) error { |
||||
p.Schedule(g.Wrap(func() error { |
||||
if err := lerna.BuildFrontendPackages(version, edition, grafanaDir); err != nil { |
||||
return fmt.Errorf("failed to build %s frontend packages: %v", edition, err) |
||||
} |
||||
|
||||
log.Printf("Finished building %s frontend packages", string(edition)) |
||||
return nil |
||||
})) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Build builds the Grafana front-end
|
||||
func Build(edition config.Edition, grafanaDir string, p syncutil.WorkerPool, g *errutil.Group) error { |
||||
log.Printf("Building %s frontend in %q", edition, grafanaDir) |
||||
grafanaDir, err := filepath.Abs(grafanaDir) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, dpath := range []string{"tmp", "public_gen", "public/build"} { |
||||
dpath = filepath.Join(grafanaDir, dpath) |
||||
if err := os.RemoveAll(dpath); err != nil { |
||||
return fmt.Errorf("failed to remove %q: %w", dpath, err) |
||||
} |
||||
} |
||||
|
||||
p.Schedule(g.Wrap(func() error { |
||||
cmd := exec.Command("yarn", "run", "build") |
||||
cmd.Dir = grafanaDir |
||||
if output, err := cmd.CombinedOutput(); err != nil { |
||||
return fmt.Errorf("failed to build %s frontend with webpack: %s", edition, output) |
||||
} |
||||
|
||||
log.Printf("Finished building %s frontend", edition) |
||||
return nil |
||||
})) |
||||
|
||||
return nil |
||||
} |
@ -1,42 +0,0 @@ |
||||
package frontend |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/blang/semver/v4" |
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
const GrafanaDir = "." |
||||
|
||||
func GetConfig(c *cli.Context, metadata config.Metadata) (config.Config, config.Edition, error) { |
||||
cfg := config.Config{ |
||||
NumWorkers: c.Int("jobs"), |
||||
GitHubToken: c.String("github-token"), |
||||
} |
||||
|
||||
mode := config.Edition(c.String("edition")) |
||||
|
||||
if metadata.ReleaseMode.Mode == config.TagMode && !metadata.ReleaseMode.IsTest { |
||||
packageJSONVersion, err := config.GetPackageJSONVersion(GrafanaDir) |
||||
if err != nil { |
||||
return config.Config{}, "", err |
||||
} |
||||
semverGrafanaVersion, err := semver.Parse(metadata.GrafanaVersion) |
||||
if err != nil { |
||||
return config.Config{}, "", err |
||||
} |
||||
semverPackageJSONVersion, err := semver.Parse(packageJSONVersion) |
||||
if err != nil { |
||||
return config.Config{}, "", err |
||||
} |
||||
// Check if the semver digits of the tag are not equal
|
||||
if semverGrafanaVersion.FinalizeVersion() != semverPackageJSONVersion.FinalizeVersion() { |
||||
return config.Config{}, "", cli.Exit(fmt.Errorf("package.json version and input tag version differ %s != %s.\nPlease update package.json", packageJSONVersion, metadata.GrafanaVersion), 1) |
||||
} |
||||
} |
||||
|
||||
cfg.PackageVersion = metadata.GrafanaVersion |
||||
return cfg, mode, nil |
||||
} |
@ -1,117 +0,0 @@ |
||||
package frontend |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"flag" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/stretchr/testify/require" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
const ( |
||||
jobs = "jobs" |
||||
githubToken = "github-token" |
||||
buildID = "build-id" |
||||
) |
||||
|
||||
type packageJson struct { |
||||
Version string `json:"version"` |
||||
} |
||||
|
||||
type flagObj struct { |
||||
name string |
||||
value string |
||||
} |
||||
|
||||
var app = cli.NewApp() |
||||
|
||||
func TestGetConfig(t *testing.T) { |
||||
tests := []struct { |
||||
ctx *cli.Context |
||||
name string |
||||
packageJsonVersion string |
||||
metadata config.Metadata |
||||
wantErr bool |
||||
}{ |
||||
{ |
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("flagSet", flag.ContinueOnError), flagObj{name: jobs, value: "2"}, flagObj{name: githubToken, value: "token"}), nil), |
||||
name: "package.json matches tag", |
||||
packageJsonVersion: "10.0.0", |
||||
metadata: config.Metadata{GrafanaVersion: "10.0.0", ReleaseMode: config.ReleaseMode{Mode: config.TagMode}}, |
||||
wantErr: false, |
||||
}, |
||||
{ |
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("flagSet", flag.ContinueOnError), flagObj{name: jobs, value: "2"}, flagObj{name: githubToken, value: "token"}), nil), |
||||
name: "custom tag, package.json doesn't match", |
||||
packageJsonVersion: "10.0.0", |
||||
metadata: config.Metadata{GrafanaVersion: "10.0.0-abcd123pre", ReleaseMode: config.ReleaseMode{Mode: config.TagMode}}, |
||||
wantErr: false, |
||||
}, |
||||
{ |
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("flagSet", flag.ContinueOnError), flagObj{name: jobs, value: "2"}, flagObj{name: githubToken, value: "token"}), nil), |
||||
name: "package.json doesn't match tag", |
||||
packageJsonVersion: "10.1.0", |
||||
metadata: config.Metadata{GrafanaVersion: "10.0.0", ReleaseMode: config.ReleaseMode{Mode: config.TagMode}}, |
||||
wantErr: true, |
||||
}, |
||||
{ |
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("flagSet", flag.ContinueOnError), flagObj{name: jobs, value: "2"}, flagObj{name: githubToken, value: "token"}), nil), |
||||
name: "test tag event, check should be skipped", |
||||
packageJsonVersion: "10.1.0", |
||||
metadata: config.Metadata{GrafanaVersion: "10.1.0-test", ReleaseMode: config.ReleaseMode{Mode: config.TagMode, IsTest: true}}, |
||||
wantErr: false, |
||||
}, |
||||
{ |
||||
ctx: cli.NewContext(app, setFlags(t, flag.NewFlagSet("flagSet", flag.ContinueOnError), flagObj{name: jobs, value: "2"}, flagObj{name: githubToken, value: "token"}, flagObj{name: buildID, value: "12345"}), nil), |
||||
name: "non-tag event", |
||||
packageJsonVersion: "10.1.0-pre", |
||||
metadata: config.Metadata{GrafanaVersion: "10.1.0-12345pre", ReleaseMode: config.ReleaseMode{Mode: config.PullRequestMode}}, |
||||
wantErr: false, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
err := createTempPackageJson(t, tt.packageJsonVersion) |
||||
require.NoError(t, err) |
||||
|
||||
got, _, err := GetConfig(tt.ctx, tt.metadata) |
||||
if !tt.wantErr { |
||||
require.Equal(t, got.PackageVersion, tt.metadata.GrafanaVersion) |
||||
} |
||||
|
||||
if tt.wantErr { |
||||
require.Equal(t, got.PackageVersion, "") |
||||
require.Error(t, err) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func setFlags(t *testing.T, flagSet *flag.FlagSet, flags ...flagObj) *flag.FlagSet { |
||||
t.Helper() |
||||
for _, f := range flags { |
||||
if f.name != "" { |
||||
flagSet.StringVar(&f.name, f.name, f.value, "") |
||||
} |
||||
} |
||||
return flagSet |
||||
} |
||||
|
||||
func createTempPackageJson(t *testing.T, version string) error { |
||||
t.Helper() |
||||
|
||||
data := packageJson{Version: version} |
||||
file, _ := json.MarshalIndent(data, "", " ") |
||||
|
||||
err := os.WriteFile("package.json", file, 0644) |
||||
require.NoError(t, err) |
||||
|
||||
t.Cleanup(func() { |
||||
err := os.RemoveAll("package.json") |
||||
require.NoError(t, err) |
||||
}) |
||||
return nil |
||||
} |
@ -1,65 +0,0 @@ |
||||
package fsutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
// CopyRecursive copies files and directories recursively.
|
||||
func CopyRecursive(src, dst string) error { |
||||
sfi, err := os.Stat(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !sfi.IsDir() { |
||||
return CopyFile(src, dst) |
||||
} |
||||
|
||||
if _, err := os.Stat(dst); os.IsNotExist(err) { |
||||
if err := os.MkdirAll(dst, sfi.Mode()); err != nil { |
||||
return fmt.Errorf("failed to create directory %q: %s", dst, err) |
||||
} |
||||
} |
||||
|
||||
entries, err := os.ReadDir(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, entry := range entries { |
||||
srcPath := filepath.Join(src, entry.Name()) |
||||
dstPath := filepath.Join(dst, entry.Name()) |
||||
|
||||
srcFi, err := os.Stat(srcPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
switch srcFi.Mode() & os.ModeType { |
||||
case os.ModeDir: |
||||
if err := CopyRecursive(srcPath, dstPath); err != nil { |
||||
return err |
||||
} |
||||
case os.ModeSymlink: |
||||
link, err := os.Readlink(srcPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := os.Symlink(link, dstPath); err != nil { |
||||
return err |
||||
} |
||||
default: |
||||
if err := CopyFile(srcPath, dstPath); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if srcFi.Mode()&os.ModeSymlink != 0 { |
||||
if err := os.Chmod(dstPath, srcFi.Mode()); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,43 +0,0 @@ |
||||
package fsutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
// CreateTempFile generates a temp filepath, based on the provided suffix.
|
||||
// A typical generated path looks like /var/folders/abcd/abcdefg/A/1137975807.
|
||||
func CreateTempFile(sfx string) (string, error) { |
||||
var suffix string |
||||
if sfx != "" { |
||||
suffix = fmt.Sprintf("*-%s", sfx) |
||||
} else { |
||||
suffix = sfx |
||||
} |
||||
f, err := os.CreateTemp("", suffix) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if err := f.Close(); err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return f.Name(), nil |
||||
} |
||||
|
||||
// CreateTempDir generates a temp directory, based on the provided suffix.
|
||||
// A typical generated path looks like /var/folders/abcd/abcdefg/A/1137975807/.
|
||||
func CreateTempDir(sfx string) (string, error) { |
||||
var suffix string |
||||
if sfx != "" { |
||||
suffix = fmt.Sprintf("*-%s", sfx) |
||||
} else { |
||||
suffix = sfx |
||||
} |
||||
dir, err := os.MkdirTemp("", suffix) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return dir, nil |
||||
} |
@ -1,48 +0,0 @@ |
||||
package fsutil |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestCreateTempFile(t *testing.T) { |
||||
t.Run("empty suffix, expects pattern like: /var/folders/abcd/abcdefg/A/1137975807", func(t *testing.T) { |
||||
filePath, err := CreateTempFile("") |
||||
require.NoError(t, err) |
||||
|
||||
pathParts := strings.Split(filePath, "/") |
||||
require.Greater(t, len(pathParts), 1) |
||||
require.Len(t, strings.Split(pathParts[len(pathParts)-1], "-"), 1) |
||||
}) |
||||
|
||||
t.Run("non-empty suffix, expects /var/folders/abcd/abcdefg/A/1137975807-foobar", func(t *testing.T) { |
||||
filePath, err := CreateTempFile("foobar") |
||||
require.NoError(t, err) |
||||
|
||||
pathParts := strings.Split(filePath, "/") |
||||
require.Greater(t, len(pathParts), 1) |
||||
require.Len(t, strings.Split(pathParts[len(pathParts)-1], "-"), 2) |
||||
}) |
||||
} |
||||
|
||||
func TestCreateTempDir(t *testing.T) { |
||||
t.Run("empty suffix, expects pattern like: /var/folders/abcd/abcdefg/A/1137975807/", func(t *testing.T) { |
||||
filePath, err := CreateTempFile("") |
||||
require.NoError(t, err) |
||||
|
||||
pathParts := strings.Split(filePath, "/") |
||||
require.Greater(t, len(pathParts), 1) |
||||
require.Len(t, strings.Split(pathParts[len(pathParts)-1], "-"), 1) |
||||
}) |
||||
|
||||
t.Run("non-empty suffix, expects /var/folders/abcd/abcdefg/A/1137975807-foobar/", func(t *testing.T) { |
||||
filePath, err := CreateTempFile("foobar") |
||||
require.NoError(t, err) |
||||
|
||||
pathParts := strings.Split(filePath, "/") |
||||
require.Greater(t, len(pathParts), 1) |
||||
require.Len(t, strings.Split(pathParts[len(pathParts)-1], "-"), 2) |
||||
}) |
||||
} |
@ -0,0 +1,72 @@ |
||||
package gcom |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/url" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/versions" |
||||
) |
||||
|
||||
func PackageName(grafana, distro, arch, version, ext string, musl bool, raspberryPi bool) string { |
||||
v := versions.ParseSemver(version) |
||||
|
||||
if raspberryPi { |
||||
grafana += "-rpi" |
||||
} |
||||
|
||||
versionString := strings.Join([]string{v.Major, v.Minor, v.Patch}, ".") |
||||
fmt.Println("Version string:", versionString) |
||||
if distro == "deb" { |
||||
if v.BuildMetadata != "" { |
||||
versionString += "+" + strings.ReplaceAll(v.BuildMetadata, "-", "~") |
||||
} |
||||
|
||||
if v.Prerelease != "" { |
||||
versionString += "~" + v.Prerelease |
||||
} |
||||
|
||||
return strings.Join([]string{grafana, versionString, arch}, "_") + "." + ext |
||||
} |
||||
|
||||
if distro == "rhel" { |
||||
if v.BuildMetadata != "" { |
||||
versionString += "+" + strings.ReplaceAll(v.BuildMetadata, "-", "~") |
||||
} |
||||
|
||||
if v.Prerelease != "" { |
||||
versionString += "~" + v.Prerelease |
||||
} |
||||
|
||||
versionString += "-1" |
||||
|
||||
// Notable difference between our deb naming and our RPM naming: the file ends with `.arch.ext`, not
|
||||
// `_arch.ext`.
|
||||
return strings.Join([]string{grafana, versionString}, "-") + "." + arch + "." + ext |
||||
} |
||||
|
||||
if v.Prerelease != "" { |
||||
versionString += "-" + v.Prerelease |
||||
} |
||||
|
||||
if v.BuildMetadata != "" { |
||||
versionString += "+" + v.BuildMetadata |
||||
} |
||||
|
||||
if musl { |
||||
arch += "-musl" |
||||
} |
||||
|
||||
// grafana-enterprise-1.2.3+example-01.linux-amd64.tar.gz
|
||||
return fmt.Sprintf("%s-%s.%s-%s.%s", grafana, versionString, distro, arch, ext) |
||||
} |
||||
|
||||
func GetURL(baseURL *url.URL, version, grafana, distro, arch, ext string, musl, raspberryPi bool) *url.URL { |
||||
packageName := PackageName(grafana, distro, arch, version, ext, musl, raspberryPi) |
||||
return &url.URL{ |
||||
Host: baseURL.Host, |
||||
Scheme: baseURL.Scheme, |
||||
Path: path.Join(baseURL.Path, packageName), |
||||
} |
||||
} |
@ -0,0 +1,367 @@ |
||||
package gcom_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/gcom" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestPackageName(t *testing.T) { |
||||
type args struct { |
||||
Distro string |
||||
Arch string |
||||
Version string |
||||
Ext string |
||||
Musl bool |
||||
RaspberryPi bool |
||||
|
||||
Expect string |
||||
} |
||||
|
||||
cases := []args{ |
||||
{ |
||||
RaspberryPi: true, |
||||
Distro: "deb", |
||||
Arch: "armhf", |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Expect: "grafana-rpi_1.2.3_armhf.deb", |
||||
}, |
||||
{ |
||||
Distro: "deb", |
||||
Arch: "arm64", |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Expect: "grafana_1.2.3_arm64.deb", |
||||
}, |
||||
{ |
||||
Distro: "rhel", |
||||
Arch: "aarch64", |
||||
Version: "1.2.3", |
||||
Ext: "rpm", |
||||
Expect: "grafana-1.2.3-1.aarch64.rpm", |
||||
}, |
||||
{ |
||||
Distro: "rhel", |
||||
Arch: "aarch64", |
||||
Ext: "rpm.sha256", |
||||
Version: "1.2.3", |
||||
Expect: "grafana-1.2.3-1.aarch64.rpm.sha256", |
||||
}, |
||||
{ |
||||
Distro: "rhel", |
||||
Ext: "rpm", |
||||
Version: "1.2.3", |
||||
Arch: "x86_64", |
||||
Expect: "grafana-1.2.3-1.x86_64.rpm", |
||||
}, |
||||
{ |
||||
Distro: "rhel", |
||||
Ext: "rpm.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "x86_64", |
||||
Expect: "grafana-1.2.3-1.x86_64.rpm.sha256", |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.darwin-amd64.tar.gz", |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.darwin-amd64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.darwin-arm64-musl.tar.gz", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.darwin-arm64-musl.tar.gz.sha256", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.darwin-arm64.tar.gz", |
||||
}, |
||||
{ |
||||
Distro: "darwin", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.darwin-arm64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.linux-amd64-musl.tar.gz", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.linux-amd64-musl.tar.gz.sha256", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.linux-amd64.tar.gz", |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Expect: "grafana-1.2.3.linux-amd64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.linux-arm64-musl.tar.gz", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.linux-arm64-musl.tar.gz.sha256", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Distro: "linux", |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.linux-arm64.tar.gz", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.linux-arm64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv6", |
||||
Expect: "grafana-1.2.3.linux-armv6.tar.gz", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv6", |
||||
Expect: "grafana-1.2.3.linux-armv6.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv7", |
||||
Expect: "grafana-1.2.3.linux-armv7-musl.tar.gz", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv7", |
||||
Expect: "grafana-1.2.3.linux-armv7-musl.tar.gz.sha256", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv7", |
||||
Expect: "grafana-1.2.3.linux-armv7.tar.gz", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz.sha256", |
||||
Version: "1.2.3", |
||||
Distro: "linux", |
||||
Arch: "armv7", |
||||
Expect: "grafana-1.2.3.linux-armv7.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Ext: "exe", |
||||
Distro: "windows", |
||||
Expect: "grafana-1.2.3.windows-amd64.exe", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Distro: "windows", |
||||
Ext: "exe.sha256", |
||||
Expect: "grafana-1.2.3.windows-amd64.exe.sha256", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Distro: "windows", |
||||
Ext: "msi", |
||||
Expect: "grafana-1.2.3.windows-amd64.msi", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Arch: "amd64", |
||||
Distro: "windows", |
||||
Ext: "msi.sha256", |
||||
Expect: "grafana-1.2.3.windows-amd64.msi.sha256", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Expect: "grafana-1.2.3.windows-amd64.tar.gz", |
||||
Arch: "amd64", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Arch: "amd64", |
||||
Ext: "tar.gz.sha256", |
||||
Expect: "grafana-1.2.3.windows-amd64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Expect: "grafana-1.2.3.windows-amd64.zip", |
||||
Ext: "zip", |
||||
Arch: "amd64", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Expect: "grafana-1.2.3.windows-amd64.zip.sha256", |
||||
Ext: "zip.sha256", |
||||
Arch: "amd64", |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.windows-arm64-musl.tar.gz", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "tar.gz.sha256", |
||||
Distro: "windows", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.windows-arm64-musl.tar.gz.sha256", |
||||
Musl: true, |
||||
}, |
||||
{ |
||||
Ext: "tar.gz", |
||||
Version: "1.2.3", |
||||
Distro: "windows", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.windows-arm64.tar.gz", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "tar.gz.sha256", |
||||
Distro: "windows", |
||||
Arch: "arm64", |
||||
Expect: "grafana-1.2.3.windows-arm64.tar.gz.sha256", |
||||
}, |
||||
{ |
||||
RaspberryPi: true, |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Arch: "armhf", |
||||
Distro: "deb", |
||||
Expect: "grafana-rpi_1.2.3_armhf.deb", |
||||
}, |
||||
{ |
||||
RaspberryPi: true, |
||||
Version: "1.2.3", |
||||
Ext: "deb.sha256", |
||||
Distro: "deb", |
||||
Arch: "armhf", |
||||
Expect: "grafana-rpi_1.2.3_armhf.deb.sha256", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Distro: "deb", |
||||
Expect: "grafana_1.2.3_amd64.deb", |
||||
Arch: "amd64", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb.sha256", |
||||
Distro: "deb", |
||||
Expect: "grafana_1.2.3_amd64.deb.sha256", |
||||
Arch: "amd64", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Arch: "arm64", |
||||
Distro: "deb", |
||||
Expect: "grafana_1.2.3_arm64.deb", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb.sha256", |
||||
Arch: "arm64", |
||||
Distro: "deb", |
||||
Expect: "grafana_1.2.3_arm64.deb.sha256", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb", |
||||
Distro: "deb", |
||||
Arch: "armhf", |
||||
Expect: "grafana_1.2.3_armhf.deb", |
||||
}, |
||||
{ |
||||
Version: "1.2.3", |
||||
Ext: "deb.sha256", |
||||
Arch: "armhf", |
||||
Distro: "deb", |
||||
Expect: "grafana_1.2.3_armhf.deb.sha256", |
||||
}, |
||||
} |
||||
|
||||
for i, v := range cases { |
||||
t.Run(fmt.Sprintf("[%d / %d] %s", i+1, len(cases), v.Expect), func(t *testing.T) { |
||||
n := gcom.PackageName("grafana", v.Distro, v.Arch, v.Version, v.Ext, v.Musl, v.RaspberryPi) |
||||
require.Equal(t, v.Expect, n) |
||||
}) |
||||
} |
||||
} |
@ -1,56 +0,0 @@ |
||||
package git_test |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/google/go-github/v45/github" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/git" |
||||
) |
||||
|
||||
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) |
||||
}) |
||||
} |
@ -1,135 +0,0 @@ |
||||
package git_test |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/google/go-github/v45/github" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/git" |
||||
) |
||||
|
||||
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) |
||||
}) |
||||
} |
@ -1,57 +0,0 @@ |
||||
package git_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/git" |
||||
) |
||||
|
||||
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) |
||||
} |
||||
} |
@ -1,124 +0,0 @@ |
||||
package golangutils |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
) |
||||
|
||||
type BuildOpts struct { |
||||
// Package refers to the path to the `main` package containing `func main`
|
||||
Package string |
||||
|
||||
// Output is used as the -o argument in the go build command
|
||||
Output string |
||||
|
||||
// Workdir should define some place in the module where the package path resolves.
|
||||
// Go commands need to be ran inside a the Go module directory.
|
||||
Workdir string |
||||
|
||||
GoOS config.OS |
||||
GoArch config.Architecture |
||||
GoArm string |
||||
Go386 string |
||||
CC string |
||||
LibC string |
||||
|
||||
CGoEnabled bool |
||||
CGoCFlags string |
||||
|
||||
// LdFlags are joined by a space character and provided to the -ldflags argument.
|
||||
// A valid element here would be `-X 'main.version=1.0.0'`.
|
||||
LdFlags []string |
||||
|
||||
Stdout io.ReadWriter |
||||
Stderr io.ReadWriter |
||||
Stdin io.ReadWriter |
||||
|
||||
// ExtraEnv allows consumers to provide extra env args that are not defined above.
|
||||
// A single element should be formatted using like so: {NAME}={VALUE}. Example: GOOS=linux.
|
||||
ExtraEnv []string |
||||
|
||||
// ExtraArgs allows consumers to provide extra arguments that are not defined above.
|
||||
// Flag names and values should be two separate elements.
|
||||
// These flags will be appended to the command arguments before the package path in "go build".
|
||||
ExtraArgs []string |
||||
} |
||||
|
||||
// Env constructs a list of key/value pairs for setting a build command's environment.
|
||||
// Should we consider using something to unmarshal the struct to env?
|
||||
func (opts BuildOpts) Env() []string { |
||||
env := []string{} |
||||
if opts.CGoEnabled { |
||||
env = append(env, "CGO_ENABLED=1") |
||||
} |
||||
|
||||
if opts.GoOS != "" { |
||||
env = append(env, fmt.Sprintf("GOOS=%s", opts.GoOS)) |
||||
} |
||||
|
||||
if opts.GoArch != "" { |
||||
env = append(env, fmt.Sprintf("GOARCH=%s", opts.GoArch)) |
||||
} |
||||
|
||||
if opts.CC != "" { |
||||
env = append(env, fmt.Sprintf("CC=%s", opts.CC)) |
||||
} |
||||
|
||||
if opts.CGoCFlags != "" { |
||||
env = append(env, fmt.Sprintf("CGO_CFLAGS=%s", opts.CGoCFlags)) |
||||
} |
||||
|
||||
if opts.GoArm != "" { |
||||
env = append(env, fmt.Sprintf("GOARM=%s", opts.GoArm)) |
||||
} |
||||
|
||||
if opts.ExtraEnv != nil { |
||||
return append(opts.ExtraEnv, env...) |
||||
} |
||||
|
||||
return env |
||||
} |
||||
|
||||
// Args constructs a list of flags and values for use with the exec.Command type when running "go build".
|
||||
func (opts BuildOpts) Args() []string { |
||||
args := []string{} |
||||
|
||||
if opts.LdFlags != nil { |
||||
args = append(args, "-ldflags", strings.Join(opts.LdFlags, " ")) |
||||
} |
||||
|
||||
if opts.Output != "" { |
||||
args = append(args, "-o", opts.Output) |
||||
} |
||||
|
||||
if opts.ExtraArgs != nil { |
||||
args = append(args, opts.ExtraArgs...) |
||||
} |
||||
|
||||
args = append(args, opts.Package) |
||||
|
||||
return args |
||||
} |
||||
|
||||
// Build runs the go build process in the current shell given the opts.
|
||||
// This function will panic if no Stdout/Stderr/Stdin is provided in the opts.
|
||||
func RunBuild(ctx context.Context, opts BuildOpts) error { |
||||
env := opts.Env() |
||||
args := append([]string{"build"}, opts.Args()...) |
||||
// Ignore gosec G304 as this function is only used in the build process.
|
||||
//nolint:gosec
|
||||
cmd := exec.CommandContext(ctx, "go", args...) |
||||
cmd.Env = env |
||||
|
||||
cmd.Stdout = opts.Stdout |
||||
cmd.Stderr = opts.Stderr |
||||
cmd.Stdin = opts.Stdin |
||||
cmd.Dir = opts.Workdir |
||||
|
||||
return cmd.Run() |
||||
} |
@ -1,2 +0,0 @@ |
||||
// Package golangutils holds utility functions, wrappers, and types for building Go binaries for Grafana.
|
||||
package golangutils |
@ -1,73 +0,0 @@ |
||||
package gpg |
||||
|
||||
import ( |
||||
"encoding/base64" |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/fsutil" |
||||
) |
||||
|
||||
// LoadGPGKeys loads GPG key pair and password from the environment and writes them to corresponding files.
|
||||
//
|
||||
// The passed config's GPG fields also get updated. Make sure to call RemoveGPGFiles at application exit.
|
||||
func LoadGPGKeys(cfg *config.Config) error { |
||||
var err error |
||||
cfg.GPGPrivateKey, err = fsutil.CreateTempFile("priv.key") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
cfg.GPGPublicKey, err = fsutil.CreateTempFile("pub.key") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
cfg.GPGPassPath, err = fsutil.CreateTempFile("") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
gpgPrivKey := os.Getenv("GPG_PRIV_KEY") |
||||
if gpgPrivKey == "" { |
||||
return fmt.Errorf("$GPG_PRIV_KEY must be defined") |
||||
} |
||||
gpgPubKey := os.Getenv("GPG_PUB_KEY") |
||||
if gpgPubKey == "" { |
||||
return fmt.Errorf("$GPG_PUB_KEY must be defined") |
||||
} |
||||
gpgPass := os.Getenv("GPG_KEY_PASSWORD") |
||||
if gpgPass == "" { |
||||
return fmt.Errorf("$GPG_KEY_PASSWORD must be defined") |
||||
} |
||||
|
||||
gpgPrivKeyB, err := base64.StdEncoding.DecodeString(gpgPrivKey) |
||||
if err != nil { |
||||
return fmt.Errorf("couldn't decode $GPG_PRIV_KEY: %w", err) |
||||
} |
||||
gpgPubKeyB, err := base64.StdEncoding.DecodeString(gpgPubKey) |
||||
if err != nil { |
||||
return fmt.Errorf("couldn't decode $GPG_PUB_KEY: %w", err) |
||||
} |
||||
|
||||
if err := os.WriteFile(cfg.GPGPrivateKey, append(gpgPrivKeyB, '\n'), 0400); err != nil { |
||||
return fmt.Errorf("failed to write GPG private key file: %w", err) |
||||
} |
||||
if err := os.WriteFile(cfg.GPGPublicKey, append(gpgPubKeyB, '\n'), 0400); err != nil { |
||||
return fmt.Errorf("failed to write GPG public key file: %w", err) |
||||
} |
||||
if err := os.WriteFile(cfg.GPGPassPath, []byte(gpgPass+"\n"), 0400); err != nil { |
||||
return fmt.Errorf("failed to write GPG password file: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// RemoveGPGFiles removes configured GPG files.
|
||||
func RemoveGPGFiles(cfg config.Config) { |
||||
for _, fpath := range []string{cfg.GPGPrivateKey, cfg.GPGPublicKey, cfg.GPGPassPath} { |
||||
if err := os.Remove(fpath); err != nil { |
||||
log.Printf("failed to remove %q", fpath) |
||||
} |
||||
} |
||||
} |
@ -1,73 +0,0 @@ |
||||
package gpg |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/fsutil" |
||||
) |
||||
|
||||
// writeRpmMacros writes ~/.rpmmacros.
|
||||
func writeRpmMacros(homeDir, gpgPassPath string) error { |
||||
fpath := filepath.Join(homeDir, ".rpmmacros") |
||||
content := fmt.Sprintf(`%%_signature gpg |
||||
%%_gpg_path %s/.gnupg |
||||
%%_gpg_name Grafana |
||||
%%_gpgbin /usr/bin/gpg |
||||
%%__gpg_sign_cmd %%{__gpg} gpg --batch --yes --pinentry-mode loopback --no-armor --passphrase-file %s --no-secmem-warning -u "%%{_gpg_name}" -sbo %%{__signature_filename} %%{__plaintext_filename} |
||||
`, homeDir, gpgPassPath) |
||||
//nolint:gosec
|
||||
if err := os.WriteFile(fpath, []byte(content), 0600); err != nil { |
||||
return fmt.Errorf("failed to write %q: %w", fpath, err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Import imports the GPG package signing key.
|
||||
// ~/.rpmmacros also gets written.
|
||||
func Import(cfg config.Config) error { |
||||
exists, err := fsutil.Exists(cfg.GPGPrivateKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !exists { |
||||
return fmt.Errorf("GPG private key file doesn't exist: %q", cfg.GPGPrivateKey) |
||||
} |
||||
|
||||
log.Printf("Importing GPG key %q...", cfg.GPGPrivateKey) |
||||
// nolint:gosec
|
||||
cmd := exec.Command("gpg", "--batch", "--yes", "--no-tty", "--allow-secret-key-import", "--import", |
||||
cfg.GPGPrivateKey) |
||||
if output, err := cmd.CombinedOutput(); err != nil { |
||||
return fmt.Errorf("failed to import private key: %s", output) |
||||
} |
||||
|
||||
homeDir, err := os.UserHomeDir() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := writeRpmMacros(homeDir, cfg.GPGPassPath); err != nil { |
||||
return err |
||||
} |
||||
|
||||
pubKeysPath := filepath.Join(homeDir, ".rpmdb", "pubkeys") |
||||
if err := os.MkdirAll(pubKeysPath, 0700); err != nil { |
||||
return fmt.Errorf("failed to make %s: %w", pubKeysPath, err) |
||||
} |
||||
gpgPub, err := os.ReadFile(cfg.GPGPublicKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
//nolint:gosec
|
||||
if err := os.WriteFile(filepath.Join(homeDir, ".rpmdb", "pubkeys", "grafana.key"), gpgPub, 0400); err != nil { |
||||
return fmt.Errorf("failed to write pub key to ~/.rpmdb: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,129 +0,0 @@ |
||||
package grafana |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/cryptoutil" |
||||
"github.com/grafana/grafana/pkg/build/golangutils" |
||||
) |
||||
|
||||
var binaries = []string{"grafana", "grafana-server", "grafana-cli"} |
||||
|
||||
const ( |
||||
SuffixEnterprise2 = "-enterprise2" |
||||
) |
||||
|
||||
const ( |
||||
ExtensionExe = ".exe" |
||||
) |
||||
|
||||
func GrafanaLDFlags(version string, r config.Revision) []string { |
||||
cmd := []string{ |
||||
"-w", |
||||
fmt.Sprintf("-X main.version=%s", version), |
||||
fmt.Sprintf("-X main.commit=%s", r.SHA256), |
||||
fmt.Sprintf("-X main.buildstamp=%d", r.Timestamp), |
||||
fmt.Sprintf("-X main.buildBranch=%s", r.Branch), |
||||
} |
||||
|
||||
if r.EnterpriseCommit != "" { |
||||
cmd = append(cmd, fmt.Sprintf("-X main.enterpriseCommit=%s", r.EnterpriseCommit)) |
||||
} |
||||
|
||||
return cmd |
||||
} |
||||
|
||||
// BinaryFolder returns the path to where the Grafana binary is build given the provided arguments.
|
||||
func BinaryFolder(edition config.Edition, args BuildArgs) string { |
||||
sfx := "" |
||||
if edition == config.EditionEnterprise2 { |
||||
sfx = SuffixEnterprise2 |
||||
} |
||||
|
||||
arch := string(args.GoArch) |
||||
if args.GoArch == config.ArchARM { |
||||
arch = string(args.GoArch) + "v" + args.GoArm |
||||
} |
||||
|
||||
format := fmt.Sprintf("%s-%s", args.GoOS, arch) |
||||
if args.LibC != "" { |
||||
format += fmt.Sprintf("-%s", args.LibC) |
||||
} |
||||
format += sfx |
||||
|
||||
if args.GoOS == config.OSWindows { |
||||
format += ExtensionExe |
||||
} |
||||
|
||||
return format |
||||
} |
||||
|
||||
func GrafanaDescriptor(opts golangutils.BuildOpts) string { |
||||
libcPart := "" |
||||
if opts.LibC != "" { |
||||
libcPart = fmt.Sprintf("/%s", opts.LibC) |
||||
} |
||||
arch := string(opts.GoArch) |
||||
if opts.GoArch == config.ArchARM { |
||||
arch = string(opts.GoArch) + "v" + opts.GoArm |
||||
} |
||||
|
||||
return fmt.Sprintf("%s/%s%s", opts.GoOS, arch, libcPart) |
||||
} |
||||
|
||||
// BuildGrafanaBinary builds a certain binary according to certain parameters.
|
||||
func BuildGrafanaBinary(ctx context.Context, name, version string, args BuildArgs, edition config.Edition) error { |
||||
opts := args.BuildOpts |
||||
opts.ExtraEnv = os.Environ() |
||||
|
||||
revision, err := config.GrafanaRevision(ctx, opts.Workdir) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
folder := BinaryFolder(edition, args) |
||||
|
||||
if opts.GoOS == config.OSWindows { |
||||
name += ExtensionExe |
||||
} |
||||
|
||||
binary := filepath.Join(opts.Workdir, "bin", folder, name) |
||||
opts.Output = binary |
||||
|
||||
if err := os.RemoveAll(binary); err != nil { |
||||
return fmt.Errorf("failed to remove %q: %w", binary, err) |
||||
} |
||||
|
||||
if err := os.RemoveAll(binary + ".md5"); err != nil { |
||||
return fmt.Errorf("failed to remove %q: %w", binary+".md5", err) |
||||
} |
||||
|
||||
descriptor := GrafanaDescriptor(opts) |
||||
|
||||
log.Printf("Building %q for %s", binary, descriptor) |
||||
|
||||
opts.LdFlags = append(args.LdFlags, GrafanaLDFlags(version, revision)...) |
||||
|
||||
if edition == config.EditionEnterprise2 { |
||||
opts.ExtraArgs = []string{"-tags=pro"} |
||||
} |
||||
|
||||
log.Printf("Running command 'go %s'", opts.Args()) |
||||
|
||||
if err := golangutils.RunBuild(ctx, opts); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Create an MD5 checksum of the binary, to be included in the archive for
|
||||
// automatic upgrades.
|
||||
if err := cryptoutil.MD5File(binary); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,2 +0,0 @@ |
||||
// Package packaging holds functions and types for creating the tar.gz, deb, and rpm packages of Grafana.
|
||||
package packaging |
@ -1 +0,0 @@ |
||||
package packaging |
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@ |
||||
package packaging_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/packaging" |
||||
) |
||||
|
||||
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()) |
||||
} |
||||
}) |
||||
} |
@ -1,66 +0,0 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/config" |
||||
"github.com/grafana/grafana/pkg/build/errutil" |
||||
"github.com/grafana/grafana/pkg/build/fsutil" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
type PluginSigningMode = int |
||||
|
||||
// BuildPlugins builds internal plugins.
|
||||
// The built plugins are placed in plugins-bundled/dist/.
|
||||
func Build(ctx context.Context, grafanaDir string, p syncutil.WorkerPool, g *errutil.Group, verMode *config.BuildConfig) error { |
||||
log.Printf("Building plugins in %q...", grafanaDir) |
||||
|
||||
root := filepath.Join(grafanaDir, "plugins-bundled", "internal") |
||||
fis, err := os.ReadDir(root) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for i := range fis { |
||||
fi := fis[i] |
||||
if !fi.IsDir() { |
||||
continue |
||||
} |
||||
|
||||
dpath := filepath.Join(root, fi.Name()) |
||||
|
||||
p.Schedule(g.Wrap(func() error { |
||||
log.Printf("Building plugin %q...", dpath) |
||||
|
||||
cmd := exec.Command("yarn", "build") |
||||
cmd.Dir = dpath |
||||
if output, err := cmd.CombinedOutput(); err != nil { |
||||
return fmt.Errorf("yarn build failed: %s", output) |
||||
} |
||||
|
||||
dstPath := filepath.Join("plugins-bundled", "dist", fi.Name()) |
||||
if err := fsutil.CopyRecursive(filepath.Join(dpath, "dist"), dstPath); err != nil { |
||||
return err |
||||
} |
||||
if !verMode.PluginSignature.Sign { |
||||
return nil |
||||
} |
||||
|
||||
return BuildManifest(ctx, dstPath, verMode.PluginSignature.AdminSign) |
||||
})) |
||||
} |
||||
|
||||
if err := g.Wait(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
log.Printf("Built all plug-ins successfully!") |
||||
|
||||
return nil |
||||
} |
@ -1,118 +0,0 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/sha256" |
||||
"encoding/hex" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/build/errutil" |
||||
"github.com/grafana/grafana/pkg/build/syncutil" |
||||
) |
||||
|
||||
// logCloseError executes the closeFunc; if it returns an error, it is logged by the log package.
|
||||
func logCloseError(closeFunc func() error) { |
||||
if err := closeFunc(); err != nil { |
||||
log.Println(err) |
||||
} |
||||
} |
||||
|
||||
// logCloseError executes the closeFunc; if it returns an error, it is logged by the log package.
|
||||
func logError(err error) { |
||||
if err != nil { |
||||
log.Println(err) |
||||
} |
||||
} |
||||
|
||||
// pluginManifest has details of an external plugin package.
|
||||
type pluginManifest struct { |
||||
Name string `json:"name"` |
||||
Version string `json:"version"` |
||||
Checksum string `json:"checksum"` |
||||
} |
||||
|
||||
// pluginsManifest represents a manifest of Grafana's external plugins.
|
||||
type pluginsManifest struct { |
||||
Plugins []pluginManifest `json:"plugins"` |
||||
} |
||||
|
||||
// downloadPlugins downloads Grafana plugins that should be bundled into packages.
|
||||
//
|
||||
// The plugin archives are downloaded into <grafanaDir>/plugins-bundled.
|
||||
func Download(ctx context.Context, grafanaDir string, p syncutil.WorkerPool) error { |
||||
g, _ := errutil.GroupWithContext(ctx) |
||||
|
||||
log.Println("Downloading external plugins...") |
||||
|
||||
var m pluginsManifest |
||||
manifestPath := filepath.Join(grafanaDir, "plugins-bundled", "external.json") |
||||
//nolint:gosec
|
||||
manifestB, err := os.ReadFile(manifestPath) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to open plugins manifest %q: %w", manifestPath, err) |
||||
} |
||||
if err := json.Unmarshal(manifestB, &m); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for i := range m.Plugins { |
||||
pm := m.Plugins[i] |
||||
p.Schedule(g.Wrap(func() error { |
||||
tgt := filepath.Join(grafanaDir, "plugins-bundled", fmt.Sprintf("%s-%s.zip", pm.Name, pm.Version)) |
||||
//nolint:gosec
|
||||
out, err := os.Create(tgt) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(out.Close) |
||||
|
||||
u := fmt.Sprintf("http://storage.googleapis.com/plugins-ci/plugins/%s/%s-%s.zip", pm.Name, pm.Name, |
||||
pm.Version) |
||||
log.Printf("Downloading plugin %q to %q...", u, tgt) |
||||
// nolint:gosec
|
||||
resp, err := http.Get(u) |
||||
if err != nil { |
||||
return fmt.Errorf("downloading %q failed: %w", u, err) |
||||
} |
||||
defer logError(resp.Body.Close()) |
||||
|
||||
if resp.StatusCode != http.StatusOK { |
||||
return fmt.Errorf("failed to download %q, status code %d", u, resp.StatusCode) |
||||
} |
||||
|
||||
if _, err := io.Copy(out, resp.Body); err != nil { |
||||
return fmt.Errorf("downloading %q failed: %w", u, err) |
||||
} |
||||
if err := out.Close(); err != nil { |
||||
return fmt.Errorf("downloading %q failed: %w", u, err) |
||||
} |
||||
|
||||
//nolint:gosec
|
||||
fd, err := os.Open(tgt) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(fd.Close) |
||||
|
||||
h := sha256.New() |
||||
if _, err := io.Copy(h, fd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
chksum := hex.EncodeToString(h.Sum(nil)) |
||||
if chksum != pm.Checksum { |
||||
return fmt.Errorf("plugin %q has bad checksum: %s (expected %s)", u, chksum, pm.Checksum) |
||||
} |
||||
|
||||
return Unzip(tgt, filepath.Join(grafanaDir, "plugins-bundled")) |
||||
})) |
||||
} |
||||
|
||||
return g.Wait() |
||||
} |
@ -1,204 +0,0 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"crypto/sha256" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
type manifest struct { |
||||
Plugin string `json:"plugin"` |
||||
Version string `json:"version"` |
||||
Files map[string]string `json:"files"` |
||||
} |
||||
|
||||
func getManifest(dpath string, chksums map[string]string) (manifest, error) { |
||||
m := manifest{} |
||||
|
||||
type pluginInfo struct { |
||||
Version string `json:"version"` |
||||
} |
||||
|
||||
type plugin struct { |
||||
ID string `json:"id"` |
||||
Info pluginInfo `json:"info"` |
||||
} |
||||
|
||||
//nolint:gosec
|
||||
f, err := os.Open(filepath.Join(dpath, "plugin.json")) |
||||
if err != nil { |
||||
return m, err |
||||
} |
||||
decoder := json.NewDecoder(f) |
||||
var p plugin |
||||
if err := decoder.Decode(&p); err != nil { |
||||
return m, err |
||||
} |
||||
|
||||
if p.ID == "" { |
||||
return m, fmt.Errorf("plugin.json doesn't define id") |
||||
} |
||||
if p.Info.Version == "" { |
||||
return m, fmt.Errorf("plugin.json doesn't define info.version") |
||||
} |
||||
|
||||
return manifest{ |
||||
Plugin: p.ID, |
||||
Version: p.Info.Version, |
||||
Files: chksums, |
||||
}, nil |
||||
} |
||||
|
||||
// BuildManifest requests a plugin's signed manifest file fromt he Grafana API.
|
||||
// If signingAdmin is true, the manifest signing admin endpoint (without plugin ID) will be used, and requires
|
||||
// an admin API key.
|
||||
func BuildManifest(ctx context.Context, dpath string, signingAdmin bool) error { |
||||
log.Printf("Building manifest for plug-in at %q", dpath) |
||||
|
||||
apiKey := os.Getenv("GRAFANA_API_KEY") |
||||
if apiKey == "" { |
||||
return fmt.Errorf("GRAFANA_API_KEY must be set") |
||||
} |
||||
|
||||
manifestPath := filepath.Join(dpath, "MANIFEST.txt") |
||||
chksums, err := getChksums(dpath, manifestPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
m, err := getManifest(dpath, chksums) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
b := bytes.NewBuffer(nil) |
||||
encoder := json.NewEncoder(b) |
||||
if err := encoder.Encode(&m); err != nil { |
||||
return err |
||||
} |
||||
jsonB := b.Bytes() |
||||
u := "https://grafana.com/api/plugins/ci/sign" |
||||
if !signingAdmin { |
||||
u = fmt.Sprintf("https://grafana.com/api/plugins/%s/ci/sign", m.Plugin) |
||||
} |
||||
log.Printf("Requesting signed manifest from Grafana API...") |
||||
req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(jsonB)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiKey)) |
||||
req.Header.Add("Content-Type", "application/json") |
||||
resp, err := http.DefaultClient.Do(req) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to get signed manifest from Grafana API: %w", err) |
||||
} |
||||
defer func() { |
||||
if err := resp.Body.Close(); err != nil { |
||||
log.Println("failed to close response body, err: %w", err) |
||||
} |
||||
}() |
||||
if resp.StatusCode != 200 { |
||||
msg, err := io.ReadAll(resp.Body) |
||||
if err != nil { |
||||
log.Printf("Failed to read response body: %s", err) |
||||
msg = []byte("") |
||||
} |
||||
return fmt.Errorf("request for signed manifest failed with status code %d: %s", resp.StatusCode, string(msg)) |
||||
} |
||||
|
||||
log.Printf("Successfully signed manifest via Grafana API, writing to %q", manifestPath) |
||||
//nolint:gosec
|
||||
f, err := os.Create(manifestPath) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to create %s: %w", manifestPath, err) |
||||
} |
||||
defer func() { |
||||
if err := f.Close(); err != nil { |
||||
log.Println("failed to close file, err: %w", err) |
||||
} |
||||
}() |
||||
if _, err := io.Copy(f, resp.Body); err != nil { |
||||
return fmt.Errorf("failed to write %s: %w", manifestPath, err) |
||||
} |
||||
if err := f.Close(); err != nil { |
||||
return fmt.Errorf("failed to write %s: %w", manifestPath, err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func getChksums(dpath, manifestPath string) (map[string]string, error) { |
||||
manifestPath = filepath.Clean(manifestPath) |
||||
|
||||
chksums := map[string]string{} |
||||
if err := filepath.Walk(dpath, func(path string, fi os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if fi.IsDir() { |
||||
return nil |
||||
} |
||||
|
||||
path = filepath.Clean(path) |
||||
|
||||
// Handle symbolic links
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink { |
||||
finalPath, err := filepath.EvalSymlinks(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
log.Printf("Handling symlink %q, pointing to %q", path, finalPath) |
||||
|
||||
info, err := os.Stat(finalPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if info.IsDir() { |
||||
return nil |
||||
} |
||||
|
||||
if _, err := filepath.Rel(dpath, finalPath); err != nil { |
||||
return fmt.Errorf("symbolic link %q targets a file outside of the plugin directory: %q", path, finalPath) |
||||
} |
||||
|
||||
if finalPath == manifestPath { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
if path == manifestPath { |
||||
return nil |
||||
} |
||||
|
||||
h := sha256.New() |
||||
//nolint:gosec
|
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(f.Close) |
||||
if _, err := io.Copy(h, f); err != nil { |
||||
return err |
||||
} |
||||
|
||||
relPath, err := filepath.Rel(dpath, path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
chksums[relPath] = fmt.Sprintf("%x", h.Sum(nil)) |
||||
|
||||
return nil |
||||
}); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return chksums, nil |
||||
} |
@ -1,64 +0,0 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
// Unzip unzips a plugin.
|
||||
func Unzip(fpath, tgtDir string) error { |
||||
log.Printf("Unzipping plugin %q into %q...", fpath, tgtDir) |
||||
|
||||
r, err := zip.OpenReader(fpath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(r.Close) |
||||
|
||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||
extractAndWriteFile := func(f *zip.File) error { |
||||
log.Printf("Extracting zip member %q...", f.Name) |
||||
|
||||
rc, err := f.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(rc.Close) |
||||
|
||||
//nolint:gosec
|
||||
dstPath := filepath.Join(tgtDir, f.Name) |
||||
|
||||
if f.FileInfo().IsDir() { |
||||
return os.MkdirAll(dstPath, f.Mode()) |
||||
} |
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), f.Mode()); err != nil { |
||||
return err |
||||
} |
||||
|
||||
//nolint:gosec
|
||||
fd, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer logCloseError(fd.Close) |
||||
|
||||
// nolint:gosec
|
||||
if _, err := io.Copy(fd, rc); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return fd.Close() |
||||
} |
||||
|
||||
for _, f := range r.File { |
||||
if err := extractAndWriteFile(f); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,10 +0,0 @@ |
||||
package stringutil |
||||
|
||||
func Contains(arr []string, s string) bool { |
||||
for _, e := range arr { |
||||
if e == s { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
@ -1,43 +0,0 @@ |
||||
package syncutil |
||||
|
||||
import ( |
||||
"log" |
||||
"runtime" |
||||
) |
||||
|
||||
func worker(jobs chan func()) { |
||||
for j := range jobs { |
||||
j() |
||||
} |
||||
} |
||||
|
||||
// WorkerPool represents a concurrent worker pool.
|
||||
type WorkerPool struct { |
||||
NumWorkers int |
||||
jobs chan func() |
||||
} |
||||
|
||||
// NewWorkerPool constructs a new WorkerPool.
|
||||
func NewWorkerPool(numWorkers int) WorkerPool { |
||||
if numWorkers <= 0 { |
||||
numWorkers = runtime.NumCPU() |
||||
} |
||||
log.Printf("Creating worker pool with %d workers", numWorkers) |
||||
jobs := make(chan func(), 100) |
||||
for i := 0; i < numWorkers; i++ { |
||||
go worker(jobs) |
||||
} |
||||
return WorkerPool{ |
||||
NumWorkers: numWorkers, |
||||
jobs: jobs, |
||||
} |
||||
} |
||||
|
||||
// Schedule schedules a job to be executed by a worker in the pool.
|
||||
func (p WorkerPool) Schedule(job func()) { |
||||
p.jobs <- job |
||||
} |
||||
|
||||
func (p WorkerPool) Close() { |
||||
close(p.jobs) |
||||
} |
@ -0,0 +1,31 @@ |
||||
package versions |
||||
|
||||
import "regexp" |
||||
|
||||
var semverRegex = regexp.MustCompile(`^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) |
||||
|
||||
type Semver struct { |
||||
Major string |
||||
Minor string |
||||
Patch string |
||||
Prerelease string |
||||
BuildMetadata string |
||||
} |
||||
|
||||
func ParseSemver(version string) Semver { |
||||
matches := semverRegex.FindStringSubmatch(version) |
||||
results := make(map[string]string) |
||||
for i, name := range semverRegex.SubexpNames() { |
||||
if i != 0 && name != "" { |
||||
results[name] = matches[i] |
||||
} |
||||
} |
||||
|
||||
return Semver{ |
||||
Major: results["major"], |
||||
Minor: results["minor"], |
||||
Patch: results["patch"], |
||||
Prerelease: results["prerelease"], |
||||
BuildMetadata: results["buildmetadata"], |
||||
} |
||||
} |
Loading…
Reference in new issue