The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/build/daggerbuild/arguments/grafana.go

299 lines
9.9 KiB

package arguments
import (
"context"
"fmt"
"log/slog"
"path"
"path/filepath"
"dagger.io/dagger"
"github.com/grafana/grafana/pkg/build/daggerbuild/cliutil"
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
"github.com/grafana/grafana/pkg/build/daggerbuild/daggerutil"
"github.com/grafana/grafana/pkg/build/daggerbuild/frontend"
"github.com/grafana/grafana/pkg/build/daggerbuild/git"
"github.com/grafana/grafana/pkg/build/daggerbuild/pipeline"
"github.com/urfave/cli/v2"
)
const BusyboxImage = "busybox:1.36"
func InitializeEnterprise(d *dagger.Client, grafana *dagger.Directory, enterprise *dagger.Directory) *dagger.Directory {
hash := d.Container().From("alpine/git").
WithDirectory("/src/grafana-enterprise", enterprise).
WithWorkdir("/src/grafana-enterprise").
WithEntrypoint([]string{}).
WithExec([]string{"/bin/sh", "-c", "git rev-parse HEAD > .buildinfo.enterprise-commit"}).
File("/src/grafana-enterprise/.buildinfo.enterprise-commit")
return d.Container().From(BusyboxImage).
WithDirectory("/src/grafana", grafana).
WithDirectory("/src/grafana-enterprise", enterprise).
WithWorkdir("/src/grafana-enterprise").
WithFile("/src/grafana/.buildinfo.enterprise-commit", hash).
WithExec([]string{"/bin/sh", "build.sh"}).
WithExec([]string{"cp", "LICENSE", "../grafana"}).
Directory("/src/grafana")
}
// GrafnaaOpts are populated by the 'GrafanaFlags' flags.
// These options define how to mount or clone the grafana/enterprise source code.
type GrafanaDirectoryOpts struct {
// GrafanaDir is the path to the Grafana source tree.
// If GrafanaDir is empty, then we're most likely cloning Grafana and using that as a directory.
GrafanaDir string
EnterpriseDir string
// GrafanaRepo will clone Grafana from a different repository when cloning Grafana.
GrafanaRepo string
EnterpriseRepo string
// GrafanaRef will checkout a specific tag, branch, or commit when cloning Grafana.
GrafanaRef string
EnterpriseRef string
// GitHubToken is used when cloning Grafana/Grafana Enterprise.
GitHubToken string
PatchesRepo string
PatchesPath string
PatchesRef string
}
func githubToken(ctx context.Context, token string) (string, error) {
// Since GrafanaDir was not provided, we must clone it.
ght := token
// If GitHubToken was not set from flag
if ght != "" {
return ght, nil
}
token, err := git.LookupGitHubToken(ctx)
if err != nil {
return "", err
}
if token == "" {
return "", fmt.Errorf("unable to acquire github token")
}
return token, nil
}
func GrafanaDirectoryOptsFromFlags(c cliutil.CLIContext) *GrafanaDirectoryOpts {
return &GrafanaDirectoryOpts{
GrafanaRepo: c.String("grafana-repo"),
EnterpriseRepo: c.String("enterprise-repo"),
GrafanaDir: c.String("grafana-dir"),
EnterpriseDir: c.String("enterprise-dir"),
GrafanaRef: c.String("grafana-ref"),
EnterpriseRef: c.String("enterprise-ref"),
GitHubToken: c.String("github-token"),
PatchesRepo: c.String("patches-repo"),
PatchesPath: c.String("patches-path"),
PatchesRef: c.String("patches-ref"),
}
}
func cloneOrMount(ctx context.Context, client *dagger.Client, localPath, repo, ref string, ght string) (*dagger.Directory, error) {
if localPath != "" {
absolute, err := filepath.Abs(localPath)
if err != nil {
return nil, fmt.Errorf("error getting absolute path for local dir: %w", err)
}
localPath = absolute
slog.Info("Using local directory for repository", "path", localPath, "repo", repo)
return daggerutil.HostDir(client, localPath)
}
ght, err := githubToken(ctx, ght)
if err != nil {
return nil, fmt.Errorf("error acquiring GitHub token: %w", err)
}
return git.CloneWithGitHubToken(client, ght, repo, ref)
}
func applyPatches(ctx context.Context, client *dagger.Client, src *dagger.Directory, repo, patchesPath, ref, ght string) (*dagger.Directory, error) {
ght, err := githubToken(ctx, ght)
if err != nil {
return nil, fmt.Errorf("error acquiring GitHub token: %w", err)
}
// Clone the patches repository on 'main'
dir, err := git.CloneWithGitHubToken(client, ght, repo, ref)
if err != nil {
return nil, fmt.Errorf("error cloning patches repository: %w", err)
}
entries, err := dir.Entries(ctx, dagger.DirectoryEntriesOpts{
Path: patchesPath,
})
if err != nil {
return nil, fmt.Errorf("error listing entries in repository: %w", err)
}
if len(entries) == 0 {
return nil, fmt.Errorf("no patches in the given path")
}
container := client.Container().From(git.GitImage).
WithEntrypoint([]string{}).
WithMountedDirectory("/src", src).
WithMountedDirectory("/patches", dir).
WithWorkdir("/src").
WithExec([]string{"git", "config", "--local", "user.name", "grafana"}).
WithExec([]string{"git", "config", "--local", "user.email", "engineering@grafana.com"})
for _, v := range entries {
if filepath.Ext(v) != ".patch" {
continue
}
container = container.WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`git am --3way --ignore-whitespace --ignore-space-change --committer-date-is-author-date %s > /dev/null 2>&1`, path.Join("/patches", patchesPath, v))})
}
return container.Directory("/src"), nil
}
func grafanaDirectory(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
o := GrafanaDirectoryOptsFromFlags(opts.CLIContext)
src, err := cloneOrMount(ctx, opts.Client, o.GrafanaDir, o.GrafanaRepo, o.GrafanaRef, o.GitHubToken)
if err != nil {
return nil, err
}
gitContainer := opts.Client.Container().From("alpine/git").
WithWorkdir("/src").
WithMountedDirectory("/src/.git", src.Directory(".git")).
WithEntrypoint([]string{})
commitFile := gitContainer.
WithExec([]string{"/bin/sh", "-c", "git rev-parse HEAD > .buildinfo.grafana-commit"}).
File("/src/.buildinfo.grafana-commit")
branchFile := gitContainer.
WithExec([]string{"/bin/sh", "-c", "git rev-parse --abbrev-ref HEAD > .buildinfo.grafana-branch"}).
File("/src/.buildinfo.grafana-branch")
src = src.
WithFile(".buildinfo.commit", commitFile).
WithFile(".buildinfo.branch", branchFile)
if o.PatchesRepo != "" {
withPatches, err := applyPatches(ctx, opts.Client, src, o.PatchesRepo, o.PatchesPath, o.PatchesRef, o.GitHubToken)
if err != nil {
opts.Log.Debug("patch application skipped", "error", err)
} else {
// Only replace src when there was no error.
src = withPatches
}
}
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)
}
yarnCache, err := opts.State.CacheVolume(ctx, YarnCacheDirectory)
if err != nil {
return nil, err
}
container := frontend.YarnInstall(opts.Client, src, nodeVersion, yarnCache, opts.Platform)
if _, err := containers.ExitError(ctx, container); err != nil {
return nil, err
}
return container.Directory("/src"), nil
}
func enterpriseDirectory(ctx context.Context, opts *pipeline.ArgumentOpts) (any, error) {
// Get the Grafana directory...
o := GrafanaDirectoryOptsFromFlags(opts.CLIContext)
grafanaDir, err := grafanaDirectory(ctx, opts)
if err != nil {
return nil, fmt.Errorf("error initializing grafana directory: %w", err)
}
clone, err := cloneOrMount(ctx, opts.Client, o.EnterpriseDir, o.EnterpriseRepo, o.EnterpriseRef, o.GitHubToken)
if err != nil {
return nil, fmt.Errorf("error cloning or mounting Grafana Enterprise directory: %w", err)
}
return InitializeEnterprise(opts.Client, grafanaDir.(*dagger.Directory), clone), nil
}
var GrafanaDirectoryFlags = []cli.Flag{
&cli.StringFlag{
Name: "grafana-dir",
Usage: "Local Grafana dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "enterprise-dir",
Usage: "Local Grafana Enterprise dir to use, instead of git clone",
Required: false,
},
&cli.StringFlag{
Name: "grafana-repo",
Usage: "Grafana repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana.git",
},
&cli.StringFlag{
Name: "enterprise-repo",
Usage: "Grafana Enterprise repo to clone, not valid if --grafana-dir is set",
Required: false,
Value: "https://github.com/grafana/grafana-enterprise.git",
},
&cli.StringFlag{
Name: "grafana-ref",
Usage: "Grafana ref to clone, not valid if --grafana-dir is set",
Required: false,
Value: "main",
},
&cli.StringFlag{
Name: "enterprise-ref",
Usage: "Grafana Enterprise ref to clone, not valid if --grafana-dir is set",
Required: false,
Value: "main",
},
&cli.StringFlag{
Name: "github-token",
Usage: "GitHub token to use for git cloning, by default will be pulled from GitHub",
Required: false,
},
&cli.StringFlag{
Name: "patches-repo",
Usage: "GitHub repository that contains git patches to apply to the Grafana source code. Must be an https git URL",
},
&cli.StringFlag{
Name: "patches-path",
Usage: "Path to folder containing '.patch' files to apply",
},
&cli.StringFlag{
Name: "patches-ref",
Usage: "Ref to checkout in the patches repository",
Value: "main",
},
}
// GrafanaDirectory will provide the valueFunc that initializes and returns a *dagger.Directory that has Grafana in it.
// Where possible, when cloning and no authentication options are provided, the valuefunc will try to use the configured github CLI for cloning.
var GrafanaDirectory = pipeline.Argument{
Name: "grafana-dir",
Description: "The source tree of the Grafana repository",
Flags: GrafanaDirectoryFlags,
ValueFunc: grafanaDirectory,
}
// EnterpriseDirectory will provide the valueFunc that initializes and returns a *dagger.Directory that has Grafana Enterprise initialized it.
// Where possible, when cloning and no authentication options are provided, the valuefunc will try to use the configured github CLI for cloning.
var EnterpriseDirectory = pipeline.Argument{
Name: "enterprise-dir",
Description: "The source tree of Grafana Enterprise",
Flags: GrafanaDirectoryFlags,
ValueFunc: enterpriseDirectory,
}