Skip to content

Commit

Permalink
Add git credential fill support
Browse files Browse the repository at this point in the history
  • Loading branch information
discordianfish committed Aug 21, 2023
1 parent 3891683 commit 3727db3
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 8 deletions.
35 changes: 27 additions & 8 deletions pkg/diambra/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/diambra/cli/pkg/container"
"github.com/diambra/cli/pkg/diambra/client"
"github.com/diambra/cli/pkg/git"
"github.com/diambra/init/initializer"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
Expand Down Expand Up @@ -236,14 +237,16 @@ var ErrInvalidArgs = errors.New("either image, manifest path or submission id mu
type SubmissionConfig struct {
logger log.Logger

Mode string
Difficulty string
EnvVars map[string]string
Sources map[string]string
Secrets map[string]string
ArgsIsCommand bool
ManifestPath string
SubmissionID int
Provider string
Mode string
Difficulty string
EnvVars map[string]string
Sources map[string]string
Secrets map[string]string
FillGitCredentials bool
ArgsIsCommand bool
ManifestPath string
SubmissionID int
}

func NewSubmissionConfig(logger log.Logger) *SubmissionConfig {
Expand All @@ -258,6 +261,7 @@ func (c *SubmissionConfig) AddFlags(flags *pflag.FlagSet) {
flags.StringToStringVarP(&c.EnvVars, "submission.env", "e", nil, "Environment variables to pass to the agent")
flags.StringToStringVarP(&c.Sources, "submission.source", "u", nil, "Source urls to pass to the agent")
flags.StringToStringVar(&c.Secrets, "submission.secret", nil, "Secrets to pass to the agent")
flags.BoolVar(&c.FillGitCredentials, "submission.git-credential-fill", false, "Use git credentials for source urls")
flags.StringVar(&c.ManifestPath, "submission.manifest", "", "Path to manifest file.")
flags.IntVar(&c.SubmissionID, "submission.id", 0, "Submission ID to retrieve manifest from")
flags.BoolVar(&c.ArgsIsCommand, "submission.set-command", false, "Treat positional arguments are command instead of entrypoint")
Expand Down Expand Up @@ -328,6 +332,21 @@ func (c *SubmissionConfig) Submission(credPath string, args []string) (*client.S
}

if manifest.Sources != nil {
if c.FillGitCredentials {
if c.Secrets == nil {
c.Secrets = make(map[string]string)
}
level.Debug(c.logger).Log("msg", "Adding git secrets")
secrets, err := git.CredentialsFill(manifest.Sources)
if err != nil {
return nil, err
}
for k, v := range secrets {
c.Secrets[k] = v
}
level.Debug(c.logger).Log("secrets", fmt.Sprintf("%v", secrets))
}

init, err := initializer.NewInitializer(c.logger, manifest.Sources, c.Secrets, map[string]string{}, "")
if err != nil {
return nil, err
Expand Down
85 changes: 85 additions & 0 deletions pkg/git/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package git

import (
"bytes"
"fmt"
"net/url"
"os/exec"
"strings"
)

type CredentialProvider interface {
Credentials(url string) (map[string]string, error)
}

type GitCredential struct{}

func (c *GitCredential) Credentials(url string) (map[string]string, error) {
cmd := exec.Command("git", "credential", "fill")
cmd.Stdin = strings.NewReader("url=" + url + "\n")

var stdout bytes.Buffer
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return nil, err
}

credentials := make(map[string]string)
lines := strings.Split(stdout.String(), "\n")
for _, line := range lines {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
credentials[parts[0]] = parts[1]
}
}

return credentials, nil
}

type git struct {
Provider CredentialProvider
}

var Git = &git{
Provider: &GitCredential{},
}

func CredentialsFill(source map[string]string) (map[string]string, error) {
return Git.CredentialsFill(source)
}

// CredentialsFill calls git credential fill for each source and returns
// a new source map with templating as well as a map of credentials for the templated values.
func (g *git) CredentialsFill(source map[string]string) (map[string]string, error) {
secrets := make(map[string]string)
i := 0
for k, v := range source {
i++
u, err := url.Parse(v)
if err != nil {
return nil, fmt.Errorf("failed to parse url %s: %w", v, err)
}
credentials, err := g.Provider.Credentials(v)
if err != nil {
return nil, err
}
if credentials["password"] == "" {
continue
}

if credentials["host"] != u.Host {
return nil, fmt.Errorf("host %s does not match %s (this should never happend)", credentials["host"], u.Host)
}

var (
uservar = fmt.Sprintf("git_username_%d", i)
passvar = fmt.Sprintf("git_password_%d", i)
)

u.User = url.UserPassword(fmt.Sprintf("{{ %s }}", uservar), fmt.Sprintf("{{ %s }}", passvar))
secrets[uservar] = credentials["username"]
secrets[passvar] = credentials["password"]
source[k] = fmt.Sprintf("%s://{{ .Secrets.%s }}:{{ .Secrets.%s }}@%s%s", u.Scheme, uservar, passvar, u.Host, u.Path)
}
return secrets, nil
}
70 changes: 70 additions & 0 deletions pkg/git/credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package git

import (
"reflect"
"testing"
)

type MockCredentialProvider struct {
creds map[string]string
}

func (m *MockCredentialProvider) Credentials(url string) (map[string]string, error) {
return m.creds, nil
}

func TestCredentialsFill(t *testing.T) {
for _, tc := range []struct {
name string
source map[string]string
credentials map[string]string
expectedSource map[string]string
expectedSecrets map[string]string
}{
{
name: "no credentials",
source: map[string]string{
"foo": "git+https://example.com/foo",
},
credentials: map[string]string{},
expectedSource: map[string]string{
"foo": "git+https://example.com/foo",
},
expectedSecrets: map[string]string{},
},
{
name: "single credential",
source: map[string]string{
"foo": "git+https://example.com/foo",
},
credentials: map[string]string{
"username": "foo",
"password": "bar",
"host": "example.com",
},
expectedSource: map[string]string{
"foo": "git+https://{{ git_username_1 }}:{{ git_password_1 }}@example.com/foo",
},
expectedSecrets: map[string]string{
"git_username_1": "foo",
"git_password_1": "bar",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
git := &git{
Provider: &MockCredentialProvider{tc.credentials},
}
secrets, err := git.CredentialsFill(tc.source)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(tc.expectedSource, tc.source) {
t.Fatalf("expected source %v, got %v", tc.expectedSource, tc.source)
}
if !reflect.DeepEqual(tc.expectedSecrets, secrets) {
t.Fatalf("expected secrets %v, got %v", tc.expectedSecrets, secrets)
}
})
}
}

0 comments on commit 3727db3

Please sign in to comment.