Skip to content

Commit

Permalink
fix: wire up git getter within cli
Browse files Browse the repository at this point in the history
Also, disable git getter by default and add IsUntrustedRepository method
back to helm package for backward-compatibility and convenience.
  • Loading branch information
mgoltzsche committed Oct 30, 2022
1 parent 32f6afc commit 9de5d90
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 55 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ It exposes a `Helm` struct that provides a `Render()` function that returns the
| `outputPathMapping[].selectors[].kind` | | Selects resources by kind. |
| `outputPathMapping[].selectors[].namespace` | | Selects resources by namespace. |
| `outputPathMapping[].selectors[].name` | | Selects resources by name. |
| | `--output-replace` | If enabled replace the output directory or file (CLI-only). |
| | `--trust-any-repo` | If enabled repositories that are not registered within `repositories.yaml` can be used as well (env var `KHELM_TRUST_ANY_REPO`). Within the kpt function this behaviour can be disabled by mounting `/helm/repository/repositories.yaml` or disabling network access. |
| | `--output-replace` | If enabled, replace the output directory or file (CLI-only). |
| | `--trust-any-repo` | If enabled, repositories that are not registered within `repositories.yaml` can be used as well (env var `KHELM_TRUST_ANY_REPO`). Within the kpt function this behaviour can be disabled by mounting `/helm/repository/repositories.yaml` or disabling network access. |
| | `--enable-git-getter` | If enabled, support helm repository URLs with the git+https scheme (env var `KHELM_ENABLE_GIT_GETTER`). |
| `debug` | `--debug` | Enables debug log and provides a stack trace on error. |

