Skip to content

Commit

Permalink
Merge pull request #12 from rvflash/netrc
Browse files Browse the repository at this point in the history
Adds support of netrc file to allow auto-login with basic authentication
  • Loading branch information
rvflash authored May 30, 2023
2 parents 4015978 + 42b9352 commit 9e9f704
Show file tree
Hide file tree
Showing 13 changed files with 385 additions and 173 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x]
go-version: [1.20.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ go 1.14

require (
github.com/fatih/color v1.15.0
github.com/go-git/go-git/v5 v5.6.1
github.com/go-git/go-git/v5 v5.7.0
github.com/golang/mock v1.7.0-rc.1
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84
github.com/matryer/is v1.4.1
github.com/mattn/go-isatty v0.0.18
github.com/rvflash/workr v0.1.0
golang.org/x/mod v0.9.0
github.com/mattn/go-isatty v0.0.19
github.com/rvflash/workr v1.0.0
golang.org/x/mod v0.10.0
)
88 changes: 58 additions & 30 deletions go.sum

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ package app

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/rvflash/goup/internal/errors"
"github.com/rvflash/goup/internal/log"
"github.com/rvflash/goup/internal/netrc"
"github.com/rvflash/goup/internal/vcs"
"github.com/rvflash/goup/pkg/goup"
"github.com/rvflash/goup/pkg/mod"
)
Expand All @@ -32,7 +35,7 @@ func WithChecker(f goup.Checker) Configurator {
}

