Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 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
117 changes: 94 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 @@ -132,6 +139,17 @@ func (c *Config) populateDefaults(ctx context.Context) error {
return nil
}

func printEnv() {
envs := os.Environ()
sort.Strings(envs) // optional: for nice sorted output

fmt.Println("----printEnv----")
for _, e := range envs {
fmt.Println(e)
}
fmt.Println("----printEnv----")
}

// Apply applies the configuration to the Git Global config
func (c *Config) Apply(ctx context.Context) error {

Expand All @@ -153,6 +171,18 @@ 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")
fmt.Println("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)
fmt.Printf("Found git-credential-cloudbees on the path at %s\n", cbGitCredentialsHelperPath)
}
printEnv()

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 +194,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 +347,38 @@ 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

fmt.Printf("command %s\n", cmd.String())

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
121 changes: 121 additions & 0 deletions internal/configuration/config_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package configuration

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

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

Expand Down Expand Up @@ -603,3 +608,119 @@ func TestConfig_insteadOfURLs(t *testing.T) {
})
}
}

// func TestConfig_Apply_Scenarios(t *testing.T) {
// originalCfg, path, err := loadConfig(config.GlobalScope)
// require.NoError(t, err)
// fmt.Printf("git config path %s \n", path)
// printConfig(t, originalCfg)
// 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) {
// fmt.Printf("------------%s-------------------------\n", tt.name)
// // 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 {
// fmt.Printf("invokeGitCredentialsHelper invoked \n")
// 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)
// fmt.Printf("git tempGitConfig path %s\n %+v\n", tempGitConfig, originalCfg)

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

// printConfig(t, originalCfg)
// fmt.Printf("------------%s-------------------------\n", tt.name)
// })
// }
// }

func printConfig(t *testing.T, originalCfg *format.Config) {

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

fmt.Println("------gitconfig-------- ")
fmt.Printf("%s\n", b.String())
fmt.Println("------gitconfig-------- ")

require.NoError(t, err)

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