Skip to content

Commit ff92b7a

Browse files
committed
feat: support auth for git+https repos
Use the credentials configured within repositories.yaml also for git+https urls. To avoid reloading the config, move repository-related code into a separate package.
1 parent afdbaa8 commit ff92b7a

17 files changed

+796
-447
lines changed

cmd/khelm/common.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/mgoltzsche/khelm/v2/pkg/config"
1111
"github.com/mgoltzsche/khelm/v2/pkg/helm"
12+
"github.com/mgoltzsche/khelm/v2/pkg/repositories"
1213
"sigs.k8s.io/kustomize/kyaml/yaml"
1314
)
1415

@@ -23,7 +24,7 @@ func render(h *helm.Helm, req *config.ChartConfig) ([]*yaml.RNode, error) {
2324
}()
2425

2526
rendered, err := h.Render(ctx, req)
26-
if helm.IsUntrustedRepository(err) {
27+
if repositories.IsUntrustedRepository(err) {
2728
log.Printf("HINT: access to untrusted repositories can be enabled using env var %s=true or option --%s", envTrustAnyRepo, flagTrustAnyRepo)
2829
}
2930
return rendered, err

example/git-getter/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
apiVersion: v2
22
description: example chart using a git url as dependency
3-
name: git-getter-example-chart
3+
name: git-getter
44
version: 0.1.0
55
dependencies:
66
- name: cert-manager
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
dependencies:
2+
- name: intermediate-chart
3+
repository: file://../localref/intermediate-chart
4+
version: 0.1.1
5+
- name: git-getter
6+
repository: git+https://github.com/mgoltzsche/khelm@example?ref=afdbaa8e7068b7d7dd71fc35dcb022fc1f12ea62
7+
version: 0.1.0
8+
digest: sha256:83407caeeb8ff5cf80c07d046efe3ee2961dda5d9dce64ee95d162a124263dfd
9+
generated: "2022-10-17T01:20:17.281584028Z"
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: v2
2+
description: Chart that refers another one that has dependencies itself
3+
name: myumbrella-chart
4+
version: 0.2.0
5+
dependencies:
6+
- name: intermediate-chart
7+
version: "~0.1.0"
8+
repository: "file://../localref/intermediate-chart"
9+
- name: "git-getter"
10+
version: "x.x.x"
11+
repository: "git+https://github.com/mgoltzsche/khelm@example?ref=afdbaa8e7068b7d7dd71fc35dcb022fc1f12ea62"
12+
#afdbaa8e7068b7d7dd71fc35dcb022fc1f12ea62
13+
#3857e76b956c4652d3cacc2fd71e7dcff5843302
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: khelm.mgoltzsche.github.com/v2
2+
kind: ChartRenderer
3+
metadata:
4+
name: mychart
5+
namespace: myotherns
6+
chart: .

pkg/getter/git/checkout.go

+54-49
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,83 @@ package git
22

33
import (
44
"context"
5+
"encoding/hex"
56
"fmt"
7+
"log"
8+
"strings"
69

710
"github.com/go-git/go-git/v5"
811
"github.com/go-git/go-git/v5/plumbing"
12+
"github.com/go-git/go-git/v5/plumbing/transport/http"
13+
"helm.sh/helm/v3/pkg/repo"
914
)
1015

11-
func gitCheckoutImpl(ctx context.Context, repoURL, ref, destDir string) error {
12-
/*err := runCmds(destDir, [][]string{
13-
{"git", "init", "--quiet"},
14-
{"git", "remote", "add", "origin", repoURL},
15-
16-
{"git", "config", "core.sparseCheckout", "true"},
17-
{"git", "sparse-checkout", "set", path},
18-
{"git", "pull", "--quiet", "--depth", "1", "origin", ref},
19-
20-
//{"git", "fetch", "--quiet", "--tags", "origin"},
21-
//{"git", "checkout", "--quiet", ref},
22-
})*/
23-
r, err := git.PlainCloneContext(ctx, destDir, false, &git.CloneOptions{
24-
URL: repoURL,
25-
// TODO: Auth: ...
26-
RemoteName: "origin",
27-
SingleBranch: true,
28-
Depth: 1,
29-
NoCheckout: true,
30-
})
16+
func gitCheckoutImpl(ctx context.Context, repoURL, ref string, repo *repo.Entry, destDir string) error {
17+
cloneOpts := git.CloneOptions{
18+
URL: repoURL,
19+
RemoteName: "origin",
20+
NoCheckout: true,
21+
}
22+
isCommitRef := isCommitSHA(ref)
23+
if !isCommitRef { // cannot find commit in other branch when checking out single branch
24+
cloneOpts.SingleBranch = true
25+
cloneOpts.Depth = 1
26+
}
27+
scheme := strings.SplitN(repoURL, ":", 2)[0]
28+
switch scheme {
29+
case "https":
30+
cloneOpts.Auth = &http.BasicAuth{
31+
Username: repo.Username,
32+
Password: repo.Password,
33+
}
34+
default:
35+
if repo.Username != "" || repo.Password != "" {
36+
log.Printf("WARNING: ignoring auth config for %s since authentication is not supported for url scheme %q", repoURL, scheme)
37+
}
38+
}
39+
r, err := git.PlainCloneContext(ctx, destDir, false, &cloneOpts)
3140
if err != nil {
32-
return err
41+
return fmt.Errorf("git clone: %w", err)
3342
}
3443
tree, err := r.Worktree()
3544
if err != nil {
3645
return fmt.Errorf("git worktree: %w", err)
3746
}
3847
// TODO: support sparse checkout, see https://github.com/go-git/go-git/issues/90
48+
refType := "without ref"
3949
opts := git.CheckoutOptions{}
4050
if ref != "" {
41-
opts.Branch = plumbing.ReferenceName("refs/tags/" + ref)
51+
if isCommitRef {
52+
opts.Hash = plumbing.NewHash(ref)
53+
refType = fmt.Sprintf("commit %s", ref)
54+
} else {
55+
opts.Branch = plumbing.ReferenceName(fmt.Sprintf("refs/tags/%s", ref))
56+
refType = fmt.Sprintf("tag %s", ref)
57+
}
4258
}
4359
err = tree.Checkout(&opts)
4460
if err != nil {
45-
return err
61+
return fmt.Errorf("git checkout %s: %w", refType, err)
4662
}
47-
return nil
48-
}
63+
/*err := runCmds(destDir, [][]string{
64+
{"git", "init", "--quiet"},
65+
{"git", "remote", "add", "origin", repoURL},
4966
50-
/*
51-
func runCmds(dir string, cmds [][]string) error {
52-
for _, c := range cmds {
53-
err := runCmd(dir, c[0], c[1:]...)
54-
if err != nil {
55-
return err
56-
}
57-
}
67+
{"git", "config", "core.sparseCheckout", "true"},
68+
{"git", "sparse-checkout", "set", path},
69+
{"git", "pull", "--quiet", "--depth", "1", "origin", ref},
70+
71+
//{"git", "fetch", "--quiet", "--tags", "origin"},
72+
//{"git", "checkout", "--quiet", ref},
73+
})*/
5874
return nil
5975
}
6076

61-
func runCmd(dir, cmd string, args ...string) error {
62-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
63-
defer cancel()
64-
c := exec.CommandContext(ctx, cmd, args...)
65-
var stderr bytes.Buffer
66-
c.Stderr = &stderr
67-
c.Dir = dir
68-
err := c.Run()
69-
if err != nil {
70-
msg := strings.TrimSpace(stderr.String())
71-
if msg == "" {
72-
msg = err.Error()
77+
func isCommitSHA(s string) bool {
78+
if len(s) == 40 {
79+
if _, err := hex.DecodeString(s); err == nil {
80+
return true
7381
}
74-
cmds := append([]string{cmd}, args...)
75-
return fmt.Errorf("%s: %s", strings.Join(cmds, " "), msg)
7682
}
77-
return err
83+
return false
7884
}
79-
*/

pkg/getter/git/gitgetter.go

+21-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path/filepath"
1313
"strings"
1414

15+
"github.com/mgoltzsche/khelm/v2/pkg/repositories"
1516
"helm.sh/helm/v3/pkg/chart"
1617
"helm.sh/helm/v3/pkg/cli"
1718
helmgetter "helm.sh/helm/v3/pkg/getter"
@@ -23,16 +24,24 @@ var gitCheckout = gitCheckoutImpl
2324

2425
type HelmPackageFunc func(ctx context.Context, path, repoDir string) (string, error)
2526

26-
func New(settings *cli.EnvSettings, packageFn HelmPackageFunc) helmgetter.Constructor {
27+
type RepositoriesFunc func() (repositories.Interface, error)
28+
29+
func New(settings *cli.EnvSettings, reposFn RepositoriesFunc, packageFn HelmPackageFunc) helmgetter.Constructor {
2730
return func(o ...helmgetter.Option) (helmgetter.Getter, error) {
31+
repos, err := reposFn()
32+
if err != nil {
33+
return nil, err
34+
}
2835
return &gitIndexGetter{
2936
settings: settings,
37+
repos: repos,
3038
packageFn: packageFn,
3139
}, nil
3240
}
3341
}
3442

3543
type gitIndexGetter struct {
44+
repos repositories.Interface
3645
settings *cli.EnvSettings
3746
Getters helmgetter.Providers
3847
packageFn HelmPackageFunc
@@ -52,7 +61,7 @@ func (g *gitIndexGetter) Get(location string, options ...helmgetter.Option) (*by
5261
if isRepoIndex {
5362
// Generate repo index from directory
5463
ref = ref.Dir()
55-
repoDir, err := download(ctx, ref, g.settings.RepositoryCache)
64+
repoDir, err := download(ctx, ref, g.settings.RepositoryCache, g.repos)
5665
if err != nil {
5766
return nil, err
5867
}
@@ -67,8 +76,9 @@ func (g *gitIndexGetter) Get(location string, options ...helmgetter.Option) (*by
6776
}
6877
} else {
6978
// Build and package chart
70-
chartPath := filepath.FromSlash(strings.TrimSuffix(ref.Path, ".tgz"))
71-
repoDir, err := download(ctx, ref, g.settings.RepositoryCache)
79+
ref.Path = strings.TrimSuffix(ref.Path, ".tgz")
80+
chartPath := filepath.FromSlash(ref.Path)
81+
repoDir, err := download(ctx, ref, g.settings.RepositoryCache, g.repos)
7282
if err != nil {
7383
return nil, err
7484
}
@@ -136,15 +146,19 @@ func generateRepoIndex(dir, cacheDir string, u *gitURL) (*repo.IndexFile, error)
136146
return idx, nil
137147
}
138148

139-
func download(ctx context.Context, ref *gitURL, cacheDir string) (string, error) {
149+
func download(ctx context.Context, ref *gitURL, cacheDir string, repos repositories.Interface) (string, error) {
140150
repoRef := *ref
141151
repoRef.Path = ""
142152
cacheKey := fmt.Sprintf("sha256-%x", sha256.Sum256([]byte(repoRef.String())))
143153
cacheDir = filepath.Join(cacheDir, "git")
144154
destDir := filepath.Join(cacheDir, cacheKey)
145155

146156
if _, e := os.Stat(destDir); os.IsNotExist(e) {
147-
err := os.MkdirAll(cacheDir, 0755)
157+
auth, _, err := repos.Get("git+" + ref.String())
158+
if err != nil {
159+
return "", err
160+
}
161+
err = os.MkdirAll(cacheDir, 0755)
148162
if err != nil {
149163
return "", err
150164
}
@@ -155,7 +169,7 @@ func download(ctx context.Context, ref *gitURL, cacheDir string) (string, error)
155169
defer os.RemoveAll(tmpDir)
156170

157171
tmpRepoDir := tmpDir
158-
err = gitCheckout(ctx, ref.Repo, ref.Ref, tmpRepoDir)
172+
err = gitCheckout(ctx, ref.Repo, ref.Ref, auth, tmpRepoDir)
159173
if err != nil {
160174
return "", err
161175
}

pkg/getter/git/gitgetter_test.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"strings"
88
"testing"
99

10+
"github.com/mgoltzsche/khelm/v2/pkg/repositories"
1011
"github.com/stretchr/testify/require"
1112
"helm.sh/helm/v3/pkg/chart"
1213
"helm.sh/helm/v3/pkg/cli"
14+
"helm.sh/helm/v3/pkg/getter"
1315
"helm.sh/helm/v3/pkg/repo"
1416
"sigs.k8s.io/yaml"
1517
)
@@ -18,7 +20,7 @@ func TestGitGetter(t *testing.T) {
1820
tmpDir, err := os.MkdirTemp("", "khelm-git-getter-test-")
1921
require.NoError(t, err)
2022
defer os.RemoveAll(tmpDir)
21-
gitCheckout = func(_ context.Context, repoURL, ref, destDir string) error {
23+
gitCheckout = func(_ context.Context, repoURL, ref string, auth *repo.Entry, destDir string) error {
2224
err := os.MkdirAll(filepath.Join(destDir, "mypath", "fakechart"), 0755)
2325
require.NoError(t, err)
2426
err = os.WriteFile(filepath.Join(destDir, "mypath", "fakechart", "Chart.yaml"), []byte(`
@@ -38,7 +40,11 @@ version: 0.1.0`), 0600)
3840
require.NoError(t, err)
3941
return file, nil
4042
}
41-
testee := New(settings, fakePackageFn)
43+
reposFn := func() (repositories.Interface, error) {
44+
trust := true
45+
return repositories.New(*settings, getter.All(settings), &trust)
46+
}
47+
testee := New(settings, reposFn, fakePackageFn)
4248
getter, err := testee()
4349
require.NoError(t, err)
4450

pkg/helm/helm.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"os"
55
"path/filepath"
66

7+
"github.com/mgoltzsche/khelm/v2/pkg/repositories"
78
"helm.sh/helm/v3/pkg/cli"
89
"helm.sh/helm/v3/pkg/getter"
910
"helm.sh/helm/v3/pkg/helmpath"
@@ -14,6 +15,7 @@ type Helm struct {
1415
TrustAnyRepository *bool
1516
Settings cli.EnvSettings
1617
Getters getter.Providers
18+
repos repositories.Interface
1719
}
1820

1921
// NewHelm creates a new helm environment
@@ -24,6 +26,18 @@ func NewHelm() *Helm {
2426
settings.RepositoryConfig = filepath.Join(helmHome, "repository", "repositories.yaml")
2527
}
2628
h := &Helm{Settings: *settings}
27-
h.Getters = getters(settings, &h.TrustAnyRepository)
29+
h.Getters = getters(settings, h.repositories)
2830
return h
2931
}
32+
33+
func (h *Helm) repositories() (repositories.Interface, error) {
34+
if h.repos != nil {
35+
return h.repos, nil
36+
}
37+
repos, err := repositories.New(h.Settings, h.Getters, h.TrustAnyRepository)
38+
if err != nil {
39+
return nil, err
40+
}
41+
h.repos = repos
42+
return repos, nil
43+
}

0 commit comments

Comments
 (0)