Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 125 additions & 71 deletions internal/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/cloudbees-io/configure-git-global-credentials/internal"
"github.com/go-git/go-git/v5/config"
format "github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/go-git/go-git/v5/plumbing/transport"
Expand Down Expand Up @@ -46,6 +48,8 @@ type Config struct {
GitLabServerURL string `mapstructure:"gitlab-server-url"`
}

const tokenEnv = "CLOUDBEES_API_TOKEN"

func loadConfig(scope config.Scope) (_ *format.Config, _ string, retErr error) {
paths, err := config.Paths(scope)
if err != nil {
Expand Down Expand Up @@ -153,6 +157,15 @@ func (c *Config) Apply(ctx context.Context) error {
return err
}

gitCredCloudbeesExists := true
cbGitCredentialsHelperPath, err := exec.LookPath("git-credential-cloudbees")
if err != nil {
internal.Debug("Could not find git-credential-cloudbees on the path, falling back to old-style helper")
gitCredCloudbeesExists = false
} else {
internal.Debug("Found git-credential-cloudbees on the path at %s", cbGitCredentialsHelperPath)
}

homePath := os.Getenv("HOME")
actionPath := filepath.Join(homePath, ".cloudbees-configure-git-global-credentials", c.uniqueId())
if err := os.MkdirAll(actionPath, os.ModePerm); err != nil {
Expand All @@ -164,34 +177,36 @@ func (c *Config) Apply(ctx context.Context) error {
var helperConfigFile string

if !c.ssh() {
fmt.Println("🔄 Installing credentials helper ...")
if !gitCredCloudbeesExists {
fmt.Println("🔄 Installing credentials helper ...")

self, err := os.Executable()
if err != nil {
return err
}
self, err := os.Executable()
if err != nil {
return err
}

helperExecutable := filepath.Join(actionPath, "git-credential-helper")
if a, err := filepath.Abs(helperExecutable); err != nil {
helperExecutable = a
}
helperExecutable := filepath.Join(actionPath, "git-credential-helper")
if a, err := filepath.Abs(helperExecutable); err != nil {
helperExecutable = a
}

err = copyFileHelper(helperExecutable, self)
if err != nil {
return err
}
err = copyFileHelper(helperExecutable, self)
if err != nil {
return err
}

fmt.Println("✅ Credentials helper installed")
fmt.Println("✅ Credentials helper installed")

helperConfig = &format.Config{}
helperConfigFile = helperExecutable + ".cfg"
helper = fmt.Sprintf("%s credential-helper --config-file %s", helperExecutable, helperConfigFile)
helperConfig = &format.Config{}
helperConfigFile = helperExecutable + ".cfg"
helper = fmt.Sprintf("%s credential-helper --config-file %s", helperExecutable, helperConfigFile)

if _, err := os.Stat(helperConfigFile); err != nil {
b, err := os.ReadFile(helperConfigFile)
if err == nil {
// make best effort to merge existing, if it fails we will overwrite the whole
_ = format.NewDecoder(bytes.NewReader(b)).Decode(helperConfig)
if _, err := os.Stat(helperConfigFile); err != nil {
b, err := os.ReadFile(helperConfigFile)
if err == nil {
// make best effort to merge existing, if it fails we will overwrite the whole
_ = format.NewDecoder(bytes.NewReader(b)).Decode(helperConfig)
}
}
}
} else {
Expand Down Expand Up @@ -228,84 +243,123 @@ func (c *Config) Apply(ctx context.Context) error {

fmt.Println("✅ SSH private key installed")
}
if c.ssh() || !gitCredCloudbeesExists {
// For SSH, or if cloudbees-git-cred-helper does not exist, use the old approach
fmt.Printf("🔄 Updating %s ...\n", cfgPath)

fmt.Printf("🔄 Updating %s ...\n", cfgPath)
urlSection := cfg.Section("url")
credentialSection := cfg.Section("credential")

urlSection := cfg.Section("url")
credentialSection := cfg.Section("credential")
for k, v := range aliases {
for _, n := range v {
urlSection.RemoveSubsection(n)
credentialSection.RemoveSubsection(n)
}

for k, v := range aliases {
for _, n := range v {
urlSection.RemoveSubsection(n)
credentialSection.RemoveSubsection(n)
}
s := urlSection.Subsection(k)

s := urlSection.Subsection(k)
s.RemoveOption("insteadOf")

s.RemoveOption("insteadOf")
for _, n := range v {
s.AddOption("insteadOf", n)
fmt.Printf("ℹ️️ Configuring Git to clone from %s instead of %s\n", k, n)
}

for _, n := range v {
s.AddOption("insteadOf", n)
fmt.Printf("ℹ️️ Configuring Git to clone from %s instead of %s\n", k, n)
}
if helper == "" {
credentialSection.RemoveSubsection(k)
continue
}

if helper == "" {
credentialSection.RemoveSubsection(k)
continue
}
if c.Provider == BitbucketDatacenterProvider {
s = credentialSection.Subsection(c.BitbucketServerURL)
} else {
s = credentialSection.Subsection(k)
}

if c.Provider == BitbucketDatacenterProvider {
s = credentialSection.Subsection(c.BitbucketServerURL)
} else {
s = credentialSection.Subsection(k)
}
s.SetOption("helper", helper)
s.SetOption("useHttpPath", "true")

s.SetOption("helper", helper)
s.SetOption("useHttpPath", "true")
ep, err := transport.NewEndpoint(k)
if err != nil {
return err
}

ep, err := transport.NewEndpoint(k)
if err != nil {
return err
}
sec := helperConfig.Section(ep.Protocol)

sec := helperConfig.Section(ep.Protocol)
s = sec.Subsection(strings.TrimPrefix(ep.String(), ep.Protocol+":"))

s = sec.Subsection(strings.TrimPrefix(ep.String(), ep.Protocol+":"))
if c.Token != "" {
s.SetOption("username", c.providerUsername())
s.SetOption("password", base64.StdEncoding.EncodeToString([]byte(c.Token)))
} else if c.SshKey != "" {

if c.Token != "" {
s.SetOption("username", c.providerUsername())
s.SetOption("password", base64.StdEncoding.EncodeToString([]byte(c.Token)))
} else if c.SshKey != "" {
} else if c.CloudBeesApiToken != "" && c.CloudBeesApiURL != "" {
s.SetOption("username", c.providerUsername())
s.SetOption("cloudBeesApiUrl", c.CloudBeesApiURL)
s.SetOption("cloudBeesApiToken", base64.StdEncoding.EncodeToString([]byte(c.CloudBeesApiToken)))
}
}

} else if c.CloudBeesApiToken != "" && c.CloudBeesApiURL != "" {
s.SetOption("username", c.providerUsername())
s.SetOption("cloudBeesApiUrl", c.CloudBeesApiURL)
s.SetOption("cloudBeesApiToken", base64.StdEncoding.EncodeToString([]byte(c.CloudBeesApiToken)))
if helperConfigFile != "" && helperConfig != nil {
var b bytes.Buffer
if err := format.NewEncoder(&b).Encode(helperConfig); err != nil {
return err
}
if err := os.WriteFile(helperConfigFile, b.Bytes(), 0666); err != nil {
return err
}
}
}

if helperConfigFile != "" && helperConfig != nil {
var b bytes.Buffer
if err := format.NewEncoder(&b).Encode(helperConfig); err != nil {
if err := format.NewEncoder(&b).Encode(cfg); err != nil {
return err
}
if err := os.WriteFile(helperConfigFile, b.Bytes(), 0666); err != nil {

if err := os.WriteFile(cfgPath, b.Bytes(), 0666); err != nil {
return err
}

fmt.Printf("✅ Git global config at %s updated\n", cfgPath)
} else {

filterUrl := make([]string, 0, len(aliases))
for url := range aliases {
filterUrl = append(filterUrl, url)
}

return c.invokeGitCredentialsHelper(cbGitCredentialsHelperPath, cfgPath, filterUrl)
}
return nil
}

func (c *Config) invokeGitCredentialsHelper(path, gitConfigPath string, filterGitUrls []string) error {

var b bytes.Buffer
if err := format.NewEncoder(&b).Encode(cfg); err != nil {
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
helperConfig := filepath.Join(homeDir, ".git-credential-cloudbees-config")

if err := os.WriteFile(cfgPath, b.Bytes(), 0666); err != nil {
return err
filterUrlArgs := []string{}

filterUrlArgs = append(filterUrlArgs, "init")
filterUrlArgs = append(filterUrlArgs, "--config", helperConfig)
filterUrlArgs = append(filterUrlArgs, "--cloudbees-api-token-env-var", tokenEnv)
filterUrlArgs = append(filterUrlArgs, "--cloudbees-api-url", c.CloudBeesApiURL)
filterUrlArgs = append(filterUrlArgs, "--git-config-file-path", gitConfigPath)
for _, filterGitUrl := range filterGitUrls {
filterUrlArgs = append(filterUrlArgs, "--filter-git-urls", filterGitUrl)
}
cmd := exec.Command(path, filterUrlArgs...)

fmt.Printf("✅ Git global config at %s updated\n", cfgPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return nil
internal.Debug("%s", cmd.String())

cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", tokenEnv, c.CloudBeesApiToken))

return cmd.Run()
}

func (c *Config) providerUsername() string {
Expand Down
12 changes: 12 additions & 0 deletions internal/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package internal

import (
"fmt"
"os"
)

func Debug(msg string, args ...any) {
if os.Getenv("RUNNER_DEBUG") == "1" {
fmt.Println("##[debug]" + fmt.Sprintf(msg, args...))
}
}