CI: move grafana-build into pkg/build (#105640)
* move grafana-build into pkg/build
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
func Action(r Registerer, c *cli.Context) error {
|
||||
// ArtifactStrings represent an artifact with a list of boolean options, like
|
||||
// targz:linux/amd64:enterprise
|
||||
artifactStrings := c.StringSlice("artifacts")
|
||||
|
||||
logLevel := slog.LevelInfo
|
||||
if c.Bool("verbose") {
|
||||
logLevel = slog.LevelDebug
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = c.Context
|
||||
log = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
}))
|
||||
parallel = c.Int64("parallel")
|
||||
destination = c.String("destination")
|
||||
platform = dagger.Platform(c.String("platform"))
|
||||
verify = c.Bool("verify")
|
||||
checksum = c.Bool("checksum")
|
||||
)
|
||||
|
||||
if len(artifactStrings) == 0 {
|
||||
return errors.New("no artifacts specified. At least 1 artifact is required using the '--artifact' or '-a' flag")
|
||||
}
|
||||
|
||||
log.Debug("Connecting to dagger daemon...")
|
||||
daggerOpts := []dagger.ClientOpt{}
|
||||
if logLevel == slog.LevelDebug {
|
||||
daggerOpts = append(daggerOpts, dagger.WithLogOutput(os.Stderr))
|
||||
}
|
||||
client, err := dagger.Connect(ctx, daggerOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Connected to dagger daemon")
|
||||
|
||||
var state pipeline.StateHandler = &pipeline.State{
|
||||
Log: log,
|
||||
Client: client,
|
||||
CLIContext: c,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
registered := r.Initializers()
|
||||
|
||||
log.Debug("Generating artifacts from artifact strings...")
|
||||
// Initialize the artifacts that were specified by the artifacts commands.
|
||||
// These are specified by using artifact strings, or comma-delimited lists of flags.
|
||||
artifacts, err := ArtifactsFromStrings(ctx, log, artifactStrings, registered, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Done generating artifact metadata")
|
||||
|
||||
state = pipeline.StateWithLogger(
|
||||
log.With("service", "state"),
|
||||
state,
|
||||
)
|
||||
|
||||
// The artifact store is responsible for storing built artifacts and issuing them to artifacts that use them as dependencies using the artifact's filename as the key.
|
||||
store := pipeline.NewArtifactStore(log)
|
||||
|
||||
opts := &pipeline.ArtifactContainerOpts{
|
||||
Client: client,
|
||||
Log: log,
|
||||
State: state,
|
||||
Platform: platform,
|
||||
Store: store,
|
||||
}
|
||||
|
||||
// Build each artifact and their dependencies, essentially constructing a dag using Dagger.
|
||||
for i, v := range artifacts {
|
||||
filename, err := v.Handler.Filename(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing artifact string '%s': %w", artifactStrings[i], err)
|
||||
}
|
||||
log := log.With("filename", filename, "artifact", v.ArtifactString)
|
||||
log.Info("Adding artifact to dag...")
|
||||
if err := BuildArtifact(ctx, log, v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Done adding artifact")
|
||||
}
|
||||
|
||||
wg := &errgroup.Group{}
|
||||
sm := semaphore.NewWeighted(parallel)
|
||||
log.Info("Exporting artifacts...")
|
||||
// Export the files from the dag, causing the containers to trigger.
|
||||
for _, v := range artifacts {
|
||||
log := log.With("artifact", v.ArtifactString, "action", "export")
|
||||
wg.Go(ExportArtifactFunc(ctx, client, sm, log, v, store, destination, checksum))
|
||||
}
|
||||
if verify {
|
||||
// Export the files from the dag, causing the containers to trigger.
|
||||
for _, v := range artifacts {
|
||||
log := log.With("artifact", v.ArtifactString, "action", "validate")
|
||||
wg.Go(VerifyArtifactFunc(ctx, client, sm, log, v, store, destination))
|
||||
}
|
||||
}
|
||||
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func BuildArtifact(ctx context.Context, log *slog.Logger, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) error {
|
||||
store := opts.Store
|
||||
exists, err := store.Exists(ctx, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// populate the dependency list
|
||||
dependencies, err := a.Handler.Dependencies(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the files / directories that the dependencies define,
|
||||
// and store the result for re-use.
|
||||
for _, v := range dependencies {
|
||||
f, err := v.Handler.Filename(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := log.With("artifact", v.ArtifactString, "filename", f)
|
||||
if err := BuildArtifact(ctx, log, v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch a.Type {
|
||||
case pipeline.ArtifactTypeDirectory:
|
||||
dir, err := BuildArtifactDirectory(ctx, a, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.StoreDirectory(ctx, a, dir)
|
||||
case pipeline.ArtifactTypeFile:
|
||||
file, err := BuildArtifactFile(ctx, a, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.StoreFile(ctx, a, file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Command(r Registerer) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
if err := Action(r, c); err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func BuildArtifactFile(ctx context.Context, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
builder, err := a.Handler.Builder(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.Handler.BuildFile(ctx, builder, opts)
|
||||
}
|
||||
|
||||
func BuildArtifactDirectory(ctx context.Context, a *pipeline.Artifact, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
builder, err := a.Handler.Builder(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.Handler.BuildDir(ctx, builder, opts)
|
||||
}
|
||||
|
||||
func ExportArtifactFunc(ctx context.Context, d *dagger.Client, sm *semaphore.Weighted, log *slog.Logger, v *pipeline.Artifact, store pipeline.ArtifactStore, dst string, checksum bool) func() error {
|
||||
return func() error {
|
||||
log.Info("Started exporting artifact...")
|
||||
|
||||
log.Info("Acquiring semaphore")
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
log.Info("Error acquiring semaphore", "error", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Acquired semaphore")
|
||||
|
||||
defer sm.Release(1)
|
||||
|
||||
filename, err := v.Handler.Filename(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing artifact string '%s': %w", v.ArtifactString, err)
|
||||
}
|
||||
|
||||
log.Info("Exporting artifact")
|
||||
paths, err := store.Export(ctx, d, v, dst, checksum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error exporting artifact '%s': %w", filename, err)
|
||||
}
|
||||
|
||||
for _, v := range paths {
|
||||
if _, err := fmt.Fprintf(Stdout, "%s\n", v); err != nil {
|
||||
return fmt.Errorf("error writing to stdout: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Done exporting artifact")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func verifyArtifact(ctx context.Context, client *dagger.Client, v *pipeline.Artifact, store pipeline.ArtifactStore) error {
|
||||
switch v.Type {
|
||||
case pipeline.ArtifactTypeDirectory:
|
||||
file, err := store.Directory(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.Handler.VerifyDirectory(ctx, client, file); err != nil {
|
||||
return err
|
||||
}
|
||||
case pipeline.ArtifactTypeFile:
|
||||
file, err := store.File(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.Handler.VerifyFile(ctx, client, file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyArtifactFunc(ctx context.Context, d *dagger.Client, sm *semaphore.Weighted, log *slog.Logger, v *pipeline.Artifact, store pipeline.ArtifactStore, dst string) func() error {
|
||||
return func() error {
|
||||
log.Info("Started verifying artifact...")
|
||||
|
||||
log.Info("Acquiring semaphore")
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
log.Info("Error acquiring semaphore", "error", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Acquired semaphore")
|
||||
defer sm.Release(1)
|
||||
|
||||
if err := verifyArtifact(ctx, d, v, store); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
BackendArguments = []pipeline.Argument{
|
||||
arguments.GrafanaDirectory,
|
||||
arguments.EnterpriseDirectory,
|
||||
arguments.GoVersion,
|
||||
arguments.ViceroyVersion,
|
||||
}
|
||||
|
||||
BackendFlags = flags.JoinFlags(
|
||||
flags.PackageNameFlags,
|
||||
flags.DistroFlags(),
|
||||
)
|
||||
)
|
||||
|
||||
var BackendInitializer = Initializer{
|
||||
InitializerFunc: NewBackendFromString,
|
||||
Arguments: BackendArguments,
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
// Name allows different backend compilations to be different even if all other factors are the same.
|
||||
// For example, Grafana Enterprise, Grafana, and Grafana Pro may be built using the same options,
|
||||
// but are fundamentally different because of the source code of the binary.
|
||||
Name packages.Name
|
||||
Src *dagger.Directory
|
||||
Distribution backend.Distribution
|
||||
BuildOpts *backend.BuildOpts
|
||||
GoVersion string
|
||||
ViceroyVersion string
|
||||
|
||||
GoBuildCache *dagger.CacheVolume
|
||||
GoModCache *dagger.CacheVolume
|
||||
// Version is embedded in the binary at build-time
|
||||
Version string
|
||||
}
|
||||
|
||||
func (b *Backend) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return backend.Builder(
|
||||
opts.Client,
|
||||
opts.Log,
|
||||
b.Distribution,
|
||||
b.BuildOpts,
|
||||
opts.Platform,
|
||||
b.Src,
|
||||
b.GoVersion,
|
||||
b.ViceroyVersion,
|
||||
b.GoBuildCache,
|
||||
b.GoModCache,
|
||||
)
|
||||
}
|
||||
|
||||
func (b *Backend) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Backend) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (b *Backend) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
f, err := b.Filename(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return backend.Build(
|
||||
opts.Client,
|
||||
builder,
|
||||
b.Src,
|
||||
b.Distribution,
|
||||
f,
|
||||
b.BuildOpts,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (b *Backend) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (b *Backend) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (b *Backend) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (b *Backend) Filename(ctx context.Context) (string, error) {
|
||||
return filepath.Join("bin", string(b.Name), string(b.Distribution)), nil
|
||||
}
|
||||
|
||||
func (b *Backend) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Not a file
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
// Nothing to do (yet)
|
||||
return nil
|
||||
}
|
||||
|
||||
type NewBackendOpts struct {
|
||||
Name packages.Name
|
||||
Enterprise bool
|
||||
Src *dagger.Directory
|
||||
Distribution backend.Distribution
|
||||
GoVersion string
|
||||
ViceroyVersion string
|
||||
Version string
|
||||
Experiments []string
|
||||
Tags []string
|
||||
Static bool
|
||||
WireTag string
|
||||
GoBuildCache *dagger.CacheVolume
|
||||
GoModCache *dagger.CacheVolume
|
||||
}
|
||||
|
||||
func NewBackendFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
goVersion, err := state.String(ctx, arguments.GoVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
viceroyVersion, err := state.String(ctx, arguments.ViceroyVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goModCache, err := state.CacheVolume(ctx, arguments.GoModCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goBuildCache, err := state.CacheVolume(ctx, arguments.GoBuildCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 1. Figure out the options that were provided as part of the artifact string.
|
||||
// For example, `linux/amd64:grafana`.
|
||||
options, err := pipeline.ParseFlags(artifact, TargzFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
static, err := options.Bool(flags.Static)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wireTag, err := options.String(flags.WireTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
experiments, err := options.StringSlice(flags.GoExperiments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := options.StringSlice(flags.GoTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
src, err := GrafanaDir(ctx, state, p.Enterprise)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bopts := &backend.BuildOpts{
|
||||
Version: p.Version,
|
||||
Enterprise: p.Enterprise,
|
||||
ExperimentalFlags: experiments,
|
||||
Static: static,
|
||||
WireTag: wireTag,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: BackendFlags,
|
||||
Handler: &Backend{
|
||||
Name: p.Name,
|
||||
Distribution: p.Distribution,
|
||||
BuildOpts: bopts,
|
||||
GoVersion: goVersion,
|
||||
ViceroyVersion: viceroyVersion,
|
||||
Src: src,
|
||||
GoModCache: goModCache,
|
||||
GoBuildCache: goBuildCache,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func NewBackend(ctx context.Context, log *slog.Logger, artifact string, opts *NewBackendOpts) (*pipeline.Artifact, error) {
|
||||
bopts := &backend.BuildOpts{
|
||||
Version: opts.Version,
|
||||
Enterprise: opts.Enterprise,
|
||||
ExperimentalFlags: opts.Experiments,
|
||||
Tags: opts.Tags,
|
||||
Static: opts.Static,
|
||||
WireTag: opts.WireTag,
|
||||
}
|
||||
|
||||
log.Info("Initializing backend artifact with options", "static", opts.Static, "version", opts.Version, "name", opts.Name, "distro", opts.Distribution)
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: BackendFlags,
|
||||
Handler: &Backend{
|
||||
Name: opts.Name,
|
||||
Distribution: opts.Distribution,
|
||||
BuildOpts: bopts,
|
||||
GoVersion: opts.GoVersion,
|
||||
ViceroyVersion: opts.ViceroyVersion,
|
||||
Src: opts.Src,
|
||||
GoModCache: opts.GoModCache,
|
||||
GoBuildCache: opts.GoBuildCache,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/cmd/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func ArtifactFlags(r Registerer) []cli.Flag {
|
||||
artifactsFlag := &cli.StringSliceFlag{
|
||||
Name: "artifacts",
|
||||
Aliases: []string{"a"},
|
||||
}
|
||||
|
||||
buildFlag := &cli.BoolFlag{
|
||||
Name: "build",
|
||||
Value: true,
|
||||
}
|
||||
publishFlag := &cli.BoolFlag{
|
||||
Name: "publish",
|
||||
Usage: "If true, then the artifacts that are built will be published. If `--build=false` and the artifacts are found in the --destination, then those artifacts are not built and are published instead.",
|
||||
Value: true,
|
||||
}
|
||||
|
||||
verifyFlag := &cli.BoolFlag{
|
||||
Name: "verify",
|
||||
Usage: "If true, then the artifacts that are built will be verified with e2e tests or similar after being exported, depending on the artifact",
|
||||
Value: false,
|
||||
}
|
||||
|
||||
flags := flags.Join(
|
||||
[]cli.Flag{
|
||||
artifactsFlag,
|
||||
buildFlag,
|
||||
publishFlag,
|
||||
verifyFlag,
|
||||
flags.Platform,
|
||||
},
|
||||
flags.PublishFlags,
|
||||
flags.ConcurrencyFlags,
|
||||
[]cli.Flag{
|
||||
flags.Verbose,
|
||||
},
|
||||
)
|
||||
|
||||
// All of these artifacts are the registered artifacts. These should mostly stay the same no matter what.
|
||||
initializers := r.Initializers()
|
||||
|
||||
// Add all of the CLI flags that are defined by each artifact's arguments.
|
||||
m := map[string]cli.Flag{}
|
||||
|
||||
// For artifact arguments that specify flags, we'll coalesce them here and add them to the list of flags.
|
||||
for _, n := range initializers {
|
||||
for _, arg := range n.Arguments {
|
||||
for _, f := range arg.Flags {
|
||||
fn := strings.Join(f.Names(), ",")
|
||||
m[fn] = f
|
||||
slog.Debug("global flag added by argument in artifact", "flag", fn, "arg", arg.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range m {
|
||||
flags = append(flags, v)
|
||||
}
|
||||
|
||||
sort.Slice(flags, func(i, j int) bool {
|
||||
return strings.Compare(flags[i].Names()[0], flags[j].Names()[0]) <= 0
|
||||
})
|
||||
|
||||
return flags
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
FrontendFlags = flags.PackageNameFlags
|
||||
FrontendArguments = []pipeline.Argument{
|
||||
arguments.YarnCacheDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
var FrontendInitializer = Initializer{
|
||||
InitializerFunc: NewFrontendFromString,
|
||||
Arguments: FrontendArguments,
|
||||
}
|
||||
|
||||
type Frontend struct {
|
||||
Enterprise bool
|
||||
Version string
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
}
|
||||
|
||||
// The frontend does not have any artifact dependencies.
|
||||
func (f *Frontend) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
|
||||
func (f *Frontend) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
|
||||
}
|
||||
|
||||
func (f *Frontend) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
panic("not implemented") // Frontend doesn't return a file
|
||||
}
|
||||
|
||||
func (f *Frontend) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
return frontend.Build(builder), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *Frontend) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *Frontend) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (f *Frontend) Filename(ctx context.Context) (string, error) {
|
||||
n := "grafana"
|
||||
if f.Enterprise {
|
||||
n = "grafana-enterprise"
|
||||
}
|
||||
|
||||
// Important note: this path is only used in two ways:
|
||||
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
|
||||
// 2. In a map to distinguish when the same artifact is being built more than once
|
||||
return filepath.Join(f.Version, n, "public"), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Should never be called since this isn't a File.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Frontend) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
// Nothing to do to verify these (for now?)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFrontendFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
options, err := pipeline.ParseFlags(artifact, FrontendFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enterprise, err := options.Bool(flags.Enterprise)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
src, err := GrafanaDir(ctx, state, enterprise)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFrontend(ctx, log, artifact, version, enterprise, src, cache)
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, log *slog.Logger, artifact, version string, enterprise bool, src *dagger.Directory, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: FrontendFlags,
|
||||
Handler: &Frontend{
|
||||
Enterprise: enterprise,
|
||||
Version: version,
|
||||
Src: src,
|
||||
YarnCache: cache,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func FrontendBuilder(
|
||||
ctx context.Context,
|
||||
src *dagger.Directory,
|
||||
cache *dagger.CacheVolume,
|
||||
opts *pipeline.ArtifactContainerOpts,
|
||||
) (*dagger.Container, error) {
|
||||
nodeVersion, err := frontend.NodeVersion(opts.Client, src).Stdout(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get node version from source code: %w", err)
|
||||
}
|
||||
|
||||
return frontend.Builder(opts.Client, opts.Platform, src, nodeVersion, cache), nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
func GrafanaDir(ctx context.Context, state pipeline.StateHandler, enterprise bool) (*dagger.Directory, error) {
|
||||
if enterprise {
|
||||
return state.Directory(ctx, arguments.EnterpriseDirectory)
|
||||
}
|
||||
return state.Directory(ctx, arguments.GrafanaDirectory)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
NPMPackagesFlags = flags.PackageNameFlags
|
||||
NPMPackagesArguments = []pipeline.Argument{
|
||||
arguments.YarnCacheDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
var NPMPackagesInitializer = Initializer{
|
||||
InitializerFunc: NewNPMPackagesFromString,
|
||||
Arguments: NPMPackagesArguments,
|
||||
}
|
||||
|
||||
type NPMPackages struct {
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
Version string
|
||||
}
|
||||
|
||||
// The frontend does not have any artifact dependencies.
|
||||
func (f *NPMPackages) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
|
||||
func (f *NPMPackages) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
|
||||
}
|
||||
|
||||
func (f *NPMPackages) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
panic("not implemented") // NPMPackages doesn't return a file
|
||||
}
|
||||
|
||||
func (f *NPMPackages) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
return frontend.NPMPackages(builder, opts.Client, opts.Log, f.Src, strings.TrimPrefix(f.Version, "v"))
|
||||
}
|
||||
|
||||
func (f *NPMPackages) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *NPMPackages) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *NPMPackages) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *NPMPackages) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Not a file
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *NPMPackages) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
// Nothing to verify (yet?)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (f *NPMPackages) Filename(ctx context.Context) (string, error) {
|
||||
// Important note: this path is only used in two ways:
|
||||
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
|
||||
// 2. In a map to distinguish when the same artifact is being built more than once
|
||||
return filepath.Join(f.Version, "npm-packages"), nil
|
||||
}
|
||||
|
||||
func NewNPMPackagesFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
grafanaDir, err := GrafanaDir(ctx, state, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewNPMPackages(ctx, log, artifact, grafanaDir, version, cache)
|
||||
}
|
||||
|
||||
func NewNPMPackages(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: NPMPackagesFlags,
|
||||
Handler: &NPMPackages{
|
||||
Src: src,
|
||||
YarnCache: cache,
|
||||
Version: version,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/fpm"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
DebArguments = TargzArguments
|
||||
DebFlags = flags.JoinFlags(
|
||||
TargzFlags,
|
||||
[]pipeline.Flag{
|
||||
flags.NightlyFlag,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
var DebInitializer = Initializer{
|
||||
InitializerFunc: NewDebFromString,
|
||||
Arguments: TargzArguments,
|
||||
}
|
||||
|
||||
// PacakgeDeb uses a built tar.gz package to create a .deb installer for debian based Linux distributions.
|
||||
type Deb struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distribution backend.Distribution
|
||||
Enterprise bool
|
||||
NameOverride string
|
||||
|
||||
Tarball *pipeline.Artifact
|
||||
|
||||
// Src is the source tree of Grafana. This should only be used in the verify function.
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
}
|
||||
|
||||
func (d *Deb) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Tarball,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Deb) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return fpm.Builder(opts.Client), nil
|
||||
}
|
||||
|
||||
func debVersion(version string) string {
|
||||
// If there is a `+security-` modifier to the version, simply use `-`
|
||||
return strings.ReplaceAll(version, "+security-", "-")
|
||||
}
|
||||
|
||||
func (d *Deb) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
targz, err := opts.Store.File(ctx, d.Tarball)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fpm.Build(builder, fpm.BuildOpts{
|
||||
Name: d.Name,
|
||||
Enterprise: d.Enterprise,
|
||||
Version: debVersion(d.Version),
|
||||
BuildID: d.BuildID,
|
||||
Distribution: d.Distribution,
|
||||
PackageType: fpm.PackageTypeDeb,
|
||||
NameOverride: d.NameOverride,
|
||||
ConfigFiles: [][]string{
|
||||
{"/src/packaging/deb/default/grafana-server", "/pkg/etc/default/grafana-server"},
|
||||
{"/src/packaging/deb/init.d/grafana-server", "/pkg/etc/init.d/grafana-server"},
|
||||
{"/src/packaging/deb/systemd/grafana-server.service", "/pkg/usr/lib/systemd/system/grafana-server.service"},
|
||||
},
|
||||
AfterInstall: "/src/packaging/deb/control/postinst",
|
||||
BeforeRemove: "/src/packaging/deb/control/prerm",
|
||||
Depends: []string{
|
||||
"adduser",
|
||||
"musl",
|
||||
},
|
||||
EnvFolder: "/pkg/etc/default",
|
||||
ExtraArgs: []string{
|
||||
"--deb-no-default-config-files",
|
||||
},
|
||||
}, targz), nil
|
||||
}
|
||||
|
||||
func (d *Deb) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *Deb) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *Deb) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *Deb) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *Deb) Filename(ctx context.Context) (string, error) {
|
||||
name := d.Name
|
||||
if d.NameOverride != "" {
|
||||
name = packages.Name(d.NameOverride)
|
||||
}
|
||||
|
||||
return packages.FileName(name, d.Version, d.BuildID, d.Distribution, "deb")
|
||||
}
|
||||
|
||||
func (d *Deb) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return fpm.VerifyDeb(ctx, client, file, d.Src, d.YarnCache, d.Distribution, d.Enterprise)
|
||||
}
|
||||
|
||||
func (d *Deb) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewDebFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
tarball, err := NewTarballFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options, err := pipeline.ParseFlags(artifact, DebFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
debname := string(p.Name)
|
||||
if nightly, _ := options.Bool(flags.Nightly); nightly {
|
||||
debname += "-nightly"
|
||||
}
|
||||
if rpi, _ := options.Bool(flags.RPI); rpi {
|
||||
debname += "-rpi"
|
||||
}
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &Deb{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distribution: p.Distribution,
|
||||
Enterprise: p.Enterprise,
|
||||
Tarball: tarball,
|
||||
Src: src,
|
||||
YarnCache: yarnCache,
|
||||
NameOverride: debname,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: TargzFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
DockerArguments = arguments.Join(
|
||||
TargzArguments,
|
||||
[]pipeline.Argument{
|
||||
arguments.DockerRegistry,
|
||||
arguments.DockerOrg,
|
||||
arguments.AlpineImage,
|
||||
arguments.UbuntuImage,
|
||||
arguments.TagFormat,
|
||||
arguments.UbuntuTagFormat,
|
||||
arguments.BoringTagFormat,
|
||||
},
|
||||
)
|
||||
DockerFlags = flags.JoinFlags(
|
||||
TargzFlags,
|
||||
flags.DockerFlags,
|
||||
)
|
||||
)
|
||||
|
||||
var DockerInitializer = Initializer{
|
||||
InitializerFunc: NewDockerFromString,
|
||||
Arguments: DockerArguments,
|
||||
}
|
||||
|
||||
// PacakgeDocker uses a built tar.gz package to create a docker image from the Dockerfile in the tar.gz
|
||||
type Docker struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distro backend.Distribution
|
||||
Enterprise bool
|
||||
|
||||
Ubuntu bool
|
||||
Registry string
|
||||
Repositories []string
|
||||
Org string
|
||||
BaseImage string
|
||||
TagFormat string
|
||||
|
||||
Tarball *pipeline.Artifact
|
||||
|
||||
// Src is the Grafana source code for running e2e tests when validating.
|
||||
// The grafana source should not be used for anything else when building a docker image. All files in the Docker image, including the Dockerfile, should be
|
||||
// from the tar.gz file.
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
}
|
||||
|
||||
func (d *Docker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Tarball,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Docker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
targz, err := opts.Store.File(ctx, d.Tarball)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return docker.Builder(opts.Client, opts.Client.Host().UnixSocket("/var/run/docker.sock"), targz), nil
|
||||
}
|
||||
|
||||
func (d *Docker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
// Unlike most other things we push to, docker image tags do not support all characters.
|
||||
// Specifically, the `+` character used in the `buildmetadata` section of semver.
|
||||
version := strings.ReplaceAll(d.Version, "+", "-")
|
||||
|
||||
tags, err := docker.Tags(d.Org, d.Registry, d.Repositories, d.TagFormat, packages.NameOpts{
|
||||
Name: d.Name,
|
||||
Version: version,
|
||||
BuildID: d.BuildID,
|
||||
Distro: d.Distro,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildOpts := &docker.BuildOpts{
|
||||
// Tags are provided as the '-t' argument, and can include the registry domain as well as the repository.
|
||||
// Docker build supports building the same image with multiple tags.
|
||||
// You might want to also include a 'latest' version of the tag.
|
||||
Tags: tags,
|
||||
Platform: backend.Platform(d.Distro),
|
||||
BuildArgs: []string{
|
||||
"GRAFANA_TGZ=grafana.tar.gz",
|
||||
"GO_SRC=tgz-builder",
|
||||
"JS_SRC=tgz-builder",
|
||||
fmt.Sprintf("BASE_IMAGE=%s", d.BaseImage),
|
||||
},
|
||||
}
|
||||
|
||||
b := docker.Build(opts.Client, builder, buildOpts)
|
||||
|
||||
return docker.Save(b, buildOpts), nil
|
||||
}
|
||||
|
||||
func (d *Docker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
func (d *Docker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
|
||||
return opts.Client.Container().From("docker").WithUnixSocket("/var/run/docker.sock", socket), nil
|
||||
}
|
||||
|
||||
func (d *Docker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (d *Docker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *Docker) Filename(ctx context.Context) (string, error) {
|
||||
ext := "docker.tar.gz"
|
||||
if d.Ubuntu {
|
||||
ext = "ubuntu.docker.tar.gz"
|
||||
}
|
||||
|
||||
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
|
||||
}
|
||||
|
||||
func (d *Docker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Currently verifying riscv64 is unsupported (because alpine and ubuntu don't have riscv64 images yet)
|
||||
if _, arch := backend.OSAndArch(d.Distro); arch == "riscv64" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return docker.Verify(ctx, client, file, d.Src, d.YarnCache, d.Distro)
|
||||
}
|
||||
|
||||
func (d *Docker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
options, err := pipeline.ParseFlags(artifact, DockerFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tarball, err := NewTarballFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ubuntu, err := options.Bool(flags.Ubuntu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ubuntu Version to use as the base for the Grafana docker image (if this is a ubuntu artifact)
|
||||
// This shouldn't fail if it's not set by the user, instead it'll default to 22.04 or something.
|
||||
ubuntuImage, err := state.String(ctx, arguments.UbuntuImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Same for Alpine
|
||||
alpineImage, err := state.String(ctx, arguments.AlpineImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registry, err := state.String(ctx, arguments.DockerRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
org, err := state.String(ctx, arguments.DockerOrg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos, err := options.StringSlice(flags.DockerRepositories)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := state.String(ctx, arguments.TagFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ubuntuFormat, err := state.String(ctx, arguments.UbuntuTagFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
boringFormat, err := state.String(ctx, arguments.BoringTagFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := alpineImage
|
||||
if ubuntu {
|
||||
format = ubuntuFormat
|
||||
base = ubuntuImage
|
||||
}
|
||||
|
||||
if p.Name == packages.PackageEnterpriseBoring {
|
||||
format = boringFormat
|
||||
}
|
||||
|
||||
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("initializing Docker artifact", "Org", org, "registry", registry, "repos", repos, "tag", format)
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &Docker{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distro: p.Distribution,
|
||||
Enterprise: p.Enterprise,
|
||||
Tarball: tarball,
|
||||
|
||||
Ubuntu: ubuntu,
|
||||
BaseImage: base,
|
||||
Registry: registry,
|
||||
Org: org,
|
||||
Repositories: repos,
|
||||
TagFormat: format,
|
||||
|
||||
Src: src,
|
||||
YarnCache: yarnCache,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: DockerFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
EntDockerArguments = arguments.Join(
|
||||
DebArguments,
|
||||
[]pipeline.Argument{
|
||||
arguments.HGDirectory,
|
||||
arguments.EntDockerRegistry,
|
||||
arguments.EntDockerOrg,
|
||||
arguments.EntDockerRepo,
|
||||
arguments.HGTagFormat,
|
||||
},
|
||||
)
|
||||
EntDockerFlags = flags.JoinFlags(
|
||||
DebFlags,
|
||||
flags.DockerFlags,
|
||||
)
|
||||
)
|
||||
|
||||
var EntDockerInitializer = Initializer{
|
||||
InitializerFunc: NewEntDockerFromString,
|
||||
Arguments: EntDockerArguments,
|
||||
}
|
||||
|
||||
// EntDocker uses a built deb installer to create a docker image
|
||||
type EntDocker struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distro backend.Distribution
|
||||
EntDir *dagger.Directory
|
||||
|
||||
// EntRegistry is the docker registry when using the `enterprise` name. (e.g. hub.docker.io)
|
||||
EntRegistry string
|
||||
// EntOrg is the docker org when using the `enterprise` name. (e.g. grafana)
|
||||
EntOrg string
|
||||
// EntOrg is the docker repo when using the `enterprise` name. (e.g. grafana-enterprise)
|
||||
EntRepo string
|
||||
// TagFormat is the docker tag format when using the `enterprise` name. (e.g. {{ .version }}-{{ .os }}-{{ .arch }})
|
||||
TagFormat string
|
||||
|
||||
Deb *pipeline.Artifact
|
||||
}
|
||||
|
||||
func (d *EntDocker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Deb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *EntDocker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
deb, err := opts.Store.File(ctx, d.Deb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting deb from state: %w", err)
|
||||
}
|
||||
|
||||
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
|
||||
|
||||
return opts.Client.Container().From("docker").
|
||||
WithUnixSocket("/var/run/docker.sock", socket).
|
||||
WithMountedDirectory("/src", d.EntDir).
|
||||
WithMountedFile("/src/grafana.deb", deb).
|
||||
WithWorkdir("/src"), nil
|
||||
}
|
||||
|
||||
func (d *EntDocker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
tags, err := docker.Tags(d.EntOrg, d.EntRegistry, []string{d.EntRepo}, d.TagFormat, packages.NameOpts{
|
||||
Name: d.Name,
|
||||
Version: d.Version,
|
||||
BuildID: d.BuildID,
|
||||
Distro: d.Distro,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder = docker.Build(opts.Client, builder, &docker.BuildOpts{
|
||||
Dockerfile: "./docker/hosted-grafana-all/Dockerfile",
|
||||
Tags: tags,
|
||||
Target: "hosted-grafana-localenterprise",
|
||||
Platform: dagger.Platform("linux/amd64"),
|
||||
BuildArgs: []string{
|
||||
"RELEASE_TYPE=main",
|
||||
// I think because deb files use a ~ as a version delimiter of some kind, so the hg docker image uses that instead of a -
|
||||
fmt.Sprintf("GRAFANA_VERSION=%s", strings.Replace(d.Version, "-", "~", 1)),
|
||||
},
|
||||
})
|
||||
|
||||
// Save the resulting docker image to the local filesystem
|
||||
return builder.WithExec([]string{"docker", "save", tags[0], "-o", "enterprise.tar"}).File("enterprise.tar"), nil
|
||||
}
|
||||
|
||||
func (d *EntDocker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
func (d *EntDocker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (d *EntDocker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (d *EntDocker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *EntDocker) Filename(ctx context.Context) (string, error) {
|
||||
ext := "docker-enterprise.tar.gz"
|
||||
|
||||
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
|
||||
}
|
||||
|
||||
func (d *EntDocker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *EntDocker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewEntDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
options, err := pipeline.ParseFlags(artifact, DockerFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deb, err := NewDebFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entRegistry, err := state.String(ctx, arguments.EntDockerRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entOrg, err := state.String(ctx, arguments.EntDockerOrg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entRepo, err := state.String(ctx, arguments.EntDockerRepo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagFormat, err := state.String(ctx, arguments.HGTagFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir, err := state.Directory(ctx, arguments.HGDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("initializing Enterprise Docker artifact", "Org", entOrg, "registry", entRegistry, "repo", entRepo, "tag", tagFormat)
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &EntDocker{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distro: p.Distribution,
|
||||
EntDir: dir,
|
||||
Deb: deb,
|
||||
|
||||
EntRegistry: entRegistry,
|
||||
EntOrg: entOrg,
|
||||
EntRepo: entRepo,
|
||||
TagFormat: tagFormat,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: DockerFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/docker"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
ProDockerArguments = arguments.Join(
|
||||
DebArguments,
|
||||
[]pipeline.Argument{
|
||||
arguments.HGDirectory,
|
||||
arguments.ProDockerRegistry,
|
||||
arguments.ProDockerOrg,
|
||||
arguments.ProDockerRepo,
|
||||
arguments.HGTagFormat,
|
||||
},
|
||||
)
|
||||
ProDockerFlags = flags.JoinFlags(
|
||||
DebFlags,
|
||||
flags.DockerFlags,
|
||||
)
|
||||
)
|
||||
|
||||
var ProDockerInitializer = Initializer{
|
||||
InitializerFunc: NewProDockerFromString,
|
||||
Arguments: ProDockerArguments,
|
||||
}
|
||||
|
||||
// ProDocker uses a built deb installer to create a docker image
|
||||
type ProDocker struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distro backend.Distribution
|
||||
ProDir *dagger.Directory
|
||||
|
||||
// ProRegistry is the docker registry when using the `pro` name. (e.g. hub.docker.io)
|
||||
ProRegistry string
|
||||
// ProOrg is the docker org when using the `pro` name. (e.g. grafana)
|
||||
ProOrg string
|
||||
// ProOrg is the docker repo when using the `pro` name. (e.g. grafana-pro)
|
||||
ProRepo string
|
||||
// TagFormat is the docker tag format when using the `pro` name. (e.g. {{ .version }}-{{ .os }}-{{ .arch }})
|
||||
TagFormat string
|
||||
|
||||
// Building the Pro image requires a Debian package instead of a tar.gz
|
||||
Deb *pipeline.Artifact
|
||||
}
|
||||
|
||||
func (d *ProDocker) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Deb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ProDocker) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
deb, err := opts.Store.File(ctx, d.Deb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting deb from state: %w", err)
|
||||
}
|
||||
|
||||
socket := opts.Client.Host().UnixSocket("/var/run/docker.sock")
|
||||
|
||||
return opts.Client.Container().From("docker").
|
||||
WithUnixSocket("/var/run/docker.sock", socket).
|
||||
WithMountedDirectory("/src", d.ProDir).
|
||||
WithMountedFile("/src/grafana.deb", deb).
|
||||
WithWorkdir("/src"), nil
|
||||
}
|
||||
|
||||
func (d *ProDocker) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
tags, err := docker.Tags(d.ProOrg, d.ProRegistry, []string{d.ProRepo}, d.TagFormat, packages.NameOpts{
|
||||
Name: d.Name,
|
||||
Version: d.Version,
|
||||
BuildID: d.BuildID,
|
||||
Distro: d.Distro,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder = docker.Build(opts.Client, builder, &docker.BuildOpts{
|
||||
Dockerfile: "./docker/hosted-grafana-all/Dockerfile",
|
||||
Tags: tags,
|
||||
Target: "hosted-grafana-localpro",
|
||||
Platform: dagger.Platform("linux/amd64"),
|
||||
BuildArgs: []string{
|
||||
"RELEASE_TYPE=main",
|
||||
// I think because deb files use a ~ as a version delimiter of some kind, so the hg docker image uses that instead of a -
|
||||
fmt.Sprintf("GRAFANA_VERSION=%s", strings.Replace(d.Version, "-", "~", 1)),
|
||||
},
|
||||
})
|
||||
|
||||
// Save the resulting docker image to the local filesystem
|
||||
return builder.WithExec([]string{"docker", "save", tags[0], "-o", "pro.tar"}).File("pro.tar"), nil
|
||||
}
|
||||
|
||||
func (d *ProDocker) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
func (d *ProDocker) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (d *ProDocker) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (d *ProDocker) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("This artifact does not produce directories")
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *ProDocker) Filename(ctx context.Context) (string, error) {
|
||||
ext := "docker-pro.tar.gz"
|
||||
|
||||
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distro, ext)
|
||||
}
|
||||
|
||||
func (d *ProDocker) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ProDocker) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewProDockerFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
options, err := pipeline.ParseFlags(artifact, DockerFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deb, err := NewDebFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proRegistry, err := state.String(ctx, arguments.ProDockerRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proOrg, err := state.String(ctx, arguments.ProDockerOrg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proRepo, err := state.String(ctx, arguments.ProDockerRepo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagFormat, err := state.String(ctx, arguments.HGTagFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir, err := state.Directory(ctx, arguments.HGDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("initializing Pro Docker artifact", "Org", proOrg, "registry", proRegistry, "repo", proRepo, "tag", tagFormat)
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &ProDocker{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distro: p.Distribution,
|
||||
ProDir: dir,
|
||||
Deb: deb,
|
||||
|
||||
ProRegistry: proRegistry,
|
||||
ProOrg: proOrg,
|
||||
ProRepo: proRepo,
|
||||
TagFormat: tagFormat,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: DockerFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/msi"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
MSIArguments = TargzArguments
|
||||
MSIFlags = TargzFlags
|
||||
)
|
||||
|
||||
var MSIInitializer = Initializer{
|
||||
InitializerFunc: NewMSIFromString,
|
||||
Arguments: TargzArguments,
|
||||
}
|
||||
|
||||
// PacakgeMSI uses a built tar.gz package to create a .exe installer for exeian based Linux distributions.
|
||||
type MSI struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distribution backend.Distribution
|
||||
Enterprise bool
|
||||
|
||||
Tarball *pipeline.Artifact
|
||||
}
|
||||
|
||||
func (d *MSI) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Tarball,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *MSI) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return msi.Builder(opts.Client)
|
||||
}
|
||||
|
||||
func (d *MSI) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
targz, err := opts.Store.File(ctx, d.Tarball)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msi.Build(opts.Client, builder, targz, d.Version, d.Enterprise)
|
||||
}
|
||||
|
||||
func (d *MSI) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
// Not a directory so this shouldn't be called
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *MSI) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *MSI) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *MSI) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
// Not a directory so this shouldn't be called
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *MSI) Filename(ctx context.Context) (string, error) {
|
||||
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distribution, "msi")
|
||||
}
|
||||
|
||||
func (d *MSI) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MSI) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewMSIFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
targz, err := NewTarballFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options, err := pipeline.ParseFlags(artifact, MSIFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !backend.IsWindows(p.Distribution) {
|
||||
return nil, fmt.Errorf("distribution ('%s') for exe '%s' is not a Windows distribution", string(p.Distribution), artifact)
|
||||
}
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &MSI{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distribution: p.Distribution,
|
||||
Enterprise: p.Enterprise,
|
||||
Tarball: targz,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: ZipFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/fpm"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/gpg"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
RPMArguments = TargzArguments
|
||||
RPMFlags = flags.JoinFlags(
|
||||
TargzFlags,
|
||||
[]pipeline.Flag{
|
||||
flags.SignFlag,
|
||||
flags.NightlyFlag,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
var RPMInitializer = Initializer{
|
||||
InitializerFunc: NewRPMFromString,
|
||||
Arguments: arguments.Join(
|
||||
TargzArguments,
|
||||
[]pipeline.Argument{
|
||||
arguments.GPGPublicKey,
|
||||
arguments.GPGPrivateKey,
|
||||
arguments.GPGPassphrase,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
// PacakgeRPM uses a built tar.gz package to create a .rpm installer for RHEL-ish Linux distributions.
|
||||
type RPM struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distribution backend.Distribution
|
||||
Enterprise bool
|
||||
Sign bool
|
||||
NameOverride string
|
||||
|
||||
GPGPublicKey string
|
||||
GPGPrivateKey string
|
||||
GPGPassphrase string
|
||||
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
|
||||
Tarball *pipeline.Artifact
|
||||
}
|
||||
|
||||
func (d *RPM) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Tarball,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *RPM) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return fpm.Builder(opts.Client), nil
|
||||
}
|
||||
|
||||
func rpmVersion(version string) string {
|
||||
// https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/#_snapshots
|
||||
// If there's a buildmeta revision, then use that as a snapshot version
|
||||
return strings.ReplaceAll(version, "+", "^")
|
||||
}
|
||||
|
||||
func (d *RPM) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
targz, err := opts.Store.File(ctx, d.Tarball)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rpm := fpm.Build(builder, fpm.BuildOpts{
|
||||
Name: d.Name,
|
||||
Enterprise: d.Enterprise,
|
||||
Version: rpmVersion(d.Version),
|
||||
BuildID: d.BuildID,
|
||||
Distribution: d.Distribution,
|
||||
PackageType: fpm.PackageTypeRPM,
|
||||
NameOverride: d.NameOverride,
|
||||
ConfigFiles: [][]string{
|
||||
{"/src/packaging/rpm/sysconfig/grafana-server", "/pkg/etc/sysconfig/grafana-server"},
|
||||
{"/src/packaging/rpm/systemd/grafana-server.service", "/pkg/usr/lib/systemd/system/grafana-server.service"},
|
||||
},
|
||||
AfterInstall: "/src/packaging/rpm/control/postinst",
|
||||
Depends: []string{
|
||||
"/sbin/service",
|
||||
},
|
||||
ExtraArgs: []string{
|
||||
"--rpm-posttrans=/src/packaging/rpm/control/posttrans",
|
||||
"--rpm-digest=sha256",
|
||||
},
|
||||
EnvFolder: "/pkg/etc/sysconfig",
|
||||
}, targz)
|
||||
|
||||
if !d.Sign {
|
||||
return rpm, nil
|
||||
}
|
||||
return gpg.Sign(opts.Client, rpm, gpg.GPGOpts{
|
||||
GPGPublicKey: d.GPGPublicKey,
|
||||
GPGPrivateKey: d.GPGPrivateKey,
|
||||
GPGPassphrase: d.GPGPassphrase,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (d *RPM) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *RPM) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *RPM) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *RPM) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *RPM) Filename(ctx context.Context) (string, error) {
|
||||
name := d.Name
|
||||
if d.NameOverride != "" {
|
||||
name = packages.Name(d.NameOverride)
|
||||
}
|
||||
|
||||
return packages.FileName(name, d.Version, d.BuildID, d.Distribution, "rpm")
|
||||
}
|
||||
|
||||
func (d *RPM) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
// return fpm.VerifyRpm(ctx, client, file, d.Src, d.YarnCache, d.Distribution, d.Enterprise, d.Sign, d.GPGPublicKey, d.GPGPrivateKey, d.GPGPassphrase)
|
||||
}
|
||||
|
||||
func (d *RPM) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func NewRPMFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
tarball, err := NewTarballFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options, err := pipeline.ParseFlags(artifact, RPMFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sign, err := options.Bool(flags.Sign)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src, err := state.Directory(ctx, arguments.GrafanaDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var gpgPublicKey, gpgPrivateKey, gpgPassphrase string
|
||||
|
||||
if sign {
|
||||
pubb64, err := state.String(ctx, arguments.GPGPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, err := base64.StdEncoding.DecodeString(pubb64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gpg-private-key-base64 cannot be decoded %w", err)
|
||||
}
|
||||
|
||||
privb64, err := state.String(ctx, arguments.GPGPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priv, err := base64.StdEncoding.DecodeString(privb64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gpg-private-key-base64 cannot be decoded %w", err)
|
||||
}
|
||||
|
||||
pass, err := state.String(ctx, arguments.GPGPassphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gpgPublicKey = string(pub)
|
||||
gpgPrivateKey = string(priv)
|
||||
gpgPassphrase = pass
|
||||
}
|
||||
|
||||
rpmname := string(p.Name)
|
||||
if nightly, _ := options.Bool(flags.Nightly); nightly {
|
||||
rpmname += "-nightly"
|
||||
}
|
||||
if rpi, _ := options.Bool(flags.RPI); rpi {
|
||||
rpmname += "-rpi"
|
||||
}
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &RPM{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distribution: p.Distribution,
|
||||
Enterprise: p.Enterprise,
|
||||
Tarball: tarball,
|
||||
Sign: sign,
|
||||
Src: src,
|
||||
YarnCache: yarnCache,
|
||||
GPGPublicKey: gpgPublicKey,
|
||||
GPGPrivateKey: gpgPrivateKey,
|
||||
GPGPassphrase: gpgPassphrase,
|
||||
NameOverride: rpmname,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: TargzFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/e2e"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/targz"
|
||||
)
|
||||
|
||||
var (
|
||||
TargzArguments = []pipeline.Argument{
|
||||
// Tarballs need the Build ID and version for naming the package properly.
|
||||
arguments.BuildID,
|
||||
arguments.Version,
|
||||
|
||||
// The grafanadirectory has contents like the LICENSE.txt and such that need to be included in the package
|
||||
arguments.GrafanaDirectory,
|
||||
|
||||
// The go version used to build the backend
|
||||
arguments.GoVersion,
|
||||
arguments.ViceroyVersion,
|
||||
arguments.YarnCacheDirectory,
|
||||
}
|
||||
TargzFlags = flags.JoinFlags(
|
||||
flags.StdPackageFlags(),
|
||||
)
|
||||
)
|
||||
|
||||
var TargzInitializer = Initializer{
|
||||
InitializerFunc: NewTarballFromString,
|
||||
Arguments: TargzArguments,
|
||||
}
|
||||
|
||||
type Tarball struct {
|
||||
Distribution backend.Distribution
|
||||
Name packages.Name
|
||||
BuildID string
|
||||
Version string
|
||||
GoVersion string
|
||||
Enterprise bool
|
||||
|
||||
Grafana *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
|
||||
// Dependent artifacts
|
||||
Backend *pipeline.Artifact
|
||||
Frontend *pipeline.Artifact
|
||||
NPMPackages *pipeline.Artifact
|
||||
BundledPlugins *pipeline.Artifact
|
||||
Storybook *pipeline.Artifact
|
||||
}
|
||||
|
||||
func NewTarballFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
goVersion, err := state.String(ctx, arguments.GoVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
viceroyVersion, err := state.String(ctx, arguments.ViceroyVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 1. Figure out the options that were provided as part of the artifact string.
|
||||
// For example, `linux/amd64:grafana`.
|
||||
options, err := pipeline.ParseFlags(artifact, TargzFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
static, err := options.Bool(flags.Static)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wireTag, err := options.String(flags.WireTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := options.StringSlice(flags.GoTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
experiments, err := options.StringSlice(flags.GoExperiments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yarnCache, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goModCache, err := state.CacheVolume(ctx, arguments.GoModCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goBuildCache, err := state.CacheVolume(ctx, arguments.GoBuildCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("Initializing tar.gz artifact with options", "name", p.Name, "build ID", p.BuildID, "version", p.Version, "distro", p.Distribution, "static", static, "enterprise", p.Enterprise)
|
||||
|
||||
src, err := GrafanaDir(ctx, state, p.Enterprise)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTarball(ctx, log, artifact, p.Distribution, p.Enterprise, p.Name, p.Version, p.BuildID, src, yarnCache, goModCache, goBuildCache, static, wireTag, tags, goVersion, viceroyVersion, experiments)
|
||||
}
|
||||
|
||||
// NewTarball returns a properly initialized Tarball artifact.
|
||||
// There are a lot of options that can affect how a tarball is built; most of which define different ways for the backend to be built.
|
||||
func NewTarball(
|
||||
ctx context.Context,
|
||||
log *slog.Logger,
|
||||
artifact string,
|
||||
distro backend.Distribution,
|
||||
enterprise bool,
|
||||
name packages.Name,
|
||||
version string,
|
||||
buildID string,
|
||||
src *dagger.Directory,
|
||||
cache *dagger.CacheVolume,
|
||||
goModCache *dagger.CacheVolume,
|
||||
goBuildCache *dagger.CacheVolume,
|
||||
static bool,
|
||||
wireTag string,
|
||||
tags []string,
|
||||
goVersion string,
|
||||
viceroyVersion string,
|
||||
experiments []string,
|
||||
) (*pipeline.Artifact, error) {
|
||||
backendArtifact, err := NewBackend(ctx, log, artifact, &NewBackendOpts{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Distribution: distro,
|
||||
Src: src,
|
||||
Static: static,
|
||||
WireTag: wireTag,
|
||||
Tags: tags,
|
||||
GoVersion: goVersion,
|
||||
ViceroyVersion: viceroyVersion,
|
||||
Experiments: experiments,
|
||||
Enterprise: enterprise,
|
||||
GoBuildCache: goBuildCache,
|
||||
GoModCache: goModCache,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frontendArtifact, err := NewFrontend(ctx, log, version, artifact, enterprise, src, cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundledPluginsArtifact, err := NewBundledPlugins(ctx, log, artifact, src, version, cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
npmArtifact, err := NewNPMPackages(ctx, log, artifact, src, version, cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storybookArtifact, err := NewStorybook(ctx, log, artifact, src, version, cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tarball := &Tarball{
|
||||
Name: name,
|
||||
Distribution: distro,
|
||||
Version: version,
|
||||
GoVersion: goVersion,
|
||||
BuildID: buildID,
|
||||
Grafana: src,
|
||||
Enterprise: enterprise,
|
||||
YarnCache: cache,
|
||||
|
||||
Backend: backendArtifact,
|
||||
Frontend: frontendArtifact,
|
||||
NPMPackages: npmArtifact,
|
||||
BundledPlugins: bundledPluginsArtifact,
|
||||
Storybook: storybookArtifact,
|
||||
}
|
||||
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: tarball,
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: TargzFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Tarball) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
version := t.Version
|
||||
|
||||
container := opts.Client.Container().
|
||||
From("alpine:3.18.4").
|
||||
WithExec([]string{"apk", "add", "--update", "tar"}).
|
||||
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf("echo %s > VERSION", version)})
|
||||
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (t *Tarball) BuildFile(ctx context.Context, b *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
var (
|
||||
state = opts.State
|
||||
log = opts.Log
|
||||
)
|
||||
|
||||
log.Debug("Getting grafana dir from state...")
|
||||
// The Grafana directory is used for other packaged data like Dockerfile, license.txt, etc.
|
||||
grafanaDir := t.Grafana
|
||||
|
||||
backendDir, err := opts.Store.Directory(ctx, t.Backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frontendDir, err := opts.Store.Directory(ctx, t.Frontend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
npmDir, err := opts.Store.Directory(ctx, t.NPMPackages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storybookDir, err := opts.Store.Directory(ctx, t.Storybook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pluginsDir, err := opts.Store.Directory(ctx, t.BundledPlugins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := []targz.MappedFile{
|
||||
targz.NewMappedFile("VERSION", b.File("VERSION")),
|
||||
targz.NewMappedFile("LICENSE", grafanaDir.File("LICENSE")),
|
||||
targz.NewMappedFile("NOTICE.md", grafanaDir.File("NOTICE.md")),
|
||||
targz.NewMappedFile("README.md", grafanaDir.File("README.md")),
|
||||
targz.NewMappedFile("Dockerfile", grafanaDir.File("Dockerfile")),
|
||||
targz.NewMappedFile("tools/zoneinfo.zip", opts.Client.Container().From(fmt.Sprintf("golang:%s", t.GoVersion)).File("/usr/local/go/lib/time/zoneinfo.zip")),
|
||||
}
|
||||
|
||||
directories := []targz.MappedDirectory{
|
||||
targz.NewMappedDir("conf", grafanaDir.Directory("conf")),
|
||||
targz.NewMappedDir("docs/sources", grafanaDir.Directory("docs/sources")),
|
||||
targz.NewMappedDir("packaging/deb", grafanaDir.Directory("packaging/deb")),
|
||||
targz.NewMappedDir("packaging/rpm", grafanaDir.Directory("packaging/rpm")),
|
||||
targz.NewMappedDir("packaging/docker", grafanaDir.Directory("packaging/docker")),
|
||||
targz.NewMappedDir("packaging/wrappers", grafanaDir.Directory("packaging/wrappers")),
|
||||
targz.NewMappedDir("bin", backendDir),
|
||||
targz.NewMappedDir("public", frontendDir),
|
||||
targz.NewMappedDir("npm-artifacts", npmDir),
|
||||
targz.NewMappedDir("storybook", storybookDir),
|
||||
targz.NewMappedDir("plugins-bundled", pluginsDir),
|
||||
}
|
||||
|
||||
root := fmt.Sprintf("grafana-%s", version)
|
||||
|
||||
return targz.Build(
|
||||
b,
|
||||
&targz.Opts{
|
||||
Root: root,
|
||||
Files: files,
|
||||
Directories: directories,
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
func (t *Tarball) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (t *Tarball) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (t *Tarball) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tarball) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (t *Tarball) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Currently verifying riscv64 is unsupported (because alpine and ubuntu don't have riscv64 images yet)
|
||||
// windows/darwin verification may never be supported.
|
||||
os, arch := backend.OSAndArch(t.Distribution)
|
||||
if os != "linux" || arch == "riscv64" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return verifyTarball(ctx, client, file, t.Grafana, t.YarnCache, t.Distribution, t.Enterprise)
|
||||
}
|
||||
|
||||
func (t *Tarball) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (t *Tarball) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
t.Backend,
|
||||
t.Frontend,
|
||||
t.NPMPackages,
|
||||
t.BundledPlugins,
|
||||
t.Storybook,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Tarball) Filename(ctx context.Context) (string, error) {
|
||||
return packages.FileName(t.Name, t.Version, t.BuildID, t.Distribution, "tar.gz")
|
||||
}
|
||||
|
||||
func verifyTarball(
|
||||
ctx context.Context,
|
||||
d *dagger.Client,
|
||||
pkg *dagger.File,
|
||||
src *dagger.Directory,
|
||||
yarnCache *dagger.CacheVolume,
|
||||
distro backend.Distribution,
|
||||
enterprise bool,
|
||||
) error {
|
||||
nodeVersion, err := frontend.NodeVersion(d, src).Stdout(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get node version from source code: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
platform = backend.Platform(distro)
|
||||
archive = containers.ExtractedArchive(d, pkg)
|
||||
)
|
||||
|
||||
// This grafana service runs in the background for the e2e tests
|
||||
service := d.Container(dagger.ContainerOpts{
|
||||
Platform: platform,
|
||||
}).From("ubuntu:22.04").
|
||||
WithExec([]string{"apt-get", "update", "-yq"}).
|
||||
WithExec([]string{"apt-get", "install", "-yq", "ca-certificates", "libfontconfig1"}).
|
||||
WithDirectory("/src", archive).
|
||||
WithWorkdir("/src")
|
||||
|
||||
if err := e2e.ValidateLicense(ctx, service, "/src/LICENSE", enterprise); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service = service.
|
||||
WithExec([]string{"./bin/grafana", "server"}).
|
||||
WithExposedPort(3000)
|
||||
|
||||
if _, err := containers.ExitError(ctx, e2e.ValidatePackage(d, service.AsService(), src, yarnCache, nodeVersion)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/zip"
|
||||
)
|
||||
|
||||
var (
|
||||
ZipArguments = TargzArguments
|
||||
ZipFlags = TargzFlags
|
||||
)
|
||||
|
||||
var ZipInitializer = Initializer{
|
||||
InitializerFunc: NewZipFromString,
|
||||
Arguments: TargzArguments,
|
||||
}
|
||||
|
||||
// PacakgeZip uses a built tar.gz package to create a .zip package for zipian based Linux distributions.
|
||||
type Zip struct {
|
||||
Name packages.Name
|
||||
Version string
|
||||
BuildID string
|
||||
Distribution backend.Distribution
|
||||
Enterprise bool
|
||||
|
||||
Tarball *pipeline.Artifact
|
||||
}
|
||||
|
||||
func (d *Zip) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{
|
||||
d.Tarball,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Zip) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return zip.Builder(opts.Client), nil
|
||||
}
|
||||
|
||||
func (d *Zip) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
targz, err := opts.Store.File(ctx, d.Tarball)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zip.Build(builder, targz), nil
|
||||
}
|
||||
|
||||
func (d *Zip) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *Zip) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *Zip) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Zip) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (d *Zip) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Zip) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (d *Zip) Filename(ctx context.Context) (string, error) {
|
||||
return packages.FileName(d.Name, d.Version, d.BuildID, d.Distribution, "zip")
|
||||
}
|
||||
|
||||
func NewZipFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
tarball, err := NewTarballFromString(ctx, log, artifact, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options, err := pipeline.ParseFlags(artifact, ZipFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := GetPackageDetails(ctx, options, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Handler: &Zip{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
BuildID: p.BuildID,
|
||||
Distribution: p.Distribution,
|
||||
Enterprise: p.Enterprise,
|
||||
Tarball: tarball,
|
||||
},
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: TargzFlags,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/backend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
type PackageDetails struct {
|
||||
Name packages.Name
|
||||
Enterprise bool
|
||||
Version string
|
||||
BuildID string
|
||||
Distribution backend.Distribution
|
||||
}
|
||||
|
||||
func GetPackageDetails(ctx context.Context, options *pipeline.OptionsHandler, state pipeline.StateHandler) (PackageDetails, error) {
|
||||
distro, err := options.String(flags.Distribution)
|
||||
if err != nil {
|
||||
return PackageDetails{}, err
|
||||
}
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return PackageDetails{}, err
|
||||
}
|
||||
buildID, err := state.String(ctx, arguments.BuildID)
|
||||
if err != nil {
|
||||
return PackageDetails{}, err
|
||||
}
|
||||
|
||||
name, err := options.String(flags.PackageName)
|
||||
if err != nil {
|
||||
return PackageDetails{}, err
|
||||
}
|
||||
|
||||
enterprise, err := options.Bool(flags.Enterprise)
|
||||
if err != nil {
|
||||
return PackageDetails{}, err
|
||||
}
|
||||
|
||||
return PackageDetails{
|
||||
Name: packages.Name(name),
|
||||
Version: version,
|
||||
BuildID: buildID,
|
||||
Distribution: backend.Distribution(distro),
|
||||
Enterprise: enterprise,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorArtifactCollision = errors.New("artifact argument specifies two different artifacts")
|
||||
ErrorDuplicateArgument = errors.New("artifact argument specifies duplicate or incompatible arguments")
|
||||
ErrorNoArtifact = errors.New("could not find compatible artifact for argument string")
|
||||
|
||||
ErrorFlagNotFound = errors.New("no option available for the given flag")
|
||||
)
|
||||
|
||||
func findInitializer(val string, initializers map[string]Initializer) (Initializer, error) {
|
||||
c := strings.Split(val, ":")
|
||||
var initializer *Initializer
|
||||
|
||||
// Find the artifact that is requested by `val`.
|
||||
// The artifact can be defined anywhere in the artifact string. Example: `linux/amd64:grafana:targz` or `linux/amd64:grafana:targz` are the same, where targz is the artifact.
|
||||
for _, v := range c {
|
||||
n, ok := initializers[v]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if initializer != nil {
|
||||
return Initializer{}, fmt.Errorf("%s: %w", val, ErrorArtifactCollision)
|
||||
}
|
||||
|
||||
initializer = &n
|
||||
}
|
||||
|
||||
if initializer == nil {
|
||||
return Initializer{}, fmt.Errorf("%s: %w", val, ErrorNoArtifact)
|
||||
}
|
||||
|
||||
return *initializer, nil
|
||||
}
|
||||
|
||||
// The ArtifactsFromStrings function should provide all of the necessary arguments to produce each artifact
|
||||
// dleimited by colons. It's a repeated flag, so all permutations are stored in 1 instance of the ArtifactsFlag struct.
|
||||
// Examples:
|
||||
// * targz:linux/amd64 -- Will produce a "Grafana" tar.gz for "linux/amd64".
|
||||
// * targz:enterprise:linux/amd64 -- Will produce a "Grafana" tar.gz for "linux/amd64".
|
||||
func ArtifactsFromStrings(ctx context.Context, log *slog.Logger, a []string, registered map[string]Initializer, state pipeline.StateHandler) ([]*pipeline.Artifact, error) {
|
||||
artifacts := make([]*pipeline.Artifact, len(a))
|
||||
for i, v := range a {
|
||||
n, err := Parse(ctx, log, v, registered, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
artifacts[i] = n
|
||||
}
|
||||
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
// Parse parses the artifact string `artifact` and finds the matching initializer.
|
||||
func Parse(ctx context.Context, log *slog.Logger, artifact string, initializers map[string]Initializer, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
artifact = strings.TrimSpace(artifact)
|
||||
initializer, err := findInitializer(artifact, initializers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
initializerFunc := initializer.InitializerFunc
|
||||
// TODO soon, the initializer might need more info about flags
|
||||
return initializerFunc(ctx, log, artifact, state)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package artifacts_test
|
||||
|
||||
// var TestArtifact struct {
|
||||
// }
|
||||
//
|
||||
// func TestParse(t *testing.T) {
|
||||
// v := "artifact:flag1:flag2"
|
||||
//
|
||||
// exampleArtifact := &pipeline.Artifact{
|
||||
// Name: "example",
|
||||
// }
|
||||
//
|
||||
// argument1 := &pipeline.Argument{
|
||||
// Name: "argument1",
|
||||
// }
|
||||
//
|
||||
// argument2 := &pipeline.Argument{
|
||||
// Name: "argument2",
|
||||
// }
|
||||
//
|
||||
// res, err := artifacts.Parse(v, map[string]artifacts.ArgumentOption{
|
||||
// "artifact": {Artifact: exampleArtifact},
|
||||
// "argument1": {Arguments: []*pipeline.Argument{argument1}},
|
||||
// "argument2": {Arguments: []*pipeline.Argument{argument2}},
|
||||
// })
|
||||
//
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// if res.Artifact.Name != exampleArtifact.Name {
|
||||
// t.Fatal("Parse should return the example artifact")
|
||||
// }
|
||||
//
|
||||
// if len(res.Arguments) != 2 {
|
||||
// t.Fatal("Parse should return 2 Arguments")
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,91 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/packages"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
BundledPluginsFlags = flags.PackageNameFlags
|
||||
BundledPluginsArguments = []pipeline.Argument{
|
||||
arguments.YarnCacheDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
type BundledPlugins struct {
|
||||
Name packages.Name
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
Version string
|
||||
}
|
||||
|
||||
// The frontend does not have any artifact dependencies.
|
||||
func (f *BundledPlugins) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
|
||||
func (f *BundledPlugins) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
panic("not implemented") // BundledPlugins doesn't return a file
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
return frontend.BuildPlugins(builder), nil
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *BundledPlugins) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (f *BundledPlugins) Filename(ctx context.Context) (string, error) {
|
||||
// Important note: this path is only used in two ways:
|
||||
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
|
||||
// 2. In a map to distinguish when the same artifact is being built more than once
|
||||
return path.Join("bin", "bundled-plugins"), nil
|
||||
}
|
||||
|
||||
func NewBundledPlugins(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cacheVolume *dagger.CacheVolume) (*pipeline.Artifact, error) {
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: BundledPluginsFlags,
|
||||
Handler: &BundledPlugins{
|
||||
Src: src,
|
||||
YarnCache: cacheVolume,
|
||||
Version: version,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package artifacts
|
||||
|
||||
import "github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
|
||||
type Initializer struct {
|
||||
InitializerFunc pipeline.ArtifactInitializer
|
||||
Arguments []pipeline.Argument
|
||||
}
|
||||
|
||||
type Registerer interface {
|
||||
Register(string, Initializer) error
|
||||
Initializers() map[string]Initializer
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package artifacts
|
||||
@@ -0,0 +1,113 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/flags"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
StorybookFlags = flags.PackageNameFlags
|
||||
StorybookArguments = []pipeline.Argument{
|
||||
arguments.YarnCacheDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
var StorybookInitializer = Initializer{
|
||||
InitializerFunc: NewStorybookFromString,
|
||||
Arguments: StorybookArguments,
|
||||
}
|
||||
|
||||
type Storybook struct {
|
||||
Src *dagger.Directory
|
||||
YarnCache *dagger.CacheVolume
|
||||
Version string
|
||||
}
|
||||
|
||||
// The frontend does not have any artifact dependencies.
|
||||
func (f *Storybook) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Builder will return a node.js alpine container that matches the .nvmrc in the Grafana source repository
|
||||
func (f *Storybook) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return FrontendBuilder(ctx, f.Src, f.YarnCache, opts)
|
||||
}
|
||||
|
||||
func (f *Storybook) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
// Not a file
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *Storybook) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
return frontend.Storybook(builder, f.Src, f.Version), nil
|
||||
}
|
||||
|
||||
func (f *Storybook) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *Storybook) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *Storybook) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (f *Storybook) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
// Not a file
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Storybook) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (f *Storybook) Filename(ctx context.Context) (string, error) {
|
||||
// Important note: this path is only used in two ways:
|
||||
// 1. When requesting an artifact be built and exported, this is the path where it will be exported to
|
||||
// 2. In a map to distinguish when the same artifact is being built more than once
|
||||
return filepath.Join(f.Version, "storybook"), nil
|
||||
}
|
||||
|
||||
func NewStorybookFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
grafanaDir, err := GrafanaDir(ctx, state, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheDir, err := state.CacheVolume(ctx, arguments.YarnCacheDirectory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStorybook(ctx, log, artifact, grafanaDir, version, cacheDir)
|
||||
}
|
||||
|
||||
func NewStorybook(ctx context.Context, log *slog.Logger, artifact string, src *dagger.Directory, version string, cache *dagger.CacheVolume) (*pipeline.Artifact, error) {
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeDirectory,
|
||||
Flags: StorybookFlags,
|
||||
Handler: &Storybook{
|
||||
Src: src,
|
||||
YarnCache: cache,
|
||||
Version: version,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SyncWriter wraps a writer and makes its writes synchronous, preventing multiple threads writing to the same writer
|
||||
// from creating wacky looking output.
|
||||
type SyncWriter struct {
|
||||
Writer io.Writer
|
||||
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSyncWriter(w io.Writer) *SyncWriter {
|
||||
return &SyncWriter{
|
||||
Writer: w,
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SyncWriter) Write(b []byte) (int, error) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
var Stdout = NewSyncWriter(os.Stdout)
|
||||
@@ -0,0 +1,94 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/arguments"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
|
||||
)
|
||||
|
||||
var (
|
||||
VersionArguments = []pipeline.Argument{
|
||||
arguments.GrafanaDirectory,
|
||||
arguments.Version,
|
||||
}
|
||||
|
||||
VersionFlags = TargzFlags
|
||||
)
|
||||
|
||||
var VersionInitializer = Initializer{
|
||||
InitializerFunc: NewVersionFromString,
|
||||
Arguments: VersionArguments,
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
// Version is embedded in the binary at build-time
|
||||
Version string
|
||||
}
|
||||
|
||||
func (b *Version) Builder(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return opts.Client.Container().WithNewFile("/VERSION", b.Version), nil
|
||||
}
|
||||
|
||||
func (b *Version) Dependencies(ctx context.Context) ([]*pipeline.Artifact, error) {
|
||||
return []*pipeline.Artifact{}, nil
|
||||
}
|
||||
|
||||
func (b *Version) BuildFile(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.File, error) {
|
||||
return builder.File("/VERSION"), nil
|
||||
}
|
||||
|
||||
func (b *Version) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Version) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Version) PublishFile(ctx context.Context, opts *pipeline.ArtifactPublishFileOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Version) PublishDir(ctx context.Context, opts *pipeline.ArtifactPublishDirOpts) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
// Filename should return a deterministic file or folder name that this build will produce.
|
||||
// This filename is used as a map key for caching, so implementers need to ensure that arguments or flags that affect the output
|
||||
// also affect the filename to ensure that there are no collisions.
|
||||
// For example, the backend for `linux/amd64` and `linux/arm64` should not both produce a `bin` folder, they should produce a
|
||||
// `bin/linux-amd64` folder and a `bin/linux-arm64` folder. Callers can mount this as `bin` or whatever if they want.
|
||||
func (b *Version) Filename(ctx context.Context) (string, error) {
|
||||
return "VERSION", nil
|
||||
}
|
||||
|
||||
func (b *Version) VerifyFile(ctx context.Context, client *dagger.Client, file *dagger.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Version) VerifyDirectory(ctx context.Context, client *dagger.Client, dir *dagger.Directory) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewVersionFromString(ctx context.Context, log *slog.Logger, artifact string, state pipeline.StateHandler) (*pipeline.Artifact, error) {
|
||||
version, err := state.String(ctx, arguments.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewVersion(ctx, log, artifact, version)
|
||||
}
|
||||
|
||||
func NewVersion(ctx context.Context, log *slog.Logger, artifact, version string) (*pipeline.Artifact, error) {
|
||||
return pipeline.ArtifactWithLogging(ctx, log, &pipeline.Artifact{
|
||||
ArtifactString: artifact,
|
||||
Type: pipeline.ArtifactTypeFile,
|
||||
Flags: VersionFlags,
|
||||
Handler: &Version{
|
||||
Version: version,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user