mirror of https://github.com/helm/helm
Merge pull request #9782 from bloodorangeio/hip-6
Implement changes proposed in HIP 6: OCI Supportpull/9707/head
commit
601bed2142
@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartHelp = `
|
||||
This command consists of multiple subcommands to work with the chart cache.
|
||||
|
||||
The subcommands can be used to push, pull, tag, list, or remove Helm charts.
|
||||
`
|
||||
|
||||
func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "chart",
|
||||
Short: "push, pull, tag, or remove Helm charts",
|
||||
Long: chartHelp,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
PersistentPreRunE: checkOCIFeatureGate(),
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newChartListCmd(cfg, out),
|
||||
newChartExportCmd(cfg, out),
|
||||
newChartPullCmd(cfg, out),
|
||||
newChartPushCmd(cfg, out),
|
||||
newChartRemoveCmd(cfg, out),
|
||||
newChartSaveCmd(cfg, out),
|
||||
)
|
||||
return cmd
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartExportDesc = `
|
||||
Export a chart stored in local registry cache.
|
||||
|
||||
This will create a new directory with the name of
|
||||
the chart, in a format that developers can modify
|
||||
and check into source control if desired.
|
||||
`
|
||||
|
||||
func newChartExportCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client := action.NewChartExport(cfg)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "export [ref]",
|
||||
Short: "export a chart to directory",
|
||||
Long: chartExportDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return client.Run(out, ref)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
|
||||
|
||||
return cmd
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartListDesc = `
|
||||
List all charts in the local registry cache.
|
||||
|
||||
Charts are sorted by ref name, alphabetically.
|
||||
`
|
||||
|
||||
func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
chartList := action.NewChartList(cfg)
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "list all saved charts",
|
||||
Long: chartListDesc,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return chartList.Run(out)
|
||||
},
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.UintVar(&chartList.ColumnWidth, "max-col-width", 60, "maximum column width for output table")
|
||||
return cmd
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartPullDesc = `
|
||||
Download a chart from a remote registry.
|
||||
|
||||
This will store the chart in the local registry cache to be used later.
|
||||
`
|
||||
|
||||
func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "pull [ref]",
|
||||
Short: "pull a chart from remote",
|
||||
Long: chartPullDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartPull(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartPushDesc = `
|
||||
Upload a chart to a remote registry.
|
||||
|
||||
Note: the ref must already exist in the local registry cache.
|
||||
|
||||
Must first run "helm chart save" or "helm chart pull".
|
||||
`
|
||||
|
||||
func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "push [ref]",
|
||||
Short: "push a chart to remote",
|
||||
Long: chartPushDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartPush(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartRemoveDesc = `
|
||||
Remove a chart from the local registry cache.
|
||||
|
||||
Note: the chart content will still exist in the cache,
|
||||
but it will no longer appear in "helm chart list".
|
||||
|
||||
To remove all unlinked content, please run "helm chart prune". (TODO)
|
||||
`
|
||||
|
||||
func newChartRemoveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "remove [ref]",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "remove a chart",
|
||||
Long: chartRemoveDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartRemove(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
const chartSaveDesc = `
|
||||
Store a copy of chart in local registry cache.
|
||||
|
||||
Note: modifying the chart after this operation will
|
||||
not change the item as it exists in the cache.
|
||||
`
|
||||
|
||||
func newChartSaveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "save [path] [ref]",
|
||||
Short: "save a chart directory",
|
||||
Long: chartSaveDesc,
|
||||
Args: require.MinimumNArgs(2),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path := args[0]
|
||||
ref := args[1]
|
||||
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := loader.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return action.NewChartSave(cfg).Run(out, ch, ref)
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
experimental "helm.sh/helm/v3/internal/experimental/action"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const pushDesc = `
|
||||
Upload a chart to a registry.
|
||||
|
||||
If the chart has an associated provenance file,
|
||||
it will also be uploaded.
|
||||
`
|
||||
|
||||
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client := experimental.NewPushWithOpts(experimental.WithPushConfig(cfg))
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "push [chart] [remote]",
|
||||
Short: "push a chart to remote",
|
||||
Long: pushDesc,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
PersistentPreRunE: checkOCIFeatureGate(),
|
||||
Args: require.MinimumNArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
chartRef := args[0]
|
||||
remote := args[1]
|
||||
client.Settings = settings
|
||||
output, err := client.Run(chartRef, remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, output)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/pusher"
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/internal/experimental/uploader"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
// Push is the action for uploading a chart.
|
||||
//
|
||||
// It provides the implementation of 'helm push'.
|
||||
type Push struct {
|
||||
Settings *cli.EnvSettings
|
||||
cfg *action.Configuration
|
||||
}
|
||||
|
||||
// PushOpt is a type of function that sets options for a push action.
|
||||
type PushOpt func(*Push)
|
||||
|
||||
// WithPushConfig sets the cfg field on the push configuration object.
|
||||
func WithPushConfig(cfg *action.Configuration) PushOpt {
|
||||
return func(p *Push) {
|
||||
p.cfg = cfg
|
||||
}
|
||||
}
|
||||
|
||||
// NewPushWithOpts creates a new push, with configuration options.
|
||||
func NewPushWithOpts(opts ...PushOpt) *Push {
|
||||
p := &Push{}
|
||||
for _, fn := range opts {
|
||||
fn(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Run executes 'helm push' against the given chart archive.
|
||||
func (p *Push) Run(chartRef string, remote string) (string, error) {
|
||||
var out strings.Builder
|
||||
|
||||
c := uploader.ChartUploader{
|
||||
Out: &out,
|
||||
Pushers: pusher.All(p.Settings),
|
||||
Options: []pusher.Option{},
|
||||
}
|
||||
|
||||
if registry.IsOCI(remote) {
|
||||
c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient))
|
||||
}
|
||||
|
||||
return out.String(), c.UploadTo(chartRef, remote)
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pusher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// OCIPusher is the default OCI backend handler
|
||||
type OCIPusher struct {
|
||||
opts options
|
||||
}
|
||||
|
||||
// Push performs a Push from repo.Pusher.
|
||||
func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error {
|
||||
for _, opt := range options {
|
||||
opt(&pusher.opts)
|
||||
}
|
||||
return pusher.push(chartRef, href)
|
||||
}
|
||||
|
||||
func (pusher *OCIPusher) push(chartRef, href string) error {
|
||||
stat, err := os.Stat(chartRef)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.Errorf("%s: no such file", chartRef)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return errors.New("cannot push directory, must provide chart archive (.tgz)")
|
||||
}
|
||||
|
||||
meta, err := loader.Load(chartRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := pusher.opts.registryClient
|
||||
|
||||
chartBytes, err := ioutil.ReadFile(chartRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pushOpts []registry.PushOption
|
||||
provRef := fmt.Sprintf("%s.prov", chartRef)
|
||||
if _, err := os.Stat(provRef); err == nil {
|
||||
provBytes, err := ioutil.ReadFile(provRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pushOpts = append(pushOpts, registry.PushOptProvData(provBytes))
|
||||
}
|
||||
|
||||
ref := fmt.Sprintf("%s:%s",
|
||||
path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name),
|
||||
meta.Metadata.Version)
|
||||
|
||||
_, err = client.Push(chartBytes, ref, pushOpts...)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewOCIPusher constructs a valid OCI client as a Pusher
|
||||
func NewOCIPusher(ops ...Option) (Pusher, error) {
|
||||
registryClient, err := registry.NewClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := OCIPusher{
|
||||
opts: options{
|
||||
registryClient: registryClient,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range ops {
|
||||
opt(&client.opts)
|
||||
}
|
||||
|
||||
return &client, nil
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pusher
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
// options are generic parameters to be provided to the pusher during instantiation.
|
||||
//
|
||||
// Pushers may or may not ignore these parameters as they are passed in.
|
||||
type options struct {
|
||||
registryClient *registry.Client
|
||||
}
|
||||
|
||||
// Option allows specifying various settings configurable by the user for overriding the defaults
|
||||
// used when performing Push operations with the Pusher.
|
||||
type Option func(*options)
|
||||
|
||||
// WithRegistryClient sets the registryClient option.
|
||||
func WithRegistryClient(client *registry.Client) Option {
|
||||
return func(opts *options) {
|
||||
opts.registryClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// Pusher is an interface to support upload to the specified URL.
|
||||
type Pusher interface {
|
||||
// Push file content by url string
|
||||
Push(chartRef, url string, options ...Option) error
|
||||
}
|
||||
|
||||
// Constructor is the function for every pusher which creates a specific instance
|
||||
// according to the configuration
|
||||
type Constructor func(options ...Option) (Pusher, error)
|
||||
|
||||
// Provider represents any pusher and the schemes that it supports.
|
||||
type Provider struct {
|
||||
Schemes []string
|
||||
New Constructor
|
||||
}
|
||||
|
||||
// Provides returns true if the given scheme is supported by this Provider.
|
||||
func (p Provider) Provides(scheme string) bool {
|
||||
for _, i := range p.Schemes {
|
||||
if i == scheme {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Providers is a collection of Provider objects.
|
||||
type Providers []Provider
|
||||
|
||||
// ByScheme returns a Provider that handles the given scheme.
|
||||
//
|
||||
// If no provider handles this scheme, this will return an error.
|
||||
func (p Providers) ByScheme(scheme string) (Pusher, error) {
|
||||
for _, pp := range p {
|
||||
if pp.Provides(scheme) {
|
||||
return pp.New()
|
||||
}
|
||||
}
|
||||
return nil, errors.Errorf("scheme %q not supported", scheme)
|
||||
}
|
||||
|
||||
var ociProvider = Provider{
|
||||
Schemes: []string{registry.OCIScheme},
|
||||
New: NewOCIPusher,
|
||||
}
|
||||
|
||||
// All finds all of the registered pushers as a list of Provider instances.
|
||||
// Currently, just the built-in pushers are collected.
|
||||
func All(settings *cli.EnvSettings) Providers {
|
||||
result := Providers{ociProvider}
|
||||
return result
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pusher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
p := Provider{
|
||||
[]string{"one", "three"},
|
||||
func(_ ...Option) (Pusher, error) { return nil, nil },
|
||||
}
|
||||
|
||||
if !p.Provides("three") {
|
||||
t.Error("Expected provider to provide three")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviders(t *testing.T) {
|
||||
ps := Providers{
|
||||
{[]string{"one", "three"}, func(_ ...Option) (Pusher, error) { return nil, nil }},
|
||||
{[]string{"two", "four"}, func(_ ...Option) (Pusher, error) { return nil, nil }},
|
||||
}
|
||||
|
||||
if _, err := ps.ByScheme("one"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := ps.ByScheme("four"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := ps.ByScheme("five"); err == nil {
|
||||
t.Error("Did not expect handler for five")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
env := cli.New()
|
||||
all := All(env)
|
||||
if len(all) != 1 {
|
||||
t.Errorf("expected 1 provider (OCI), got %d", len(all))
|
||||
}
|
||||
}
|
||||
|
||||
func TestByScheme(t *testing.T) {
|
||||
env := cli.New()
|
||||
g := All(env)
|
||||
if _, err := g.ByScheme(registry.OCIScheme); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
@ -1,368 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
orascontent "oras.land/oras-go/pkg/content"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheRootDir is the root directory for a cache
|
||||
CacheRootDir = "cache"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cache handles local/in-memory storage of Helm charts, compliant with OCI Layout
|
||||
Cache struct {
|
||||
debug bool
|
||||
out io.Writer
|
||||
rootDir string
|
||||
ociStore *orascontent.OCIStore
|
||||
memoryStore *orascontent.Memorystore
|
||||
}
|
||||
|
||||
// CacheRefSummary contains as much info as available describing a chart reference in cache
|
||||
// Note: fields here are sorted by the order in which they are set in FetchReference method
|
||||
CacheRefSummary struct {
|
||||
Name string
|
||||
Repo string
|
||||
Tag string
|
||||
Exists bool
|
||||
Manifest *ocispec.Descriptor
|
||||
Config *ocispec.Descriptor
|
||||
ContentLayer *ocispec.Descriptor
|
||||
Size int64
|
||||
Digest digest.Digest
|
||||
CreatedAt time.Time
|
||||
Chart *chart.Chart
|
||||
}
|
||||
)
|
||||
|
||||
// NewCache returns a new OCI Layout-compliant cache with config
|
||||
func NewCache(opts ...CacheOption) (*Cache, error) {
|
||||
cache := &Cache{
|
||||
out: ioutil.Discard,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(cache)
|
||||
}
|
||||
// validate
|
||||
if cache.rootDir == "" {
|
||||
return nil, errors.New("must set cache root dir on initialization")
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// FetchReference retrieves a chart ref from cache
|
||||
func (cache *Cache) FetchReference(ref *Reference) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := CacheRefSummary{
|
||||
Name: ref.FullName(),
|
||||
Repo: ref.Repo,
|
||||
Tag: ref.Tag,
|
||||
}
|
||||
for _, desc := range cache.ociStore.ListReferences() {
|
||||
if desc.Annotations[ocispec.AnnotationRefName] == r.Name {
|
||||
r.Exists = true
|
||||
manifestBytes, err := cache.fetchBlob(&desc)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
var manifest ocispec.Manifest
|
||||
err = json.Unmarshal(manifestBytes, &manifest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Manifest = &desc
|
||||
r.Config = &manifest.Config
|
||||
numLayers := len(manifest.Layers)
|
||||
if numLayers != 1 {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest does not contain exactly 1 layer (total: %d)", numLayers))
|
||||
}
|
||||
var contentLayer *ocispec.Descriptor
|
||||
for _, layer := range manifest.Layers {
|
||||
switch layer.MediaType {
|
||||
case HelmChartContentLayerMediaType:
|
||||
contentLayer = &layer
|
||||
}
|
||||
}
|
||||
if contentLayer == nil {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest does not contain a layer with mediatype %s", HelmChartContentLayerMediaType))
|
||||
}
|
||||
if contentLayer.Size == 0 {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest layer with mediatype %s is of size 0", HelmChartContentLayerMediaType))
|
||||
}
|
||||
r.ContentLayer = contentLayer
|
||||
info, err := cache.ociStore.Info(ctx(cache.out, cache.debug), contentLayer.Digest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Size = info.Size
|
||||
r.Digest = info.Digest
|
||||
r.CreatedAt = info.CreatedAt
|
||||
contentBytes, err := cache.fetchBlob(contentLayer)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
ch, err := loader.LoadArchive(bytes.NewBuffer(contentBytes))
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Chart = ch
|
||||
}
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// StoreReference stores a chart ref in cache
|
||||
func (cache *Cache) StoreReference(ref *Reference, ch *chart.Chart) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := CacheRefSummary{
|
||||
Name: ref.FullName(),
|
||||
Repo: ref.Repo,
|
||||
Tag: ref.Tag,
|
||||
Chart: ch,
|
||||
}
|
||||
existing, _ := cache.FetchReference(ref)
|
||||
r.Exists = existing.Exists
|
||||
config, _, err := cache.saveChartConfig(ch)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Config = config
|
||||
contentLayer, _, err := cache.saveChartContentLayer(ch)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.ContentLayer = contentLayer
|
||||
info, err := cache.ociStore.Info(ctx(cache.out, cache.debug), contentLayer.Digest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Size = info.Size
|
||||
r.Digest = info.Digest
|
||||
r.CreatedAt = info.CreatedAt
|
||||
manifest, _, err := cache.saveChartManifest(config, contentLayer)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Manifest = manifest
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// DeleteReference deletes a chart ref from cache
|
||||
// TODO: garbage collection, only manifest removed
|
||||
func (cache *Cache) DeleteReference(ref *Reference) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := cache.FetchReference(ref)
|
||||
if err != nil || !r.Exists {
|
||||
return r, err
|
||||
}
|
||||
cache.ociStore.DeleteReference(r.Name)
|
||||
err = cache.ociStore.SaveIndex()
|
||||
return r, err
|
||||
}
|
||||
|
||||
// ListReferences lists all chart refs in a cache
|
||||
func (cache *Cache) ListReferences() ([]*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rr []*CacheRefSummary
|
||||
for _, desc := range cache.ociStore.ListReferences() {
|
||||
name := desc.Annotations[ocispec.AnnotationRefName]
|
||||
if name == "" {
|
||||
if cache.debug {
|
||||
fmt.Fprintf(cache.out, "warning: found manifest without name: %s", desc.Digest.Hex())
|
||||
}
|
||||
continue
|
||||
}
|
||||
ref, err := ParseReference(name)
|
||||
if err != nil {
|
||||
return rr, err
|
||||
}
|
||||
r, err := cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return rr, err
|
||||
}
|
||||
rr = append(rr, r)
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
// AddManifest provides a manifest to the cache index.json
|
||||
func (cache *Cache) AddManifest(ref *Reference, manifest *ocispec.Descriptor) error {
|
||||
if err := cache.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
cache.ociStore.AddReference(ref.FullName(), *manifest)
|
||||
err := cache.ociStore.SaveIndex()
|
||||
return err
|
||||
}
|
||||
|
||||
// Provider provides a valid containerd Provider
|
||||
func (cache *Cache) Provider() content.Provider {
|
||||
return content.Provider(cache.ociStore)
|
||||
}
|
||||
|
||||
// Ingester provides a valid containerd Ingester
|
||||
func (cache *Cache) Ingester() content.Ingester {
|
||||
return content.Ingester(cache.ociStore)
|
||||
}
|
||||
|
||||
// ProvideIngester provides a valid oras ProvideIngester
|
||||
func (cache *Cache) ProvideIngester() orascontent.ProvideIngester {
|
||||
return orascontent.ProvideIngester(cache.ociStore)
|
||||
}
|
||||
|
||||
// init creates files needed necessary for OCI layout store
|
||||
func (cache *Cache) init() error {
|
||||
if cache.ociStore == nil {
|
||||
ociStore, err := orascontent.NewOCIStore(cache.rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.ociStore = ociStore
|
||||
cache.memoryStore = orascontent.NewMemoryStore()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveChartConfig stores the Chart.yaml as json blob and returns a descriptor
|
||||
func (cache *Cache) saveChartConfig(ch *chart.Chart) (*ocispec.Descriptor, bool, error) {
|
||||
configBytes, err := json.Marshal(ch.Metadata)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
configExists, err := cache.storeBlob(configBytes)
|
||||
if err != nil {
|
||||
return nil, configExists, err
|
||||
}
|
||||
descriptor := cache.memoryStore.Add("", HelmChartConfigMediaType, configBytes)
|
||||
return &descriptor, configExists, nil
|
||||
}
|
||||
|
||||
// saveChartContentLayer stores the chart as tarball blob and returns a descriptor
|
||||
func (cache *Cache) saveChartContentLayer(ch *chart.Chart) (*ocispec.Descriptor, bool, error) {
|
||||
destDir := filepath.Join(cache.rootDir, ".build")
|
||||
os.MkdirAll(destDir, 0755)
|
||||
tmpFile, err := chartutil.Save(ch, destDir)
|
||||
defer os.Remove(tmpFile)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to save")
|
||||
}
|
||||
contentBytes, err := ioutil.ReadFile(tmpFile)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
contentExists, err := cache.storeBlob(contentBytes)
|
||||
if err != nil {
|
||||
return nil, contentExists, err
|
||||
}
|
||||
descriptor := cache.memoryStore.Add("", HelmChartContentLayerMediaType, contentBytes)
|
||||
return &descriptor, contentExists, nil
|
||||
}
|
||||
|
||||
// saveChartManifest stores the chart manifest as json blob and returns a descriptor
|
||||
func (cache *Cache) saveChartManifest(config *ocispec.Descriptor, contentLayer *ocispec.Descriptor) (*ocispec.Descriptor, bool, error) {
|
||||
manifest := ocispec.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
Config: *config,
|
||||
Layers: []ocispec.Descriptor{*contentLayer},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
manifestExists, err := cache.storeBlob(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, manifestExists, err
|
||||
}
|
||||
descriptor := ocispec.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageManifest,
|
||||
Digest: digest.FromBytes(manifestBytes),
|
||||
Size: int64(len(manifestBytes)),
|
||||
}
|
||||
return &descriptor, manifestExists, nil
|
||||
}
|
||||
|
||||
// storeBlob stores a blob on filesystem
|
||||
func (cache *Cache) storeBlob(blobBytes []byte) (bool, error) {
|
||||
var exists bool
|
||||
writer, err := cache.ociStore.Store.Writer(ctx(cache.out, cache.debug),
|
||||
content.WithRef(digest.FromBytes(blobBytes).Hex()))
|
||||
if err != nil {
|
||||
return exists, err
|
||||
}
|
||||
_, err = writer.Write(blobBytes)
|
||||
if err != nil {
|
||||
return exists, err
|
||||
}
|
||||
err = writer.Commit(ctx(cache.out, cache.debug), 0, writer.Digest())
|
||||
if err != nil {
|
||||
if !errdefs.IsAlreadyExists(err) {
|
||||
return exists, err
|
||||
}
|
||||
exists = true
|
||||
}
|
||||
err = writer.Close()
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// fetchBlob retrieves a blob from filesystem
|
||||
func (cache *Cache) fetchBlob(desc *ocispec.Descriptor) ([]byte, error) {
|
||||
reader, err := cache.ociStore.ReaderAt(ctx(cache.out, cache.debug), *desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
bytes := make([]byte, desc.Size)
|
||||
_, err = reader.ReadAt(bytes, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// CacheOption allows specifying various settings configurable by the user for overriding the defaults
|
||||
// used when creating a new default cache
|
||||
CacheOption func(*Cache)
|
||||
)
|
||||
|
||||
// CacheOptDebug returns a function that sets the debug setting on cache options set
|
||||
func CacheOptDebug(debug bool) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.debug = debug
|
||||
}
|
||||
}
|
||||
|
||||
// CacheOptWriter returns a function that sets the writer setting on cache options set
|
||||
func CacheOptWriter(out io.Writer) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.out = out
|
||||
}
|
||||
}
|
||||
|
||||
// CacheOptRoot returns a function that sets the root directory setting on cache options set
|
||||
func CacheOptRoot(rootDir string) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.rootDir = rootDir
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// ClientOption allows specifying various settings configurable by the user for overriding the defaults
|
||||
// used when creating a new default client
|
||||
ClientOption func(*Client)
|
||||
)
|
||||
|
||||
// ClientOptDebug returns a function that sets the debug setting on client options set
|
||||
func ClientOptDebug(debug bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.debug = debug
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptWriter returns a function that sets the writer setting on client options set
|
||||
func ClientOptWriter(out io.Writer) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.out = out
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptResolver returns a function that sets the resolver setting on client options set
|
||||
func ClientOptResolver(resolver *Resolver) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.resolver = resolver
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptAuthorizer returns a function that sets the authorizer setting on client options set
|
||||
func ClientOptAuthorizer(authorizer *Authorizer) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.authorizer = authorizer
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptCache returns a function that sets the cache setting on a client options set
|
||||
func ClientOptCache(cache *Cache) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.cache = cache
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptCredentialsFile returns a function that sets the cache setting on a client options set
|
||||
func ClientOptCredentialsFile(credentialsFile string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.credentialsFile = credentialsFile
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptColumnWidth returns a function that sets the column width on a client options set
|
||||
func ClientOptColumnWidth(columnWidth uint) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.columnWidth = columnWidth
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
validPortRegEx = regexp.MustCompile(`^([1-9]\d{0,3}|0|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$`) // adapted from https://stackoverflow.com/a/12968117
|
||||
// TODO: Currently we don't support digests, so we are only splitting on the
|
||||
// colon. However, when we add support for digests, we'll need to use the
|
||||
// regexp anyway to split on both colons and @, so leaving it like this for
|
||||
// now
|
||||
referenceDelimiter = regexp.MustCompile(`[:]`)
|
||||
errEmptyRepo = errors.New("parsed repo was empty")
|
||||
errTooManyColons = errors.New("ref may only contain a single colon character (:) unless specifying a port number")
|
||||
)
|
||||
|
||||
type (
|
||||
// Reference defines the main components of a reference specification
|
||||
Reference struct {
|
||||
Tag string
|
||||
Repo string
|
||||
}
|
||||
)
|
||||
|
||||
// ParseReference converts a string to a Reference
|
||||
func ParseReference(s string) (*Reference, error) {
|
||||
if s == "" {
|
||||
return nil, errEmptyRepo
|
||||
}
|
||||
// Split the components of the string on the colon or @, if it is more than 3,
|
||||
// immediately return an error. Other validation will be performed later in
|
||||
// the function
|
||||
splitComponents := fixSplitComponents(referenceDelimiter.Split(s, -1))
|
||||
if len(splitComponents) > 3 {
|
||||
return nil, errTooManyColons
|
||||
}
|
||||
|
||||
var ref *Reference
|
||||
switch len(splitComponents) {
|
||||
case 1:
|
||||
ref = &Reference{Repo: splitComponents[0]}
|
||||
case 2:
|
||||
ref = &Reference{Repo: splitComponents[0], Tag: splitComponents[1]}
|
||||
case 3:
|
||||
ref = &Reference{Repo: strings.Join(splitComponents[:2], ":"), Tag: splitComponents[2]}
|
||||
}
|
||||
|
||||
// ensure the reference is valid
|
||||
err := ref.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// FullName the full name of a reference (repo:tag)
|
||||
func (ref *Reference) FullName() string {
|
||||
if ref.Tag == "" {
|
||||
return ref.Repo
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ref.Repo, ref.Tag)
|
||||
}
|
||||
|
||||
// validate makes sure the ref meets our criteria
|
||||
func (ref *Reference) validate() error {
|
||||
|
||||
err := ref.validateRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ref.validateNumColons()
|
||||
}
|
||||
|
||||
// validateRepo checks that the Repo field is non-empty
|
||||
func (ref *Reference) validateRepo() error {
|
||||
if ref.Repo == "" {
|
||||
return errEmptyRepo
|
||||
}
|
||||
// Makes sure the repo results in a parsable URL (similar to what is done
|
||||
// with containerd reference parsing)
|
||||
_, err := url.Parse("//" + ref.Repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// validateNumColons ensures the ref only contains a single colon character (:)
|
||||
// (or potentially two, there might be a port number specified i.e. :5000)
|
||||
func (ref *Reference) validateNumColons() error {
|
||||
if strings.Contains(ref.Tag, ":") {
|
||||
return errTooManyColons
|
||||
}
|
||||
parts := strings.Split(ref.Repo, ":")
|
||||
lastIndex := len(parts) - 1
|
||||
if 1 < lastIndex {
|
||||
return errTooManyColons
|
||||
}
|
||||
if 0 < lastIndex {
|
||||
port := strings.Split(parts[lastIndex], "/")[0]
|
||||
if !isValidPort(port) {
|
||||
return errTooManyColons
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidPort returns whether or not a string looks like a valid port
|
||||
func isValidPort(s string) bool {
|
||||
return validPortRegEx.MatchString(s)
|
||||
}
|
||||
|
||||
// fixSplitComponents this will modify reference parts based on presence of port
|
||||
// Example: {localhost, 5000/x/y/z, 0.1.0} => {localhost:5000/x/y/z, 0.1.0}
|
||||
func fixSplitComponents(c []string) []string {
|
||||
if len(c) <= 1 {
|
||||
return c
|
||||
}
|
||||
possiblePortParts := strings.Split(c[1], "/")
|
||||
if _, err := strconv.Atoi(possiblePortParts[0]); err == nil {
|
||||
components := []string{strings.Join(c[:2], ":")}
|
||||
components = append(components, c[2:]...)
|
||||
return components
|
||||
}
|
||||
return c
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseReference(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
// bad refs
|
||||
s := ""
|
||||
_, err := ParseReference(s)
|
||||
is.Error(err, "empty ref")
|
||||
|
||||
s = "my:bad:ref"
|
||||
_, err = ParseReference(s)
|
||||
is.Error(err, "ref contains too many colons (2)")
|
||||
|
||||
s = "my:really:bad:ref"
|
||||
_, err = ParseReference(s)
|
||||
is.Error(err, "ref contains too many colons (3)")
|
||||
|
||||
// good refs
|
||||
s = "mychart"
|
||||
ref, err := ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("mychart", ref.Repo)
|
||||
is.Equal("", ref.Tag)
|
||||
is.Equal("mychart", ref.FullName())
|
||||
|
||||
s = "mychart:1.5.0"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("mychart", ref.Repo)
|
||||
is.Equal("1.5.0", ref.Tag)
|
||||
is.Equal("mychart:1.5.0", ref.FullName())
|
||||
|
||||
s = "myrepo/mychart"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("myrepo/mychart", ref.Repo)
|
||||
is.Equal("", ref.Tag)
|
||||
is.Equal("myrepo/mychart", ref.FullName())
|
||||
|
||||
s = "myrepo/mychart:1.5.0"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("myrepo/mychart", ref.Repo)
|
||||
is.Equal("1.5.0", ref.Tag)
|
||||
is.Equal("myrepo/mychart:1.5.0", ref.FullName())
|
||||
|
||||
s = "mychart:5001:1.5.0"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("mychart:5001", ref.Repo)
|
||||
is.Equal("1.5.0", ref.Tag)
|
||||
is.Equal("mychart:5001:1.5.0", ref.FullName())
|
||||
|
||||
s = "myrepo:5001/mychart:1.5.0"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("myrepo:5001/mychart", ref.Repo)
|
||||
is.Equal("1.5.0", ref.Tag)
|
||||
is.Equal("myrepo:5001/mychart:1.5.0", ref.FullName())
|
||||
|
||||
s = "127.0.0.1:5001/mychart:1.5.0"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("127.0.0.1:5001/mychart", ref.Repo)
|
||||
is.Equal("1.5.0", ref.Tag)
|
||||
is.Equal("127.0.0.1:5001/mychart:1.5.0", ref.FullName())
|
||||
|
||||
s = "localhost:5000/mychart:latest"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("localhost:5000/mychart", ref.Repo)
|
||||
is.Equal("latest", ref.Tag)
|
||||
is.Equal("localhost:5000/mychart:latest", ref.FullName())
|
||||
|
||||
s = "my.host.com/my/nested/repo:1.2.3"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("my.host.com/my/nested/repo", ref.Repo)
|
||||
is.Equal("1.2.3", ref.Tag)
|
||||
is.Equal("my.host.com/my/nested/repo:1.2.3", ref.FullName())
|
||||
|
||||
s = "localhost:5000/x/y/z"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("localhost:5000/x/y/z", ref.Repo)
|
||||
is.Equal("", ref.Tag)
|
||||
is.Equal("localhost:5000/x/y/z", ref.FullName())
|
||||
|
||||
s = "localhost:5000/x/y/z:123"
|
||||
ref, err = ParseReference(s)
|
||||
is.NoError(err)
|
||||
is.Equal("localhost:5000/x/y/z", ref.Repo)
|
||||
is.Equal("123", ref.Tag)
|
||||
is.Equal("localhost:5000/x/y/z:123", ref.FullName())
|
||||
|
||||
s = "localhost:5000/x/y/z:123:x"
|
||||
_, err = ParseReference(s)
|
||||
is.Error(err, "ref contains too many colons (3)")
|
||||
|
||||
s = "localhost:5000/x/y/z:123:x:y"
|
||||
_, err = ParseReference(s)
|
||||
is.Error(err, "ref contains too many colons (4)")
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package uploader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/pusher"
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartUploader handles uploading a chart.
|
||||
type ChartUploader struct {
|
||||
// Out is the location to write warning and info messages.
|
||||
Out io.Writer
|
||||
// Pusher collection for the operation
|
||||
Pushers pusher.Providers
|
||||
// Options provide parameters to be passed along to the Pusher being initialized.
|
||||
Options []pusher.Option
|
||||
// RegistryClient is a client for interacting with registries.
|
||||
RegistryClient *registry.Client
|
||||
}
|
||||
|
||||
// UploadTo uploads a chart. Depending on the settings, it may also upload a provenance file.
|
||||
func (c *ChartUploader) UploadTo(ref, remote string) error {
|
||||
u, err := url.Parse(remote)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid chart URL format: %s", remote)
|
||||
}
|
||||
|
||||
if u.Scheme == "" {
|
||||
return errors.New(fmt.Sprintf("scheme prefix missing from remote (e.g. \"%s://\")", registry.OCIScheme))
|
||||
}
|
||||
|
||||
p, err := c.Pushers.ByScheme(u.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Push(ref, u.String(), c.Options...)
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
// ChartExport performs a chart export operation.
|
||||
type ChartExport struct {
|
||||
cfg *Configuration
|
||||
|
||||
Destination string
|
||||
}
|
||||
|
||||
// NewChartExport creates a new ChartExport object with the given configuration.
|
||||
func NewChartExport(cfg *Configuration) *ChartExport {
|
||||
return &ChartExport{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart export operation
|
||||
func (a *ChartExport) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := a.cfg.RegistryClient.LoadChart(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the chart to local destination directory
|
||||
err = chartutil.SaveDir(ch, a.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := filepath.Join(a.Destination, ch.Metadata.Name)
|
||||
fmt.Fprintf(out, "Exported chart to %s/\n", d)
|
||||
return nil
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartList performs a chart list operation.
|
||||
type ChartList struct {
|
||||
cfg *Configuration
|
||||
ColumnWidth uint
|
||||
}
|
||||
|
||||
// NewChartList creates a new ChartList object with the given configuration.
|
||||
func NewChartList(cfg *Configuration) *ChartList {
|
||||
return &ChartList{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart list operation
|
||||
func (a *ChartList) Run(out io.Writer) error {
|
||||
client := a.cfg.RegistryClient
|
||||
opt := registry.ClientOptColumnWidth(a.ColumnWidth)
|
||||
opt(client)
|
||||
return client.PrintChartTable()
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartPull performs a chart pull operation.
|
||||
type ChartPull struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartPull creates a new ChartPull object with the given configuration.
|
||||
func NewChartPull(cfg *Configuration) *ChartPull {
|
||||
return &ChartPull{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart pull operation
|
||||
func (a *ChartPull) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.PullChartToCache(r)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartPush performs a chart push operation.
|
||||
type ChartPush struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartPush creates a new ChartPush object with the given configuration.
|
||||
func NewChartPush(cfg *Configuration) *ChartPush {
|
||||
return &ChartPush{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart push operation
|
||||
func (a *ChartPush) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.PushChart(r)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// ChartRemove performs a chart remove operation.
|
||||
type ChartRemove struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartRemove creates a new ChartRemove object with the given configuration.
|
||||
func NewChartRemove(cfg *Configuration) *ChartRemove {
|
||||
return &ChartRemove{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart remove operation
|
||||
func (a *ChartRemove) Run(out io.Writer, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.cfg.RegistryClient.RemoveChart(r)
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// ChartSave performs a chart save operation.
|
||||
type ChartSave struct {
|
||||
cfg *Configuration
|
||||
}
|
||||
|
||||
// NewChartSave creates a new ChartSave object with the given configuration.
|
||||
func NewChartSave(cfg *Configuration) *ChartSave {
|
||||
return &ChartSave{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the chart save operation
|
||||
func (a *ChartSave) Run(out io.Writer, ch *chart.Chart, ref string) error {
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If no tag is present, use the chart version
|
||||
if r.Tag == "" {
|
||||
r.Tag = ch.Metadata.Version
|
||||
}
|
||||
|
||||
return a.cfg.RegistryClient.SaveChart(ch, r)
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
func chartSaveAction(t *testing.T) *ChartSave {
|
||||
t.Helper()
|
||||
config := actionConfigFixture(t)
|
||||
action := NewChartSave(config)
|
||||
return action
|
||||
}
|
||||
|
||||
func TestChartSave(t *testing.T) {
|
||||
action := chartSaveAction(t)
|
||||
|
||||
input := buildChart()
|
||||
if err := action.Run(ioutil.Discard, input, "localhost:5000/test:0.2.0"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ref, err := registry.ParseReference("localhost:5000/test:0.2.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := action.cfg.RegistryClient.LoadChart(ref); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// now let's check if `helm chart save` can use the chart version when the tag is not present
|
||||
if err := action.Run(ioutil.Discard, input, "localhost:5000/test"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ref, err = registry.ParseReference("localhost:5000/test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO: guess latest based on semver?
|
||||
_, err = action.cfg.RegistryClient.LoadChart(ref)
|
||||
if err == nil {
|
||||
t.Error("Expected error parsing ref without tag")
|
||||
}
|
||||
|
||||
ref.Tag = "0.1.0"
|
||||
if _, err := action.cfg.RegistryClient.LoadChart(ref); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue