CI: move grafana-build into pkg/build (#105640)
* move grafana-build into pkg/build
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"dagger.io/dagger"
|
||||
"github.com/grafana/grafana/pkg/build/daggerbuild/containers"
|
||||
)
|
||||
|
||||
const GitImage = "alpine/git"
|
||||
|
||||
type GitCloneOptions struct {
|
||||
Ref string
|
||||
URL string
|
||||
|
||||
SSHKeyPath string
|
||||
|
||||
// Username is injected into the final URL used for cloning
|
||||
Username string
|
||||
// Password is injected into the final URL used for cloning
|
||||
Password string
|
||||
}
|
||||
|
||||
// CloneContainer returns the container definition that uses git clone to clone the 'git url' and checks out the ref provided at 'ref'.
|
||||
// Multiple refs can be provided via a space character (' '). If multiple refs are provided, then the container will attempt to checkout
|
||||
// each ref at a time, stopping at the first one that is successful.
|
||||
// This can be useful in PRs which have a coupled association with another codebase.
|
||||
// A practical example (and why this exists): "${pr_source_branch} ${pr_target_branch} ${main}" will first attempt to checkout the PR source branch, then the PR target branch, then "main"; whichever is successul first.
|
||||
func CloneContainer(d *dagger.Client, opts *GitCloneOptions) (*dagger.Container, error) {
|
||||
var err error
|
||||
if opts.URL == "" {
|
||||
return nil, errors.New("URL can not be empty")
|
||||
}
|
||||
|
||||
if opts.SSHKeyPath != "" && (opts.Username != "" || opts.Password != "") {
|
||||
return nil, fmt.Errorf("conflicting options: use either username/password or an SSH key")
|
||||
}
|
||||
|
||||
cloneURL := opts.URL
|
||||
if opts.Username != "" && opts.Password != "" {
|
||||
cloneURL, err = injectURLCredentials(cloneURL, opts.Username, opts.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to inject credentials into cloning URL: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cloneArgs := []string{"git", "clone"}
|
||||
|
||||
cloneArgs = append(cloneArgs, "${GIT_CLONE_URL}", "src")
|
||||
|
||||
container := d.Container().From(GitImage).
|
||||
WithEnvVariable("REF", opts.Ref).
|
||||
WithEnvVariable("UNAUTHENTICATED_CLONE_URL", opts.URL).
|
||||
WithEntrypoint([]string{})
|
||||
|
||||
if opts.SSHKeyPath != "" {
|
||||
if !strings.Contains(opts.URL, "@") {
|
||||
return nil, errors.New("git URL with SSH needs an '@'")
|
||||
}
|
||||
if !strings.Contains(opts.URL, ":") {
|
||||
return nil, errors.New("git URL with SSH needs a ':'")
|
||||
}
|
||||
|
||||
host := opts.URL[strings.Index(opts.URL, "@")+1 : strings.Index(opts.URL, ":")]
|
||||
|
||||
container = container.
|
||||
WithExec([]string{"mkdir", "-p", "/root/.ssh"}).
|
||||
WithMountedFile("/root/.ssh/id_rsa", d.Host().Directory(filepath.Dir(opts.SSHKeyPath)).File(filepath.Base(opts.SSHKeyPath))).
|
||||
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf(`ssh-keyscan %s > /root/.ssh/known_hosts`, host)})
|
||||
}
|
||||
|
||||
cloneURLSecret := d.SetSecret("git-clone-url", cloneURL)
|
||||
|
||||
// GIT_REFS is included as an environment variable here to control caching.
|
||||
// 1. We should ALWAYS be using the commit hash to clone / checkout git refs.
|
||||
// 2. If the ref changes, then we should run 'fetch' again.
|
||||
container = container.
|
||||
WithSecretVariable("GIT_CLONE_URL", cloneURLSecret).
|
||||
WithExec([]string{"/bin/sh", "-c", strings.Join(cloneArgs, " ")}).
|
||||
WithEnvVariable("GIT_REFS", opts.Ref).
|
||||
WithExec([]string{"git", "-C", "src", "fetch"})
|
||||
|
||||
ref := "main"
|
||||
if opts.Ref != "" {
|
||||
ref = opts.Ref
|
||||
}
|
||||
|
||||
// TODO: this section really needs to be its own function with unit tests, or an interface or something.
|
||||
var (
|
||||
checkouts = strings.Split(ref, " ")
|
||||
checkoutArgs = []string{fmt.Sprintf(`if git -C src checkout %[1]s; then echo "checked out %[1]s";`, checkouts[0])}
|
||||
)
|
||||
|
||||
for _, v := range checkouts[1:] {
|
||||
checkoutArgs = append(checkoutArgs, fmt.Sprintf(`elif git -C src checkout %[1]s; then echo "checked out %[1]s";`, v))
|
||||
}
|
||||
|
||||
checkoutArgs = append(checkoutArgs, "else exit 3; fi")
|
||||
|
||||
container = container.WithExec([]string{"/bin/sh", "-c", strings.Join(checkoutArgs, " ")})
|
||||
log.Println(strings.Join(checkoutArgs, " "))
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func CloneWithGitHubToken(d *dagger.Client, token, url, ref string) (*dagger.Directory, error) {
|
||||
container, err := CloneContainer(d, &GitCloneOptions{
|
||||
URL: url,
|
||||
Ref: ref,
|
||||
Username: "x-oauth-token",
|
||||
Password: token,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container, err = containers.ExitError(context.Background(), container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return container.Directory("src"), nil
|
||||
}
|
||||
|
||||
// injectURLCredentials modifies as provided URL to set the given username and password in it.
|
||||
func injectURLCredentials(u string, username string, password string) (string, error) {
|
||||
rawURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ui := url.UserPassword(username, password)
|
||||
rawURL.User = ui
|
||||
return rawURL.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user