Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add builtin netrc helper #82

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ go 1.20
require (
github.com/adrg/xdg v0.4.0
github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func main() {
app.Usage = "git plugin"
app.Action = run
app.Version = version
app.Commands = []*cli.Command{netrcCommand}
app.Flags = globalFlags

if err := app.Run(os.Args); err != nil {
Expand Down
77 changes: 77 additions & 0 deletions netrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"fmt"
"log"
"os"
"os/exec"

"github.com/urfave/cli/v2"

"github.com/woodpecker-ci/plugin-git/netrc"
)

var netrcCommand = &cli.Command{
Name: "netrc",
Usage: "built-in credentials helper to read netrc",
Flags: []cli.Flag{&cli.StringFlag{
Name: "home",
Usage: "Change home directory",
EnvVars: []string{"PLUGIN_HOME"},
}},
Comment on lines +17 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this already added because you have the app.Flags = globalFlags statement which adds the flags globally?

Copy link
Member Author

Choose a reason for hiding this comment

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

need to test ... but as it's mostly used only internally I dont think we do need it anyway ...

Copy link
Contributor

Choose a reason for hiding this comment

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

Tested this, working as I expected.

You can try with this:

package main

import (
	"fmt"
	"os"

	"github.com/urfave/cli/v2"
)

func main() {
	app := cli.NewApp()
	app.Flags = []cli.Flag{
		&cli.StringFlag{
			Name: "testflag",
		},
	}
	app.Commands = []*cli.Command{
		&cli.Command{
			Name: "cmd",
			Action: func(ctx *cli.Context) error {
				fmt.Println(ctx.String("testflag"))
				return nil
			},
		},
	}
	fmt.Println(app.Run(os.Args))
}

Copy link
Contributor

Choose a reason for hiding this comment

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

That means just apply this:

Suggested change
Flags: []cli.Flag{&cli.StringFlag{
Name: "home",
Usage: "Change home directory",
EnvVars: []string{"PLUGIN_HOME"},
}},

Copy link
Contributor

Choose a reason for hiding this comment

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

@6543 @qwerty287 What is the status here?

Copy link
Member Author

Choose a reason for hiding this comment

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

as per lable > wip till i personally do dedicate time to it or it gets priorised at work ...

Action: netrcGet,
}

func netrcGet(c *cli.Context) error {
if c.Args().Len() == 0 {
curExec, err := os.Executable()
if err != nil {
return err
}
fmt.Printf("built-in credentials helper to read netrc\n"+
"exec \"git config --global credential.helper '%s netrc'\" to use it\n", curExec)
return nil
}

// set custom home
if c.IsSet("home") {
os.Setenv("HOME", c.String("home"))
}

// implement custom git credentials helper
// https://git-scm.com/docs/gitcredentials
switch c.Args().First() {
case "get":
netRC, err := netrc.Read()
if err != nil {
return err
}
if netRC != nil {
fmt.Printf("username=%s\n", netRC.Login)
fmt.Printf("password=%s\n", netRC.Password)
fmt.Println("quit=true")
}
case "store":
// TODO: netrc.Save()
case "erase":
_, err := netrc.Delete()
if err != nil {
return err
}
default:
return fmt.Errorf("got unknown helper arg '%s'", c.Args().First())
}

return nil
}

func setNetRCHelper() *exec.Cmd {
curExec, err := os.Executable()
if err != nil {
log.Fatal(err)
}

credHelper := fmt.Sprintf("%s netrc", curExec)

return appendEnv(exec.Command("git", "config", "--global", "credential.helper", credHelper), defaultEnvVars...)
}
131 changes: 131 additions & 0 deletions netrc/netrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package netrc

import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)

type NetRC struct {
Machine,
Login,
Password string
}

func getFilePath() (string, error) {
// get home
homeDir := getHomeDir()

// calc netrc file path
file := filepath.Join(homeDir, ".netrc")
if runtime.GOOS == "windows" {
file = filepath.Join(homeDir, "_netrc")
}

stats, err := os.Stat(file)
if err != nil {
return "", nil
}
if !stats.Mode().IsRegular() {
return "", fmt.Errorf("'%s' exist but is a %s", file, stats.Mode().Type().String())
}
return file, nil
}

// Delete delete the netrc if file exist
func Delete() (bool, error) {
file, err := getFilePath()
if err != nil || file == "" {
return false, err
}

return true, os.Remove(file)
}

// Save save a netrc
func Save(n *NetRC) error {
if n == nil {
return nil
}

// get home
homeDir := getHomeDir()

// calc netrc file path
file := filepath.Join(homeDir, ".netrc")
if runtime.GOOS == "windows" {
file = filepath.Join(homeDir, "_netrc")
}

content := fmt.Sprintf(`
machine %s
login %s
password %s
`,
n.Machine,
n.Login,
n.Password,
)

return os.WriteFile(file, []byte(content), 0o600)
}

// Read return netrc if file or env var exist
func Read() (*NetRC, error) {
// try to read from env var
netRC := &NetRC{
Machine: os.Getenv("CI_NETRC_MACHINE"),
Login: os.Getenv("CI_NETRC_USERNAME"),
Password: os.Getenv("CI_NETRC_PASSWORD"),
}
// if we get at least user and password from env we can return
if netRC.Login != "" && netRC.Password != "" {
return netRC, nil
}

file, err := getFilePath()
if err != nil || file == "" {
return nil, err
}
raw, err := os.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("error while reading file '%s': %w", file, err)
}

return parseNetRC(string(raw))
}

func getHomeDir() string {
if homeDir := os.Getenv("HOME"); homeDir != "" {
return homeDir
}
if homeDir, _ := os.UserHomeDir(); homeDir != "" {
return homeDir
}
pwd, _ := os.Getwd()
return pwd
}

func parseNetRC(raw string) (*NetRC, error) {
netRC := &NetRC{}
for _, v := range strings.Split(raw, "\n") {
v = strings.TrimSpace(v)
if strings.HasPrefix(v, "machine") {
netRC.Machine = strings.TrimSpace(strings.TrimPrefix(v, "machine"))
}
if strings.HasPrefix(v, "login") {
netRC.Login = strings.TrimSpace(strings.TrimPrefix(v, "login"))
}
if strings.HasPrefix(v, "password") {
netRC.Password = strings.TrimSpace(strings.TrimPrefix(v, "password"))
}
}

if netRC.Login == "" && netRC.Password == "" {
return nil, fmt.Errorf("parsing netrc failed, got empty result")
}

return netRC, nil
}
26 changes: 26 additions & 0 deletions netrc/netrc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package netrc

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseNetRC(t *testing.T) {
n, err := parseNetRC(``)
assert.Error(t, err)
assert.Nil(t, n)

n, err = parseNetRC(`machine example.org`)
assert.Error(t, err)
assert.Nil(t, n)

n, err = parseNetRC(`
machine test.com
password someTestPWD

login awesomeUser
`)
assert.NoError(t, err)
assert.EqualValues(t, &NetRC{Machine: "test.com", Login: "awesomeUser", Password: "someTestPWD"}, n)
}
6 changes: 6 additions & 0 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
Expand Down Expand Up @@ -51,6 +52,11 @@ func (p Plugin) Exec() error {

var cmds []*exec.Cmd

// windows doesn't understand netrc, so we use our build-in helper
if runtime.GOOS == "windows" {
cmds = append(cmds, setNetRCHelper())
}

if p.Config.SkipVerify {
cmds = append(cmds, skipVerify())
} else if p.Config.CustomCert != "" {
Expand Down