// WithLogger defines the logger used to print events.
// By default we use a DevNull.
// By default, we use a DevNull.
func WithLogger(l log.Printer) Configurator {
return func(a *App) error {
if l == nil {
Expand All @@ -55,13 +58,26 @@ func WithParser(f mod.Parser) Configurator {
}
}

// WithNetrc parses netrc file to allow auto-login with basic authentication.
func WithNetrc() Configurator {
return func(a *App) error {
rc, err := netrc.Parse()
if err != nil {
return fmt.Errorf("netrc: %w: %w", err, errors.ErrRepository)
}
a.autologin = rc
return nil
}
}

// Open tries to create a new instance of App.
func Open(version string, opts ...Configurator) (*App, error) {
a := &App{
buildVersion: version,
}
opts = append([]Configurator{
WithLogger(log.DevNull()),
WithNetrc(),
WithParser(mod.Parse),
WithChecker(goup.Check),
}, opts...)
Expand All @@ -79,6 +95,7 @@ type App struct {
goup.Config

check goup.Checker
autologin vcs.BasicAuthentifier
parse mod.Parser
logger log.Printer
buildVersion string
Expand All @@ -100,6 +117,7 @@ func (a *App) Check(ctx context.Context, paths []string) (failure bool) {
return false
}
}
a.Config.BasicAuth = a.autologin
for _, path := range checkPaths(paths) {
f, err := a.parse(path)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion internal/log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ func (l *Logger) printf(format string, color func(a ...interface{}) string, args

func colors(f func(a ...interface{}) string, args []interface{}) []interface{} {
for k, v := range args {
args[k] = f(v)
arg := v
args[k] = f(arg)
}
return args
}
Expand Down
53 changes: 53 additions & 0 deletions internal/netrc/netrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Package netrc provides methods to handle basic authentification used by the auto-login process in .netrc f.
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html.
package netrc

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

"github.com/jdxcode/netrc"
"github.com/rvflash/goup/internal/vcs"
)

// File represents a netrc f.
type File struct {
f *netrc.Netrc
}

// Parse the netrc f behind the NETRC environment variable.
// If undefined, we try to find the .netrc f in the user home directory.
func Parse() (rc File, err error) {
path := os.Getenv("NETRC")
if path == "" {
path, err = os.UserHomeDir()
if err != nil {
return rc, fmt.Errorf("user home directory: %w", err)
}
path = filepath.Join(path, ".netrc")
}
f, err := netrc.Parse(path)
if err != nil {
if os.IsNotExist(err) {
return rc, nil
}
return rc, fmt.Errorf("netrc parsing %q: %w", path, err)
}
return File{f: f}, nil
}

// BasicAuth returns if available the basic auth information for this hostname.
func (c File) BasicAuth(host string) *vcs.BasicAuth {
if c.f == nil {
return nil
}
h := c.f.Machine(host)
if h == nil {
return nil
}
return &vcs.BasicAuth{
Username: h.Get("login"),
Password: h.Get("password"),
}
}
20 changes: 16 additions & 4 deletions internal/vcs/git/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/rvflash/goup/internal/errors"
Expand All @@ -30,13 +31,15 @@ const (

// VCS is a Git PrintVersion Control VCS.
type VCS struct {
auth vcs.BasicAuthentifier
client vcs.ClientChooser
storage storage.Storer
}

// New returns a new instance of VCS.
func New(client vcs.ClientChooser) *VCS {
func New(client vcs.ClientChooser, auth vcs.BasicAuthentifier) *VCS {
return &VCS{
auth: auth,
client: client,
storage: memory.NewStorage(),
}
Expand Down Expand Up @@ -110,8 +113,17 @@ func (s *VCS) fetch(rawURL string) *reference {
URLs: []string{u.String()},
})
// Retrieves the releases list of the repository.
var res []*plumbing.Reference
res, err = rem.List(&git.ListOptions{})
var (
res []*plumbing.Reference
rfs = &git.ListOptions{}
)
if ba := s.auth.BasicAuth(u.Host); ba != nil {
rfs.Auth = &http.BasicAuth{
Username: ba.Username,
Password: ba.Password,
}
}
res, err = rem.List(rfs)
if err != nil {
ref.err = vcs.Errorf(Name, errors.ErrFetch, err)
return ref
Expand All @@ -128,7 +140,7 @@ func (s *VCS) fetch(rawURL string) *reference {
}

func (s *VCS) ready(ctx context.Context) bool {
return ctx != nil && s.storage != nil && s.client != nil
return ctx != nil && s.storage != nil && s.client != nil && s.auth != nil
}

type reference struct {
Expand Down
91 changes: 69 additions & 22 deletions internal/vcs/git/vcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
)

const (
pkgName = "github.com/src-d/go-git"
repoURL = "https://github.com/src-d/go-git"
unsafeURL = "http://github.com/src-d/go-git"
hostname = "github.com"
pkgName = hostname + "/src-d/go-git"
repoURL = "https://" + pkgName
unsafeURL = "http://" + pkgName
)

func TestVCS_CanFetch(t *testing.T) {
Expand All @@ -39,21 +40,38 @@ func TestVCS_FetchPath(t *testing.T) {
var (
are = is.New(t)
dt = map[string]struct {
cli vcs.ClientChooser
ctx context.Context
in string
err error
cli vcs.ClientChooser
auth vcs.BasicAuthentifier
ctx context.Context
in string
err error
}{
"default": {err: errup.ErrSystem},
"missing context": {cli: mockvcs.NewMockClientChooser(ctrl), in: pkgName, err: errup.ErrSystem},
"missing path": {cli: mockvcs.NewMockClientChooser(ctrl), ctx: ctx, err: errup.ErrRepository},
"ok": {cli: newMockClientChooser(ctrl), ctx: ctx, in: pkgName},
"Default": {err: errup.ErrSystem},
"Missing authentifier": {cli: mockvcs.NewMockClientChooser(ctrl), err: errup.ErrSystem},
"Missing context": {
cli: mockvcs.NewMockClientChooser(ctrl),
auth: mockvcs.NewMockBasicAuthentifier(ctrl),
in: pkgName,
err: errup.ErrSystem,
},
"Missing path": {
cli: mockvcs.NewMockClientChooser(ctrl),
auth: mockvcs.NewMockBasicAuthentifier(ctrl),
ctx: ctx,
err: errup.ErrRepository,
},
"OK": {
cli: newMockClientChooser(ctrl),
auth: newMockBasicAuthentifier(ctrl),
ctx: ctx,
in: pkgName,
},
}
)
for name, ts := range dt {
tt := ts
t.Run(name, func(t *testing.T) {
s := git.New(tt.cli)
s := git.New(tt.cli, tt.auth)
_, err := s.FetchPath(tt.ctx, tt.in)
are.True(errors.Is(err, tt.err)) // mismatch error
})
Expand All @@ -69,22 +87,45 @@ func TestVCS_FetchURL(t *testing.T) {
var (
are = is.New(t)
dt = map[string]struct {
cli vcs.ClientChooser
ctx context.Context
in string
err error
cli vcs.ClientChooser
auth vcs.BasicAuthentifier
ctx context.Context
in string
err error
}{
"Default": {err: errup.ErrSystem},
"Missing context": {cli: mockvcs.NewMockClientChooser(ctrl), in: repoURL, err: errup.ErrSystem},
"Missing url": {cli: mockvcs.NewMockClientChooser(ctrl), ctx: ctx, err: errup.ErrRepository},
"Invalid": {cli: newMockClientChooser(ctrl), ctx: ctx, in: unsafeURL, err: errup.ErrRepository},
"Ok": {cli: mockvcs.NewMockClientChooser(ctrl), ctx: ctx, in: repoURL},
"Default": {err: errup.ErrSystem},
"Missing authentifier": {cli: mockvcs.NewMockClientChooser(ctrl), err: errup.ErrSystem},
"Missing context": {
cli: mockvcs.NewMockClientChooser(ctrl),
auth: mockvcs.NewMockBasicAuthentifier(ctrl),
in: repoURL,
err: errup.ErrSystem,
},
"Missing url": {
cli: mockvcs.NewMockClientChooser(ctrl),
auth: mockvcs.NewMockBasicAuthentifier(ctrl),
ctx: ctx,
err: errup.ErrRepository,
},
"Invalid": {
cli: newMockClientChooser(ctrl),
auth: mockvcs.NewMockBasicAuthentifier(ctrl),
ctx: ctx,
in: unsafeURL,
err: errup.ErrRepository,
},
"OK": {
cli: mockvcs.NewMockClientChooser(ctrl),
auth: newMockBasicAuthentifier(ctrl),
ctx: ctx,
in: repoURL,
},
}
)
for name, ts := range dt {
tt := ts
t.Run(name, func(t *testing.T) {
s := git.New(tt.cli)
s := git.New(tt.cli, tt.auth)
_, err := s.FetchURL(tt.ctx, tt.in)
are.True(errors.Is(err, tt.err)) // mismatch error
})
Expand All @@ -96,3 +137,9 @@ func newMockClientChooser(ctrl *gomock.Controller) *mockvcs.MockClientChooser {
c.EXPECT().AllowInsecure(pkgName).Return(false).AnyTimes()
return c
}

func newMockBasicAuthentifier(ctrl *gomock.Controller) *mockvcs.MockBasicAuthentifier {
m := mockvcs.NewMockBasicAuthentifier(ctrl)
m.EXPECT().BasicAuth(hostname).Return(nil).AnyTimes()
return m
}
11 changes: 11 additions & 0 deletions internal/vcs/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ type System interface {
FetchURL(ctx context.Context, url string) (semver.Tags, error)
}

// BasicAuth contains basic auth properties.
type BasicAuth struct {
Username string
Password string
}

// BasicAuthentifier must be implemented to allow request with basic host by hostname.
type BasicAuthentifier interface {
BasicAuth(host string) *BasicAuth
}

// Client must be implemented by any HTTP client.
type Client interface {
Do(req *http.Request) (*http.Response, error)
Expand Down
3 changes: 2 additions & 1 deletion pkg/goup/goup.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
InsecurePatterns string
OnlyReleases string
Timeout time.Duration
BasicAuth vcs.BasicAuthentifier
}

// Checker must be implemented to checkFile updates on go.mod file or module.
Expand All @@ -54,7 +55,7 @@ func newGoUp(conf Config, sets ...setter) *goUp {
log: make(chan Message),
}
httpClient = vcs.NewHTTPClient(conf.Timeout, conf.InsecurePatterns)
gitVCS = git.New(httpClient)
gitVCS = git.New(httpClient, conf.BasicAuth)
)
sets = append([]setter{
setGit(gitVCS),
Expand Down
Loading

0 comments on commit 9e9f704

Please sign in to comment.