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