mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 23:52:19 +08:00
300 lines
9.9 KiB
Go
300 lines
9.9 KiB
Go
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,
|
|
}
|