### Repository configuration
Expand All @@ -276,6 +277,8 @@ The following example points to an old version of cert-manager using a git URL:
git+https://github.com/cert-manager/cert-manager@deploy/charts?ref=v0.6.2
```

To enable this feature, set the `--enable-git-getter` option or the corresponding environment variable: `KHELM_ENABLE_GIT_GETTER=true`.

This feature is meant to be compatible with Helm's [helm-git](https://github.com/aslafy-z/helm-git#usage) plugin (but is reimplemented in Go).
However currently khelm does not support `sparse` git checkouts (due to [lack of support in go-git](https://github.com/go-git/go-git/issues/90)).

Expand Down
3 changes: 1 addition & 2 deletions cmd/khelm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/mgoltzsche/khelm/v2/pkg/config"
"github.com/mgoltzsche/khelm/v2/pkg/helm"
"github.com/mgoltzsche/khelm/v2/pkg/repositories"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

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

rendered, err := h.Render(ctx, req)
if repositories.IsUntrustedRepository(err) {
if helm.IsUntrustedRepository(err) {
log.Printf("HINT: access to untrusted repositories can be enabled using env var %s=true or option --%s", envTrustAnyRepo, flagTrustAnyRepo)
}
return rendered, err
Expand Down
30 changes: 25 additions & 5 deletions cmd/khelm/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"context"
"fmt"
"io"
"log"
Expand All @@ -10,13 +11,16 @@ import (
"strconv"
"strings"

"github.com/mgoltzsche/khelm/v2/pkg/getter/git"
"github.com/mgoltzsche/khelm/v2/pkg/helm"
"github.com/spf13/cobra"
helmgetter "helm.sh/helm/v3/pkg/getter"
)

const (
envKustomizePluginConfig = "KUSTOMIZE_PLUGIN_CONFIG_STRING"
envKustomizePluginConfigRoot = "KUSTOMIZE_PLUGIN_CONFIG_ROOT"
envEnableGitGetter = "KHELM_ENABLE_GIT_GETTER"
envTrustAnyRepo = "KHELM_TRUST_ANY_REPO"
envDebug = "KHELM_DEBUG"
envHelmDebug = "HELM_DEBUG"
Expand All @@ -36,11 +40,15 @@ func Execute(reader io.Reader, writer io.Writer) error {
helmDebug, _ := strconv.ParseBool(os.Getenv(envHelmDebug))
h := helm.NewHelm()
debug = debug || helmDebug
enableGitGetter := false
h.Settings.Debug = debug
if trustAnyRepo, ok := os.LookupEnv(envTrustAnyRepo); ok {
trust, _ := strconv.ParseBool(trustAnyRepo)
h.TrustAnyRepository = &trust
}
if gitSupportStr, ok := os.LookupEnv(envEnableGitGetter); ok {
enableGitGetter, _ = strconv.ParseBool(gitSupportStr)
}

// Run as kustomize plugin (if kustomize-specific env var provided)
if kustomizeGenCfgYAML, isKustomizePlugin := os.LookupEnv(envKustomizePluginConfig); isKustomizePlugin {
Expand All @@ -50,12 +58,16 @@ func Execute(reader io.Reader, writer io.Writer) error {
return err
}

logVersionPreRun := func(_ *cobra.Command, _ []string) {
preRun := func(_ *cobra.Command, _ []string) {
logVersion()
if enableGitGetter {
addGitGetterSupport(h)
}
}
rootCmd := &cobra.Command{
PreRun: logVersionPreRun,
PersistentPreRun: preRun,
}
rootCmd.PersistentFlags().BoolVar(&enableGitGetter, "enable-git-getter", enableGitGetter, fmt.Sprintf("enable git+https helm repository URL scheme support (%s)", envEnableGitGetter))
errBuf := bytes.Buffer{}

if filepath.Base(os.Args[0]) == "khelmfn" {
Expand All @@ -65,8 +77,7 @@ func Execute(reader io.Reader, writer io.Writer) error {
rootCmd.SetOut(writer)
rootCmd.SetErr(&errBuf)
rootCmd.PersistentFlags().BoolVar(&debug, "debug", debug, fmt.Sprintf("enable debug log (%s)", envDebug))
rootCmd.PreRun = func(_ *cobra.Command, _ []string) {
logVersion()
rootCmd.PreRun = func(cmd *cobra.Command, args []string) {
fmt.Printf("# Reading kpt function input from stdin (use `%s template` to run without kpt)\n", os.Args[0])
}
}
Expand All @@ -88,7 +99,7 @@ In addition to helm's templating capabilities khelm allows to:
templateCmd := templateCommand(h, writer)
templateCmd.SetOut(writer)
templateCmd.SetErr(&errBuf)
templateCmd.PreRun = logVersionPreRun
templateCmd.PreRun = preRun
rootCmd.AddCommand(templateCmd)

// Run command
Expand All @@ -110,3 +121,12 @@ func logVersion() {
func versionInfo() string {
return fmt.Sprintf("%s (helm %s)", khelmVersion, helmVersion)
}

func addGitGetterSupport(h *helm.Helm) {
h.Getters = append(h.Getters, helmgetter.Provider{
Schemes: git.Schemes,
New: git.New(&h.Settings, h.Repositories, func(ctx context.Context, chartDir, repoDir string) (string, error) {
return h.Package(ctx, chartDir, repoDir, chartDir)
}),
})
}
14 changes: 14 additions & 0 deletions cmd/khelm/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ func TestTemplateCommand(t *testing.T) {
[]string{filepath.Join(exampleDir, "chart-hooks"), "--no-hooks"},
1, "myvalue",
},
{
"git-dependency",
[]string{filepath.Join(exampleDir, "git-dependency"), "--enable-git-getter", "--trust-any-repo"},
24, "ca-sync",
},
{
"local-chart-with-transitive-remote-and-git-dependencies",
[]string{filepath.Join(exampleDir, "localrefref-with-git"), "--enable-git-getter", "--trust-any-repo"},
33, "admission.certmanager.k8s.io",
},
} {
t.Run(c.name, func(t *testing.T) {
var out bytes.Buffer
Expand Down Expand Up @@ -128,6 +138,10 @@ func TestTemplateCommandError(t *testing.T) {
"reject cluster scoped resources",
[]string{"cert-manager", "--repo=https://charts.jetstack.io", "--namespaced-only"},
},
{
"reject git urls by default",
[]string{"git-dependency", "--enable-git-getter=false"},
},
} {
t.Run(c.name, func(t *testing.T) {
os.Args = append([]string{"testee", "template"}, c.args...)
Expand Down
17 changes: 13 additions & 4 deletions e2e/cli-tests.bats
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ teardown() {
}

@test "CLI should accept git url as helm repository" {
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" "$IMAGE" template cert-manager \
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" \
-e KHELM_ENABLE_GIT_GETTER=true \
"$IMAGE" template cert-manager \
--repo git+https://github.com/cert-manager/cert-manager@deploy/charts?ref=v0.6.2 \
--output /out/manifest.yaml \
--debug
Expand All @@ -71,14 +73,18 @@ teardown() {

@test "CLI should cache git repository" {
mkdir $OUT_DIR/cache
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$OUT_DIR/cache:/helm/cache" "$IMAGE" template cert-manager \
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$OUT_DIR/cache:/helm/cache" \
-e KHELM_ENABLE_GIT_GETTER=true \
"$IMAGE" template cert-manager \
--repo git+https://github.com/cert-manager/cert-manager@deploy/charts?ref=v0.6.2 \
--output /out/manifest.yaml \
--debug
[ -f "$OUT_DIR/manifest.yaml" ]
grep -q ca-sync "$OUT_DIR/manifest.yaml"
rm -f "$OUT_DIR/manifest.yaml"
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$OUT_DIR/cache:/helm/cache" --network=none "$IMAGE" template cert-manager \
docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -v "$OUT_DIR/cache:/helm/cache" \
-e KHELM_ENABLE_GIT_GETTER=true \
--network=none "$IMAGE" template cert-manager \
--repo git+https://github.com/cert-manager/cert-manager@deploy/charts?ref=v0.6.2 \
--output /out/manifest.yaml \
--debug
Expand All @@ -87,7 +93,10 @@ teardown() {
}

@test "CLI should reject git repository when not in repositories.yaml and trust-any disabled" {
run -1 docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" -e KHELM_TRUST_ANY_REPO=false "$IMAGE" template cert-manager \
run -1 docker run --rm -u $(id -u):$(id -g) -v "$OUT_DIR:/out" \
-e KHELM_ENABLE_GIT_GETTER=true \
-e KHELM_TRUST_ANY_REPO=false \
"$IMAGE" template cert-manager \
--repo git+https://github.com/cert-manager/cert-manager@deploy/charts?ref=v0.6.2 \
--output /out/manifest.yaml \
--debug
Expand Down
5 changes: 4 additions & 1 deletion pkg/getter/git/gitgetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
helmyaml "sigs.k8s.io/yaml"
)

var gitCheckout = gitCheckoutImpl
var (
Schemes = []string{"git+https", "git+ssh"}
gitCheckout = gitCheckoutImpl
)

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

Expand Down
10 changes: 6 additions & 4 deletions pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"helm.sh/helm/v3/pkg/helmpath"
)

func IsUntrustedRepository(err error) bool {
return repositories.IsUntrustedRepository(err)
}

// Helm maintains the helm environment state
type Helm struct {
TrustAnyRepository *bool
Expand All @@ -25,12 +29,10 @@ func NewHelm() *Helm {
// Fallback for old helm env var
settings.RepositoryConfig = filepath.Join(helmHome, "repository", "repositories.yaml")
}
h := &Helm{Settings: *settings}
h.Getters = getters(settings, h.repositories)
return h
return &Helm{Settings: *settings, Getters: getter.All(settings)}
}

func (h *Helm) repositories() (repositories.Interface, error) {
func (h *Helm) Repositories() (repositories.Interface, error) {
if h.repos != nil {
return h.repos, nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/helm/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (h *Helm) loadChart(ctx context.Context, cfg *config.ChartConfig) (*chart.C
fileExists := err == nil
if cfg.Repository == "" {
if fileExists {
repos, err := h.repositories()
repos, err := h.Repositories()
if err != nil {
return nil, err
}
Expand All @@ -46,7 +46,7 @@ func (h *Helm) loadChart(ctx context.Context, cfg *config.ChartConfig) (*chart.C

func (h *Helm) loadRemoteChart(ctx context.Context, cfg *config.ChartConfig) (*chart.Chart, error) {
repoURLs := map[string]struct{}{cfg.Repository: {}}
repos, err := h.repositories()
repos, err := h.Repositories()
if err != nil {
return nil, err
}
Expand Down
26 changes: 24 additions & 2 deletions pkg/helm/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,37 @@ import (
"helm.sh/helm/v3/pkg/getter"
)

func packageHelmChart(ctx context.Context, cfg *config.ChartConfig, destDir string, repos repositories.Interface, settings cli.EnvSettings, getters getter.Providers) (string, error) {
type PackageOptions struct {
ChartDir string
BaseDir string
DestDir string
}

// Package builds and packages a local Helm chart.
// Returns the tgz file path.
func (h *Helm) Package(ctx context.Context, chartDir, baseDir, destDir string) (string, error) {
repos, err := h.Repositories()
if err != nil {
return "", err
}
cfg := config.ChartConfig{
LoaderConfig: config.LoaderConfig{
Chart: chartDir,
},
BaseDir: baseDir,
}
return packageHelmChart(ctx, &cfg, repos, h.Settings, h.Getters)
}

func packageHelmChart(ctx context.Context, cfg *config.ChartConfig, repos repositories.Interface, settings cli.EnvSettings, getters getter.Providers) (string, error) {
// TODO: add unit test (there is an e2e/cli test for this though)
_, err := buildAndLoadLocalChart(ctx, cfg, repos, settings, getters)
if err != nil {
return "", err
}
// See https://github.com/helm/helm/blob/v3.10.0/cmd/helm/package.go#L104
client := action.NewPackage()
client.Destination = destDir
client.Destination = cfg.BaseDir
chartPath := absPath(cfg.Chart, cfg.BaseDir)
tgzFile, err := client.Run(chartPath, map[string]interface{}{})
if err != nil {
Expand Down
31 changes: 0 additions & 31 deletions pkg/helm/providers.go

This file was deleted.

2 changes: 0 additions & 2 deletions pkg/helm/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ func TestRender(t *testing.T) {
"chart-hooks-test",
}},
{"chart-hooks-disabled", "example/chart-hooks-disabled/generator.yaml", []string{"default"}, " key: myvalue", []string{"chart-hooks-disabled-myconfig"}},
{"git-dependency", "example/git-dependency/generator.yaml", []string{"cert-manager", "kube-system"}, "ca-sync", nil},
{"local-chart-with-transitive-remote-and-git-dependencies", "example/localrefref-with-git/generator.yaml", []string{"kube-system", "myotherns"}, "admission.certmanager.k8s.io", nil},
} {
t.Run(c.name, func(t *testing.T) {
for _, cached := range []string{"", "cached "} {
Expand Down

0 comments on commit 9de5d90

Please sign in to comment.