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