add support for helm authentication when storing charts

This commit is contained in:
Josh Wolf
2022-01-24 16:31:03 -07:00
parent a1be863812
commit 320a4af36a
7 changed files with 105 additions and 91 deletions

View File

@@ -2,6 +2,7 @@ package cli
import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
"github.com/rancherfederal/hauler/cmd/hauler/cli/store"
)
@@ -263,7 +264,10 @@ func addStoreAddImage() *cobra.Command {
}
func addStoreAddChart() *cobra.Command {
o := &store.AddChartOpts{RootOpts: rootStoreOpts}
o := &store.AddChartOpts{
RootOpts: rootStoreOpts,
ChartOpts: &action.ChartPathOptions{},
}
cmd := &cobra.Command{
Use: "chart",

View File

@@ -2,10 +2,10 @@ package store
import (
"context"
"os"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
"github.com/rancherfederal/ocil/pkg/artifacts/file"
"github.com/rancherfederal/ocil/pkg/artifacts/image"
@@ -96,43 +96,53 @@ func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image) error {
type AddChartOpts struct {
*RootOpts
Version string
RepoURL string
// TODO: Support helm auth
ChartOpts *action.ChartPathOptions
}
func (o *AddChartOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVarP(&o.RepoURL, "repo", "r", "", "Chart repository URL")
f.StringVar(&o.Version, "version", "", "(Optional) Version of the chart to download, defaults to latest if not specified")
f.StringVar(&o.ChartOpts.RepoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&o.ChartOpts.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used")
f.BoolVar(&o.ChartOpts.Verify, "verify", false, "verify the package before using it")
f.StringVar(&o.ChartOpts.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&o.ChartOpts.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&o.ChartOpts.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.ChartOpts.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&o.ChartOpts.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.StringVar(&o.ChartOpts.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}
func AddChartCmd(ctx context.Context, o *AddChartOpts, s *store.Layout, chartName string) error {
path := ""
if _, err := os.Stat(chartName); err == nil {
path = chartName
}
// TODO: Reduce duplicates between api chart and upstream helm opts
cfg := v1alpha1.Chart{
Name: chartName,
RepoURL: o.RepoURL,
Version: o.Version,
Path: path,
RepoURL: o.ChartOpts.RepoURL,
Version: o.ChartOpts.Version,
}
return storeChart(ctx, s, cfg)
return storeChart(ctx, s, cfg, o.ChartOpts)
}
func storeChart(ctx context.Context, s *store.Layout, cfg v1alpha1.Chart) error {
func storeChart(ctx context.Context, s *store.Layout, cfg v1alpha1.Chart, opts *action.ChartPathOptions) error {
l := log.FromContext(ctx)
chrt, err := chart.NewChart(cfg)
// TODO: This shouldn't be necessary
opts.RepoURL = cfg.RepoURL
opts.Version = cfg.Version
chrt, err := chart.NewChart(cfg.Name, opts)
if err != nil {
return err
}
ref, err := reference.NewTagged(chrt.Name, chrt.Version)
c, err := chrt.Load()
if err != nil {
return err
}
ref, err := reference.NewTagged(c.Name(), c.Metadata.Version)
if err != nil {
return err
}

View File

@@ -8,6 +8,7 @@ import (
"os"
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/rancherfederal/ocil/pkg/store"
@@ -106,7 +107,8 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error {
}
for _, ch := range cfg.Spec.Charts {
err := storeChart(ctx, s, ch)
// TODO: Provide a way to configure syncs
err := storeChart(ctx, s, ch, &action.ChartPathOptions{})
if err != nil {
return err
}
@@ -134,7 +136,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error {
}
for _, cfg := range cfg.Spec.Charts {
tc, err := tchart.NewThickChart(cfg)
tc, err := tchart.NewThickChart(cfg, &action.ChartPathOptions{})
if err != nil {
return err
}

View File

@@ -24,7 +24,6 @@ type Chart struct {
Name string `json:"name,omitempty"`
RepoURL string `json:"repoURL,omitempty"`
Version string `json:"version,omitempty"`
Path string `json:"path,omitempty"`
}
type ThickCharts struct {

View File

@@ -3,6 +3,7 @@ package chart
import (
"github.com/rancherfederal/ocil/pkg/artifacts"
"github.com/rancherfederal/ocil/pkg/artifacts/image"
"helm.sh/helm/v3/pkg/action"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
"github.com/rancherfederal/hauler/pkg/content/chart"
@@ -19,8 +20,8 @@ type tchart struct {
contents map[string]artifacts.OCI
}
func NewThickChart(cfg v1alpha1.ThickChart) (artifacts.OCICollection, error) {
o, err := chart.NewChart(cfg.Chart)
func NewThickChart(cfg v1alpha1.ThickChart, opts *action.ChartPathOptions) (artifacts.OCICollection, error) {
o, err := chart.NewChart(cfg.Chart.Name, opts)
if err != nil {
return nil, err
}

View File

@@ -23,8 +23,6 @@ import (
"github.com/rancherfederal/ocil/pkg/layer"
"github.com/rancherfederal/ocil/pkg/consts"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
)
var _ artifacts.OCI = (*Chart)(nil)
@@ -32,65 +30,45 @@ var _ artifacts.OCI = (*Chart)(nil)
// Chart implements the OCI interface for Chart API objects. API spec values are
// stored into the Repo, Name, and Version fields.
type Chart struct {
Repo string
Name string
Version string
path string
annotations map[string]string
}
// NewChart is a helper method that returns NewLocalChart or NewRemoteChart depending on v1alpha1.Chart contents
func NewChart(cfg v1alpha1.Chart) (*Chart, error) {
var (
ch *Chart
err error
)
if cfg.Path != "" {
ch, err = NewLocalChart(cfg.Path)
} else {
ch, err = NewRemoteChart(cfg.Name, cfg.RepoURL, cfg.Version)
}
return ch, err
}
func NewLocalChart(path string) (*Chart, error) {
c, err := loader.Load(path)
if err != nil {
return nil, err
}
return &Chart{
Name: c.Name(),
Version: c.Metadata.Version,
path: path,
}, nil
}
func NewRemoteChart(name, repo, version string) (*Chart, error) {
func NewChart(name string, opts *action.ChartPathOptions) (*Chart, error) {
cpo := action.ChartPathOptions{
RepoURL: repo,
Version: version,
RepoURL: opts.RepoURL,
Version: opts.Version,
CaFile: opts.CaFile,
CertFile: opts.CertFile,
KeyFile: opts.KeyFile,
InsecureSkipTLSverify: opts.InsecureSkipTLSverify,
Keyring: opts.Keyring,
Password: opts.Password,
PassCredentialsAll: opts.PassCredentialsAll,
Username: opts.Username,
Verify: opts.Verify,
}
cp, err := cpo.LocateChart(name, cli.New())
if err != nil {
return nil, err
}
c, err := loader.Load(cp)
chartPath, err := cpo.LocateChart(name, cli.New())
if err != nil {
return nil, err
}
// c, err := loader.Loader(chartPath)
// if err != nil {
// return nil, err
// }
//
// ch, err := c.Load()
// if err != nil {
// return nil, err
// }
//
return &Chart{
Repo: repo,
Name: c.Name(),
Version: c.Metadata.Version,
path: cp,
}, nil
path: chartPath,
}, err
}
func (h *Chart) MediaType() string {

View File

@@ -2,13 +2,13 @@ package chart_test
import (
"os"
"path/filepath"
"reflect"
"testing"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/mholt/archiver/v3"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"helm.sh/helm/v3/pkg/action"
"github.com/rancherfederal/ocil/pkg/consts"
@@ -19,7 +19,7 @@ var (
chartpath = "../../../testdata/podinfo-6.0.3.tgz"
)
func TestNewLocalChart(t *testing.T) {
func TestNewChart(t *testing.T) {
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
t.Fatal(err)
@@ -30,20 +30,9 @@ func TestNewLocalChart(t *testing.T) {
t.Fatal(err)
}
want := v1.Descriptor{
MediaType: consts.ChartLayerMediaType,
Size: 13524,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "e30b95a08787de69ffdad3c232d65cfb131b5b50c6fd44295f48a078fceaa44e",
},
Annotations: map[string]string{
ocispec.AnnotationTitle: filepath.Base(chartpath),
},
}
type args struct {
path string
name string
opts *action.ChartPathOptions
}
tests := []struct {
name string
@@ -54,9 +43,20 @@ func TestNewLocalChart(t *testing.T) {
{
name: "should create from a chart archive",
args: args{
path: chartpath,
name: chartpath,
opts: &action.ChartPathOptions{},
},
want: v1.Descriptor{
MediaType: consts.ChartLayerMediaType,
Size: 13524,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "e30b95a08787de69ffdad3c232d65cfb131b5b50c6fd44295f48a078fceaa44e",
},
Annotations: map[string]string{
ocispec.AnnotationTitle: "podinfo-6.0.3.tgz",
},
},
want: want,
wantErr: false,
},
// TODO: This isn't matching digests b/c of file timestamps not being respected
@@ -68,10 +68,30 @@ func TestNewLocalChart(t *testing.T) {
// want: want,
// wantErr: false,
// },
{
// TODO: Use a mock helm server
name: "should fetch a remote chart",
args: args{
name: "ingress-nginx",
opts: &action.ChartPathOptions{RepoURL: "https://kubernetes.github.io/ingress-nginx", Version: "4.0.16"},
},
want: v1.Descriptor{
MediaType: consts.ChartLayerMediaType,
Size: 38591,
Digest: v1.Hash{
Algorithm: "sha256",
Hex: "b0ea91f7febc6708ad9971871d2de6e8feb2072110c3add6dd7082d90753caa2",
},
Annotations: map[string]string{
ocispec.AnnotationTitle: "ingress-nginx-4.0.16.tgz",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := chart.NewLocalChart(tt.args.path)
got, err := chart.NewChart(tt.args.name, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("NewLocalChart() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -88,8 +108,8 @@ func TestNewLocalChart(t *testing.T) {
}
desc := m.Layers[0]
if !reflect.DeepEqual(desc, want) {
t.Errorf("%v | %v", desc, want)
if !reflect.DeepEqual(desc, tt.want) {
t.Errorf("got: %v\nwant: %v", desc, tt.want)
return
}
})