Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
101 changes: 78 additions & 23 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,7 +48,12 @@ type Config struct {
GitLabServerURL string `mapstructure:"gitlab-server-url"`
}

func loadConfig(scope config.Scope) (_ *format.Config, _ string, retErr error) {
const (
tokenEnv = "CLOUDBEES_API_TOKEN"
cbGitCredentialsHelperPath = "git-credential-cloudbees"
)

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

gitCredCloudbeesExists := true
cbGitCredentialsHelperPath, err := exec.LookPath(cbGitCredentialsHelperPath)
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,35 +180,44 @@ 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 {
filterUrl := make([]string, 0, len(aliases))
for url := range aliases {
filterUrl = append(filterUrl, url)
}

return invokeGitCredentialsHelper(ctx, cbGitCredentialsHelperPath, cfgPath, c.CloudBeesApiURL, c.CloudBeesApiToken, filterUrl)
}
} else {
// check if the SSH key looks to be a base64 encoded private key that the user forgot to decode
Expand Down Expand Up @@ -308,6 +333,36 @@ func (c *Config) Apply(ctx context.Context) error {
return nil
}

var invokeGitCredentialsHelper = func(ctx context.Context, path, gitConfigPath, cloudbeesApiURL, cloudbeesApiToken string, filterGitUrls []string) error {

homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
helperConfig := filepath.Join(homeDir, ".git-credential-cloudbees-config")

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", cloudbeesApiURL)
filterUrlArgs = append(filterUrlArgs, "--git-config-file-path", gitConfigPath)
for _, filterGitUrl := range filterGitUrls {
filterUrlArgs = append(filterUrlArgs, "--filter-git-urls", filterGitUrl)
}
cmd := exec.CommandContext(ctx, path, filterGitUrls...)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be cmd := exec.CommandContext(ctx, path, filterUrlArgs...) ?


cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

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

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

return cmd.Run()
}

func (c *Config) providerUsername() string {
switch c.Provider {
case "github":
Expand Down
102 changes: 102 additions & 0 deletions internal/configuration/config_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package configuration

import (
"bytes"
"context"
"os"
"path/filepath"
"testing"

"github.com/go-git/go-git/v5/config"
format "github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -603,3 +610,98 @@ func TestConfig_insteadOfURLs(t *testing.T) {
})
}
}
func TestConfig_Apply_Scenarios(t *testing.T) {
originalCfg, _, err := loadConfig(config.GlobalScope)
require.NoError(t, err)
tests := []struct {
name string
config Config
setupCredentialsHelper bool
}{
{
name: "with credentials helper",
config: Config{
Repositories: "user/repo",
Provider: "github",
},
setupCredentialsHelper: true,
},
{
name: "without credentials helper",
config: Config{
Repositories: "user/repo",
Provider: "github",
},
setupCredentialsHelper: false,
},
{
name: "with credentials helper and ssh",
config: Config{
Repositories: "user/repo",
Provider: "github",
SshKey: `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACB5tesp0633JJ+Q2hfpUXljwtBX263Tq9ENr76NdZ9e3wAAAKAFw5AuBcOQ
LgAAAAtzc2gtZWQyNTUxOQAAACB5tesp0633JJ+Q2hfpUXljwtBX263Tq9ENr76NdZ9e3w
AAAEApe1n3xwD4plUvs5E82QSBggtUz1M6HiiaVEYWp7ybpnm16ynTrfckn5DaF+lReWPC
0FfbrdOr0Q2vvo11n17fAAAAFnlvdXJfZW1haWxAZXhhbXBsZS5jb20BAgMEBQYH
-----END OPENSSH PRIVATE KEY-----
`,
},
setupCredentialsHelper: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset the credentials helper invocation flag
gitCredentialsHelperInvoked := false

// mock credentials helper function
invokeGitCredentialsHelper = func(ctx context.Context, path, gitConfigPath, cloudbeesApiURL, cloudbeesApiToken string, filterGitUrls []string) error {
gitCredentialsHelperInvoked = true
return nil
}

// mock loadConfig helper function
loadConfig = func(scope config.Scope) (_ *format.Config, _ string, retErr error) {

tempGitConfig := filepath.Join(t.TempDir(), ".gitconfig")

var b bytes.Buffer
err = format.NewEncoder(&b).Encode(originalCfg)
require.NoError(t, err)

err = os.WriteFile(tempGitConfig, b.Bytes(), 0666)
require.NoError(t, err)

return originalCfg, tempGitConfig, nil
}

if tt.setupCredentialsHelper {
helperBinary(t)
}

// Execute the Apply method
err := tt.config.Apply(context.Background())
assert.NoError(t, err)

if tt.config.ssh() {
assert.Equal(t, false, gitCredentialsHelperInvoked)
} else {
assert.Equal(t, tt.setupCredentialsHelper, gitCredentialsHelperInvoked)
}

})
}
}

func helperBinary(t *testing.T) {
binPath := filepath.Join(t.TempDir(), cbGitCredentialsHelperPath)
err := os.WriteFile(binPath, []byte("dummy git credential helper"), 0500)
require.NoError(t, err)

err = os.Setenv("PATH", filepath.Dir(binPath)+string(os.PathListSeparator)+os.Getenv("PATH"))
require.NoError(t, err)

}
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...))
}
}