From aafe84d823e0a1683d2c5f3678a6ca9376bcba78 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Mon, 14 Sep 2020 20:24:57 +0100 Subject: [PATCH] Add support for cloning private repos as part of automation API (#5333) --- CHANGELOG.md | 5 +++ sdk/go/x/auto/example_test.go | 58 ++++++++++++++++++++++++++++++++ sdk/go/x/auto/git.go | 48 +++++++++++++++++++++++++- sdk/go/x/auto/local_workspace.go | 19 +++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec58a014e..13367355b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ CHANGELOG asking interactively as the final option. [#5327](https://github.com/pulumi/pulumi/pull/5327) +- feat(autoapi): Add support for working with private Git repos. Either `SSHPrivateKeyPath`, + `PersonalAccessToken` or `UserName` and `Password` can be pushed to the `auto.GitRepo` struct + when interacting with a private repo + [#5333](https://github.com/pulumi/pulumi/pull/5333) + ## 2.10.0 (2020-09-10) - feat(autoapi): add Upsert methods for stacks diff --git a/sdk/go/x/auto/example_test.go b/sdk/go/x/auto/example_test.go index 8a7381efe..200c8fc3d 100644 --- a/sdk/go/x/auto/example_test.go +++ b/sdk/go/x/auto/example_test.go @@ -18,6 +18,7 @@ package auto import ( "context" "fmt" + "os" "os/exec" "path/filepath" @@ -205,6 +206,63 @@ func ExampleGitRepo() { NewStackRemoteSource(ctx, fqsn, repo, Project(project)) } +func ExampleGitRepo_personalAccessToken() { + ctx := context.Background() + pName := "go_remote_proj" + fqsn := FullyQualifiedStackName("myOrg", pName, "myStack") + + // Get the Sourcecode Repository PERSONAL_ACCESS_TOKEN + token, _ := os.LookupEnv("PERSONAL_ACCESS_TOKEN") + + repo := GitRepo{ + URL: "https://github.com/pulumi/test-repo.git", + ProjectPath: "goproj", + Auth: &GitAuth{ + PersonalAccessToken: token, + }, + } + + // initialize a stack from the git repo, specifying our project override + NewStackRemoteSource(ctx, fqsn, repo) +} + +func ExampleGitRepo_privateKeyPath() { + ctx := context.Background() + pName := "go_remote_proj" + fqsn := FullyQualifiedStackName("myOrg", pName, "myStack") + + repo := GitRepo{ + URL: "https://github.com/pulumi/test-repo.git", + ProjectPath: "goproj", + Auth: &GitAuth{ + SSHPrivateKeyPath: "/Users/myuser/.ssh/id_rsa", + Password: "PrivateKeyPassword", + }, + } + + // initialize a stack from the git repo, specifying our project override + NewStackRemoteSource(ctx, fqsn, repo) +} + +func ExampleGitRepo_usernameAndPassword() { + ctx := context.Background() + pName := "go_remote_proj" + fqsn := FullyQualifiedStackName("myOrg", pName, "myStack") + + repo := GitRepo{ + URL: "https://github.com/pulumi/test-repo.git", + ProjectPath: "goproj", + Auth: &GitAuth{ + // This will use a username and password combination for the private repo + Username: "myuser", + Password: "myPassword1234!", + }, + } + + // initialize a stack from the git repo, specifying our project override + NewStackRemoteSource(ctx, fqsn, repo) +} + func ExampleLocalWorkspace() { ctx := context.Background() // create a workspace from a local project diff --git a/sdk/go/x/auto/git.go b/sdk/go/x/auto/git.go index bde379686..9e3a53228 100644 --- a/sdk/go/x/auto/git.go +++ b/sdk/go/x/auto/git.go @@ -21,11 +21,57 @@ import ( "github.com/pkg/errors" git "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" ) func setupGitRepo(ctx context.Context, workDir string, repoArgs *GitRepo) (string, error) { + cloneOptions := &git.CloneOptions{ + URL: repoArgs.URL, + } + + if repoArgs.Auth != nil { + + authDetails := repoArgs.Auth + // Each of the authentication options are mutually exclusive so let's check that only 1 is specified + if (authDetails.SSHPrivateKeyPath != "" && authDetails.PersonalAccessToken != "") || + (authDetails.SSHPrivateKeyPath != "" && authDetails.Username != "") || + (authDetails.PersonalAccessToken != "" && authDetails.Username != "") { + return "", errors.New("please specify one authentication option of `Personal Access Token`, " + + "`Username\\Password` or `SSH Private Key Path`") + } + + // Firstly we will try to check that an SSH Private Key Path has been specified + if authDetails.SSHPrivateKeyPath != "" { + publicKeys, err := ssh.NewPublicKeysFromFile("git", repoArgs.Auth.SSHPrivateKeyPath, repoArgs.Auth.Password) + if err != nil { + return "", errors.Wrap(err, "unable to use SSH Private Key") + } + + cloneOptions.Auth = publicKeys + } + + // Then we check to see if a Personal Access Token has been specified + // the username for use with a PAT can be *anything* but an empty string + // so we are setting this to `git` + if authDetails.PersonalAccessToken != "" { + cloneOptions.Auth = &http.BasicAuth{ + Username: "git", + Password: repoArgs.Auth.PersonalAccessToken, + } + } + + // then we check to see if a username and a password has been specified + if authDetails.Password != "" && authDetails.Username != "" { + cloneOptions.Auth = &http.BasicAuth{ + Username: repoArgs.Auth.Username, + Password: repoArgs.Auth.Password, + } + } + } + // clone - repo, err := git.PlainCloneContext(ctx, workDir, false, &git.CloneOptions{URL: repoArgs.URL}) + repo, err := git.PlainCloneContext(ctx, workDir, false, cloneOptions) if err != nil { return "", errors.Wrap(err, "unable to clone repo") } diff --git a/sdk/go/x/auto/local_workspace.go b/sdk/go/x/auto/local_workspace.go index ee4eb8b45..7d10e7364 100644 --- a/sdk/go/x/auto/local_workspace.go +++ b/sdk/go/x/auto/local_workspace.go @@ -584,6 +584,25 @@ type GitRepo struct { CommitHash string // Optional function to execute after enlisting in the specified repo. Setup SetupFn + // GitAuth is the different Authentication options for the Git repository + Auth *GitAuth +} + +// GitAuth is the authentication details that can be specified for a private Git repo. +// There are 3 different authentication paths: +// * PersonalAccessToken +// * SSHPrivateKeyPath (and it's potential password) +// * Username and Password +// Only 1 authentication path is valid. If more than 1 is specified it will result in an error +type GitAuth struct { + // The absolute path to a private key for access to the git repo + SSHPrivateKeyPath string + // The password that pairs with a username or as part of an SSH Private Key + Password string + // PersonalAccessToken is a Git personal access token in replacement of your password + PersonalAccessToken string + // Username is the username to use when authenticating to a git repository + Username string } // SetupFn is a function to execute after enlisting in a git repo.