mirror of https://github.com/helm/helm
Helm 3: initial registry support (#5243)
* initial registry support Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * fix dependency mess Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * add extra chart command output Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * sanitize registry path (windows fix) Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * store all sha256 blobs in same dir Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * switch to use chartutil.SaveDir Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * populate chart command long descriptions Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * remove test cache dir in teardown Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * add long description of chart export Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com> * clean up table rows code Signed-off-by: Josh Dolitsky <jdolitsky@gmail.com>pull/5280/head
parent
612d3a9f27
commit
a32f8ebb37
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
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/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chartHelp = `
|
||||||
|
This command consists of multiple subcommands to interact with charts and registries.
|
||||||
|
|
||||||
|
It can be used to push, pull, tag, list, or remove Helm charts.
|
||||||
|
Example usage:
|
||||||
|
$ helm chart pull [URL]
|
||||||
|
`
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
cmd.AddCommand(
|
||||||
|
newChartListCmd(cfg, out),
|
||||||
|
newChartExportCmd(cfg, out),
|
||||||
|
newChartPullCmd(cfg, out),
|
||||||
|
newChartPushCmd(cfg, out),
|
||||||
|
newChartRemoveCmd(cfg, out),
|
||||||
|
newChartSaveCmd(cfg, out),
|
||||||
|
)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove once WARN lines removed from oras or containerd
|
||||||
|
func init() {
|
||||||
|
logrus.SetLevel(logrus.ErrorLevel)
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/cmd/helm/require"
|
||||||
|
"k8s.io/helm/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 {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "export [ref]",
|
||||||
|
Short: "export a chart to directory",
|
||||||
|
Long: chartExportDesc,
|
||||||
|
Args: require.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ref := args[0]
|
||||||
|
return action.NewChartExport(cfg).Run(out, ref)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/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 {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "list all saved charts",
|
||||||
|
Long: chartListDesc,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return action.NewChartList(cfg).Run(out)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/cmd/helm/require"
|
||||||
|
"k8s.io/helm/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),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ref := args[0]
|
||||||
|
return action.NewChartPull(cfg).Run(out, ref)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/cmd/helm/require"
|
||||||
|
"k8s.io/helm/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),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ref := args[0]
|
||||||
|
return action.NewChartPush(cfg).Run(out, ref)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/cmd/helm/require"
|
||||||
|
"k8s.io/helm/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),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
ref := args[0]
|
||||||
|
return action.NewChartRemove(cfg).Run(out, ref)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/cmd/helm/require"
|
||||||
|
"k8s.io/helm/pkg/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
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),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
path := args[0]
|
||||||
|
ref := args[1]
|
||||||
|
return action.NewChartSave(cfg).Run(out, path, ref)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/chartutil"
|
||||||
|
"k8s.io/helm/pkg/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChartExport performs a chart export operation.
|
||||||
|
type ChartExport struct {
|
||||||
|
cfg *Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 directory
|
||||||
|
// TODO: make destination dir configurable
|
||||||
|
err = chartutil.SaveDir(ch, ".")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(out, "Exported to %s/\n", ch.Metadata.Name)
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChartList performs a chart list operation.
|
||||||
|
type ChartList struct {
|
||||||
|
cfg *Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return a.cfg.RegistryClient.PrintChartTable()
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/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.PullChart(r)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/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)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/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)
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/chart/loader"
|
||||||
|
"k8s.io/helm/pkg/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, path string, ref string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := loader.LoadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := registry.ParseReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.cfg.RegistryClient.SaveChart(ch, r)
|
||||||
|
}
|
@ -0,0 +1,493 @@
|
|||||||
|
/*
|
||||||
|
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 "k8s.io/helm/pkg/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
orascontent "github.com/deislabs/oras/pkg/content"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
checksum "github.com/opencontainers/go-digest"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/chart"
|
||||||
|
"k8s.io/helm/pkg/chart/loader"
|
||||||
|
"k8s.io/helm/pkg/chartutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tableHeaders = []string{"name", "version", "digest", "size", "created"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
filesystemCache struct {
|
||||||
|
out io.Writer
|
||||||
|
rootDir string
|
||||||
|
store *orascontent.Memorystore
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cache *filesystemCache) LayersToChart(layers []ocispec.Descriptor) (*chart.Chart, error) {
|
||||||
|
metaLayer, contentLayer, err := extractLayers(layers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name, version, err := extractChartNameVersionFromLayer(contentLayer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain raw chart meta content (json)
|
||||||
|
_, metaJSONRaw, ok := cache.store.Get(metaLayer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("error retrieving meta layer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct chart metadata object
|
||||||
|
metadata := chart.Metadata{}
|
||||||
|
err = json.Unmarshal(metaJSONRaw, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metadata.Name = name
|
||||||
|
metadata.Version = version
|
||||||
|
|
||||||
|
// Obtain raw chart content
|
||||||
|
_, contentRaw, ok := cache.store.Get(contentLayer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("error retrieving meta layer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct chart object and attach metadata
|
||||||
|
ch, err := loader.LoadArchive(bytes.NewBuffer(contentRaw))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ch.Metadata = &metadata
|
||||||
|
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *filesystemCache) ChartToLayers(ch *chart.Chart) ([]ocispec.Descriptor, error) {
|
||||||
|
|
||||||
|
// extract/separate the name and version from other metadata
|
||||||
|
if ch.Metadata == nil {
|
||||||
|
return nil, errors.New("chart does not contain metadata")
|
||||||
|
}
|
||||||
|
name := ch.Metadata.Name
|
||||||
|
version := ch.Metadata.Version
|
||||||
|
|
||||||
|
// Create meta layer, clear name and version from Chart.yaml and convert to json
|
||||||
|
ch.Metadata.Name = ""
|
||||||
|
ch.Metadata.Version = ""
|
||||||
|
metaJSONRaw, err := json.Marshal(ch.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metaLayer := cache.store.Add(HelmChartMetaFileName, HelmChartMetaMediaType, metaJSONRaw)
|
||||||
|
|
||||||
|
// Create content layer
|
||||||
|
// TODO: something better than this hack. Currently needed for chartutil.Save()
|
||||||
|
// If metadata does not contain Name or Version, an error is returned
|
||||||
|
// such as "no chart name specified (Chart.yaml)"
|
||||||
|
ch.Metadata = &chart.Metadata{Name: "-", Version: "-"}
|
||||||
|
destDir := mkdir(filepath.Join(cache.rootDir, "blobs", ".build"))
|
||||||
|
tmpFile, err := chartutil.Save(ch, destDir)
|
||||||
|
defer os.Remove(tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to save")
|
||||||
|
}
|
||||||
|
contentRaw, err := ioutil.ReadFile(tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentLayer := cache.store.Add(HelmChartContentFileName, HelmChartContentMediaType, contentRaw)
|
||||||
|
|
||||||
|
// Set annotations
|
||||||
|
contentLayer.Annotations[HelmChartNameAnnotation] = name
|
||||||
|
contentLayer.Annotations[HelmChartVersionAnnotation] = version
|
||||||
|
|
||||||
|
layers := []ocispec.Descriptor{metaLayer, contentLayer}
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *filesystemCache) LoadReference(ref *Reference) ([]ocispec.Descriptor, error) {
|
||||||
|
tagDir := filepath.Join(cache.rootDir, "refs", escape(ref.Locator), "tags", tagOrDefault(ref.Object))
|
||||||
|
|
||||||
|
// add meta layer
|
||||||
|
metaJSONRaw, err := getSymlinkDestContent(filepath.Join(tagDir, "meta"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metaLayer := cache.store.Add(HelmChartMetaFileName, HelmChartMetaMediaType, metaJSONRaw)
|
||||||
|
|
||||||
|
// add content layer
|
||||||
|
contentRaw, err := getSymlinkDestContent(filepath.Join(tagDir, "content"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentLayer := cache.store.Add(HelmChartContentFileName, HelmChartContentMediaType, contentRaw)
|
||||||
|
|
||||||
|
// set annotations on content layer (chart name and version)
|
||||||
|
err = setLayerAnnotationsFromChartLink(contentLayer, filepath.Join(tagDir, "chart"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
printChartSummary(cache.out, metaLayer, contentLayer)
|
||||||
|
layers := []ocispec.Descriptor{metaLayer, contentLayer}
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *filesystemCache) StoreReference(ref *Reference, layers []ocispec.Descriptor) (bool, error) {
|
||||||
|
tag := tagOrDefault(ref.Object)
|
||||||
|
tagDir := mkdir(filepath.Join(cache.rootDir, "refs", escape(ref.Locator), "tags", tag))
|
||||||
|
|
||||||
|
// Retrieve just the meta and content layers
|
||||||
|
metaLayer, contentLayer, err := extractLayers(layers)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract chart name and version
|
||||||
|
name, version, err := extractChartNameVersionFromLayer(contentLayer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create chart file
|
||||||
|
chartPath, err := createChartFile(filepath.Join(cache.rootDir, "charts"), name, version)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create chart symlink
|
||||||
|
err = createSymlink(chartPath, filepath.Join(tagDir, "chart"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save meta blob
|
||||||
|
metaExists, metaPath := digestPath(filepath.Join(cache.rootDir, "blobs"), metaLayer.Digest)
|
||||||
|
if !metaExists {
|
||||||
|
fmt.Fprintf(cache.out, "%s: Saving meta (%s)\n",
|
||||||
|
shortDigest(metaLayer.Digest.Hex()), byteCountBinary(metaLayer.Size))
|
||||||
|
_, metaJSONRaw, ok := cache.store.Get(metaLayer)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("error retrieving meta layer")
|
||||||
|
}
|
||||||
|
err = writeFile(metaPath, metaJSONRaw)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create meta symlink
|
||||||
|
err = createSymlink(metaPath, filepath.Join(tagDir, "meta"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save content blob
|
||||||
|
contentExists, contentPath := digestPath(filepath.Join(cache.rootDir, "blobs"), contentLayer.Digest)
|
||||||
|
if !contentExists {
|
||||||
|
fmt.Fprintf(cache.out, "%s: Saving content (%s)\n",
|
||||||
|
shortDigest(contentLayer.Digest.Hex()), byteCountBinary(contentLayer.Size))
|
||||||
|
_, contentRaw, ok := cache.store.Get(contentLayer)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("error retrieving content layer")
|
||||||
|
}
|
||||||
|
err = writeFile(contentPath, contentRaw)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create content symlink
|
||||||
|
err = createSymlink(contentPath, filepath.Join(tagDir, "content"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
printChartSummary(cache.out, metaLayer, contentLayer)
|
||||||
|
return metaExists && contentExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *filesystemCache) DeleteReference(ref *Reference) error {
|
||||||
|
tagDir := filepath.Join(cache.rootDir, "refs", escape(ref.Locator), "tags", tagOrDefault(ref.Object))
|
||||||
|
if _, err := os.Stat(tagDir); os.IsNotExist(err) {
|
||||||
|
return errors.New("ref not found")
|
||||||
|
}
|
||||||
|
return os.RemoveAll(tagDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *filesystemCache) TableRows() ([][]interface{}, error) {
|
||||||
|
return getRefsSorted(filepath.Join(cache.rootDir, "refs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape sanitizes a registry URL to remove characters such as ":"
|
||||||
|
// which are illegal on windows
|
||||||
|
func escape(s string) string {
|
||||||
|
return strings.Replace(s, ":", "_", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape reverses escape
|
||||||
|
func unescape(s string) string {
|
||||||
|
return strings.Replace(s, "_", ":", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printChartSummary prints details about a chart layers
|
||||||
|
func printChartSummary(out io.Writer, metaLayer ocispec.Descriptor, contentLayer ocispec.Descriptor) {
|
||||||
|
fmt.Fprintf(out, "Name: %s\n", contentLayer.Annotations[HelmChartNameAnnotation])
|
||||||
|
fmt.Fprintf(out, "Version: %s\n", contentLayer.Annotations[HelmChartVersionAnnotation])
|
||||||
|
fmt.Fprintf(out, "Meta: %s\n", metaLayer.Digest)
|
||||||
|
fmt.Fprintf(out, "Content: %s\n", contentLayer.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileExists determines if a file exists
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdir will create a directory (no error check) and return the path
|
||||||
|
func mkdir(dir string) string {
|
||||||
|
os.MkdirAll(dir, 0755)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSymlink creates a symbolic link, deleting existing one if exists
|
||||||
|
func createSymlink(src string, dest string) error {
|
||||||
|
os.Remove(dest)
|
||||||
|
err := os.Symlink(src, dest)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSymlinkDestContent returns the file contents of a symlink's destination
|
||||||
|
func getSymlinkDestContent(linkPath string) ([]byte, error) {
|
||||||
|
src, err := os.Readlink(linkPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.ReadFile(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLayerAnnotationsFromChartLink will set chart name/version annotations on a layer
|
||||||
|
// based on the path of the chart link destination
|
||||||
|
func setLayerAnnotationsFromChartLink(layer ocispec.Descriptor, chartLinkPath string) error {
|
||||||
|
src, err := os.Readlink(chartLinkPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// example path: /some/path/charts/mychart/versions/1.2.0
|
||||||
|
chartName := filepath.Base(filepath.Dir(filepath.Dir(src)))
|
||||||
|
chartVersion := filepath.Base(src)
|
||||||
|
layer.Annotations[HelmChartNameAnnotation] = chartName
|
||||||
|
layer.Annotations[HelmChartVersionAnnotation] = chartVersion
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractLayers obtains the meta and content layers from a list of layers
|
||||||
|
func extractLayers(layers []ocispec.Descriptor) (ocispec.Descriptor, ocispec.Descriptor, error) {
|
||||||
|
var metaLayer, contentLayer ocispec.Descriptor
|
||||||
|
|
||||||
|
if len(layers) != 2 {
|
||||||
|
return metaLayer, contentLayer, errors.New("manifest does not contain exactly 2 layers")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, layer := range layers {
|
||||||
|
switch layer.MediaType {
|
||||||
|
case HelmChartMetaMediaType:
|
||||||
|
metaLayer = layer
|
||||||
|
case HelmChartContentMediaType:
|
||||||
|
contentLayer = layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if metaLayer.Size == 0 {
|
||||||
|
return metaLayer, contentLayer, errors.New("manifest does not contain a Helm chart meta layer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentLayer.Size == 0 {
|
||||||
|
return metaLayer, contentLayer, errors.New("manifest does not contain a Helm chart content layer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return metaLayer, contentLayer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractChartNameVersionFromLayer retrieves the chart name and version from layer annotations
|
||||||
|
func extractChartNameVersionFromLayer(layer ocispec.Descriptor) (string, string, error) {
|
||||||
|
name, ok := layer.Annotations[HelmChartNameAnnotation]
|
||||||
|
if !ok {
|
||||||
|
return "", "", errors.New("could not find chart name in annotations")
|
||||||
|
}
|
||||||
|
version, ok := layer.Annotations[HelmChartVersionAnnotation]
|
||||||
|
if !ok {
|
||||||
|
return "", "", errors.New("could not find chart version in annotations")
|
||||||
|
}
|
||||||
|
return name, version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createChartFile creates a file under "<chartsdir>" dir which is linked to by ref
|
||||||
|
func createChartFile(chartsRootDir string, name string, version string) (string, error) {
|
||||||
|
chartPathDir := filepath.Join(chartsRootDir, name, "versions")
|
||||||
|
chartPath := filepath.Join(chartPathDir, version)
|
||||||
|
if _, err := os.Stat(chartPath); err != nil && os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(chartPathDir, 0755)
|
||||||
|
err := ioutil.WriteFile(chartPath, []byte("-"), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chartPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digestPath returns the path to addressable content, and whether the file exists
|
||||||
|
func digestPath(rootDir string, digest checksum.Digest) (bool, string) {
|
||||||
|
path := filepath.Join(rootDir, "sha256", digest.Hex())
|
||||||
|
exists := fileExists(path)
|
||||||
|
return exists, path
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFile creates a path, ensuring parent directory
|
||||||
|
func writeFile(path string, c []byte) error {
|
||||||
|
os.MkdirAll(filepath.Dir(path), 0755)
|
||||||
|
return ioutil.WriteFile(path, c, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// byteCountBinary produces a human-readable file size
|
||||||
|
func byteCountBinary(b int64) string {
|
||||||
|
const unit = 1024
|
||||||
|
if b < unit {
|
||||||
|
return fmt.Sprintf("%d B", b)
|
||||||
|
}
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := b / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagOrDefault returns the tag if present, if not the default tag
|
||||||
|
func tagOrDefault(tag string) string {
|
||||||
|
if tag != "" {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
return HelmChartDefaultTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortDigest returns first 7 characters of a sha256 digest
|
||||||
|
func shortDigest(digest string) string {
|
||||||
|
if len(digest) == 64 {
|
||||||
|
return digest[:7]
|
||||||
|
}
|
||||||
|
return digest
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRefsSorted returns a map of all refs stored in a refsRootDir
|
||||||
|
func getRefsSorted(refsRootDir string) ([][]interface{}, error) {
|
||||||
|
refsMap := map[string]map[string]string{}
|
||||||
|
|
||||||
|
// Walk the storage dir, check for symlinks under "refs" dir pointing to valid files in "blobs/" and "charts/"
|
||||||
|
err := filepath.Walk(refsRootDir, func(path string, fileInfo os.FileInfo, fileError error) error {
|
||||||
|
|
||||||
|
// Check if this file is a symlink
|
||||||
|
linkPath, err := os.Readlink(path)
|
||||||
|
if err == nil {
|
||||||
|
destFileInfo, err := os.Stat(linkPath)
|
||||||
|
if err == nil {
|
||||||
|
tagDir := filepath.Dir(path)
|
||||||
|
|
||||||
|
// Determine the ref
|
||||||
|
locator := unescape(strings.TrimLeft(
|
||||||
|
strings.TrimPrefix(filepath.Dir(filepath.Dir(tagDir)), refsRootDir), "/\\"))
|
||||||
|
object := filepath.Base(tagDir)
|
||||||
|
ref := fmt.Sprintf("%s:%s", locator, object)
|
||||||
|
|
||||||
|
// Init hashmap entry if does not exist
|
||||||
|
if _, ok := refsMap[ref]; !ok {
|
||||||
|
refsMap[ref] = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data to entry based on file name (symlink name)
|
||||||
|
base := filepath.Base(path)
|
||||||
|
switch base {
|
||||||
|
case "chart":
|
||||||
|
refsMap[ref]["name"] = filepath.Base(filepath.Dir(filepath.Dir(linkPath)))
|
||||||
|
refsMap[ref]["version"] = destFileInfo.Name()
|
||||||
|
case "content":
|
||||||
|
|
||||||
|
// Make sure the filename looks like a sha256 digest (64 chars)
|
||||||
|
digest := destFileInfo.Name()
|
||||||
|
if len(digest) == 64 {
|
||||||
|
refsMap[ref]["digest"] = shortDigest(digest)
|
||||||
|
refsMap[ref]["size"] = byteCountBinary(destFileInfo.Size())
|
||||||
|
refsMap[ref]["created"] = units.HumanDuration(time.Now().UTC().Sub(destFileInfo.ModTime()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter out any refs that are incomplete (do not have all required fields)
|
||||||
|
for k, ref := range refsMap {
|
||||||
|
allKeysFound := true
|
||||||
|
for _, v := range tableHeaders {
|
||||||
|
if _, ok := ref[v]; !ok {
|
||||||
|
allKeysFound = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allKeysFound {
|
||||||
|
delete(refsMap, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort and convert to format expected by uitable
|
||||||
|
refs := make([][]interface{}, len(refsMap))
|
||||||
|
keys := make([]string, 0, len(refsMap))
|
||||||
|
for key := range refsMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for i, key := range keys {
|
||||||
|
refs[i] = make([]interface{}, len(tableHeaders)+1)
|
||||||
|
refs[i][0] = key
|
||||||
|
ref := refsMap[key]
|
||||||
|
for j, k := range tableHeaders {
|
||||||
|
refs[i][j+1] = ref[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return refs, err
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
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 "k8s.io/helm/pkg/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
orascontent "github.com/deislabs/oras/pkg/content"
|
||||||
|
"github.com/deislabs/oras/pkg/oras"
|
||||||
|
"github.com/gosuri/uitable"
|
||||||
|
|
||||||
|
"k8s.io/helm/pkg/chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ClientOptions is used to construct a new client
|
||||||
|
ClientOptions struct {
|
||||||
|
Out io.Writer
|
||||||
|
Resolver Resolver
|
||||||
|
CacheRootDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client works with OCI-compliant registries and local Helm chart cache
|
||||||
|
Client struct {
|
||||||
|
out io.Writer
|
||||||
|
resolver Resolver
|
||||||
|
cache *filesystemCache // TODO: something more robust
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewClient returns a new registry client with config
|
||||||
|
func NewClient(options *ClientOptions) *Client {
|
||||||
|
return &Client{
|
||||||
|
out: options.Out,
|
||||||
|
resolver: options.Resolver,
|
||||||
|
cache: &filesystemCache{
|
||||||
|
out: options.Out,
|
||||||
|
rootDir: options.CacheRootDir,
|
||||||
|
store: orascontent.NewMemoryStore(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushChart uploads a chart to a registry
|
||||||
|
func (c *Client) PushChart(ref *Reference) error {
|
||||||
|
c.setDefaultTag(ref)
|
||||||
|
fmt.Fprintf(c.out, "The push refers to repository [%s]\n", ref.Locator)
|
||||||
|
layers, err := c.cache.LoadReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = oras.Push(context.Background(), c.resolver, ref.String(), c.cache.store, layers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var totalSize int64
|
||||||
|
for _, layer := range layers {
|
||||||
|
totalSize += layer.Size
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c.out,
|
||||||
|
"%s: pushed to remote (%d layers, %s total)\n", ref.Object, len(layers), byteCountBinary(totalSize))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullChart downloads a chart from a registry
|
||||||
|
func (c *Client) PullChart(ref *Reference) error {
|
||||||
|
c.setDefaultTag(ref)
|
||||||
|
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Object, ref.Locator)
|
||||||
|
layers, err := oras.Pull(context.Background(), c.resolver, ref.String(), c.cache.store, KnownMediaTypes()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exists, err := c.cache.StoreReference(ref, layers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
fmt.Fprintf(c.out, "Status: Downloaded newer chart for %s:%s\n", ref.Locator, ref.Object)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(c.out, "Status: Chart is up to date for %s:%s\n", ref.Locator, ref.Object)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveChart stores a copy of chart in local cache
|
||||||
|
func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error {
|
||||||
|
c.setDefaultTag(ref)
|
||||||
|
layers, err := c.cache.ChartToLayers(ch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.cache.StoreReference(ref, layers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c.out, "%s: saved\n", ref.Object)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadChart retrieves a chart object by reference
|
||||||
|
func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) {
|
||||||
|
c.setDefaultTag(ref)
|
||||||
|
layers, err := c.cache.LoadReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ch, err := c.cache.LayersToChart(layers)
|
||||||
|
return ch, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChart deletes a locally saved chart
|
||||||
|
func (c *Client) RemoveChart(ref *Reference) error {
|
||||||
|
c.setDefaultTag(ref)
|
||||||
|
err := c.cache.DeleteReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c.out, "%s: removed\n", ref.Object)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintChartTable prints a list of locally stored charts
|
||||||
|
func (c *Client) PrintChartTable() error {
|
||||||
|
table := uitable.New()
|
||||||
|
table.MaxColWidth = 60
|
||||||
|
table.AddRow("REF", "NAME", "VERSION", "DIGEST", "SIZE", "CREATED")
|
||||||
|
rows, err := c.cache.TableRows()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
table.AddRow(row...)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(c.out, table.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setDefaultTag(ref *Reference) {
|
||||||
|
if ref.Object == "" {
|
||||||
|
ref.Object = HelmChartDefaultTag
|
||||||
|
fmt.Fprintf(c.out, "Using default tag: %s\n", HelmChartDefaultTag)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"k8s.io/helm/pkg/chart"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/remotes/docker"
|
||||||
|
"github.com/docker/distribution/configuration"
|
||||||
|
"github.com/docker/distribution/registry"
|
||||||
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testCacheRootDir = "helm-registry-test"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegistryClientTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
Out io.Writer
|
||||||
|
DockerRegistryHost string
|
||||||
|
CacheRootDir string
|
||||||
|
RegistryClient *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) SetupSuite() {
|
||||||
|
suite.CacheRootDir = testCacheRootDir
|
||||||
|
|
||||||
|
// Init test client
|
||||||
|
var out bytes.Buffer
|
||||||
|
suite.Out = &out
|
||||||
|
suite.RegistryClient = NewClient(&ClientOptions{
|
||||||
|
Out: suite.Out,
|
||||||
|
Resolver: Resolver{
|
||||||
|
Resolver: docker.NewResolver(docker.ResolverOptions{}),
|
||||||
|
},
|
||||||
|
CacheRootDir: suite.CacheRootDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Registry config
|
||||||
|
config := &configuration.Configuration{}
|
||||||
|
port, err := getFreePort()
|
||||||
|
if err != nil {
|
||||||
|
suite.Nil(err, "no error finding free port for test registry")
|
||||||
|
}
|
||||||
|
suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port)
|
||||||
|
config.HTTP.Addr = fmt.Sprintf(":%d", port)
|
||||||
|
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
|
||||||
|
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
|
||||||
|
dockerRegistry, err := registry.NewRegistry(context.Background(), config)
|
||||||
|
suite.Nil(err, "no error creating test registry")
|
||||||
|
|
||||||
|
// Start Docker registry
|
||||||
|
go dockerRegistry.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) TearDownSuite() {
|
||||||
|
os.RemoveAll(suite.CacheRootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_0_SaveChart() {
|
||||||
|
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
// empty chart
|
||||||
|
err = suite.RegistryClient.SaveChart(&chart.Chart{}, ref)
|
||||||
|
suite.NotNil(err)
|
||||||
|
|
||||||
|
// valid chart
|
||||||
|
ch := &chart.Chart{}
|
||||||
|
ch.Metadata = &chart.Metadata{
|
||||||
|
Name: "testchart",
|
||||||
|
Version: "1.2.3",
|
||||||
|
}
|
||||||
|
err = suite.RegistryClient.SaveChart(ch, ref)
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_1_LoadChart() {
|
||||||
|
|
||||||
|
// non-existent ref
|
||||||
|
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
ch, err := suite.RegistryClient.LoadChart(ref)
|
||||||
|
suite.NotNil(err)
|
||||||
|
|
||||||
|
// existing ref
|
||||||
|
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
ch, err = suite.RegistryClient.LoadChart(ref)
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.Equal("testchart", ch.Metadata.Name)
|
||||||
|
suite.Equal("1.2.3", ch.Metadata.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_2_PushChart() {
|
||||||
|
|
||||||
|
// non-existent ref
|
||||||
|
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.PushChart(ref)
|
||||||
|
suite.NotNil(err)
|
||||||
|
|
||||||
|
// existing ref
|
||||||
|
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.PushChart(ref)
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_3_PullChart() {
|
||||||
|
|
||||||
|
// non-existent ref
|
||||||
|
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.PullChart(ref)
|
||||||
|
suite.NotNil(err)
|
||||||
|
|
||||||
|
// existing ref
|
||||||
|
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.PullChart(ref)
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_4_PrintChartTable() {
|
||||||
|
err := suite.RegistryClient.PrintChartTable()
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RegistryClientTestSuite) Test_5_RemoveChart() {
|
||||||
|
|
||||||
|
// non-existent ref
|
||||||
|
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.RemoveChart(ref)
|
||||||
|
suite.NotNil(err)
|
||||||
|
|
||||||
|
// existing ref
|
||||||
|
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
|
||||||
|
suite.Nil(err)
|
||||||
|
err = suite.RegistryClient.RemoveChart(ref)
|
||||||
|
suite.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistryClientTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(RegistryClientTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
// borrowed from https://github.com/phayes/freeport
|
||||||
|
func getFreePort() (int, error) {
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.ListenTCP("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
return l.Addr().(*net.TCPAddr).Port, nil
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
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 "k8s.io/helm/pkg/registry"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HelmChartDefaultTag is the default tag used when storing a chart reference with no tag
|
||||||
|
HelmChartDefaultTag = "latest"
|
||||||
|
|
||||||
|
// HelmChartMetaMediaType is the reserved media type for Helm chart metadata
|
||||||
|
HelmChartMetaMediaType = "application/vnd.cncf.helm.chart.meta.v1+json"
|
||||||
|
|
||||||
|
// HelmChartContentMediaType is the reserved media type for Helm chart package content
|
||||||
|
HelmChartContentMediaType = "application/vnd.cncf.helm.chart.content.v1+tar"
|
||||||
|
|
||||||
|
// HelmChartMetaFileName is the reserved file name for Helm chart metadata
|
||||||
|
HelmChartMetaFileName = "chart-meta.json"
|
||||||
|
|
||||||
|
// HelmChartContentFileName is the reserved file name for Helm chart package content
|
||||||
|
HelmChartContentFileName = "chart-content.tgz"
|
||||||
|
|
||||||
|
// HelmChartNameAnnotation is the reserved annotation key for Helm chart name
|
||||||
|
HelmChartNameAnnotation = "sh.helm.chart.name"
|
||||||
|
|
||||||
|
// HelmChartVersionAnnotation is the reserved annotation key for Helm chart version
|
||||||
|
HelmChartVersionAnnotation = "sh.helm.chart.version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KnownMediaTypes returns a list of layer mediaTypes that the Helm client knows about
|
||||||
|
func KnownMediaTypes() []string {
|
||||||
|
return []string{
|
||||||
|
HelmChartMetaMediaType,
|
||||||
|
HelmChartContentMediaType,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
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 TestConstants(t *testing.T) {
|
||||||
|
knownMediaTypes := KnownMediaTypes()
|
||||||
|
assert.Contains(t, knownMediaTypes, HelmChartMetaMediaType)
|
||||||
|
assert.Contains(t, knownMediaTypes, HelmChartContentMediaType)
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
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 "k8s.io/helm/pkg/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/reference"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Reference defines the main components of a reference specification
|
||||||
|
Reference struct {
|
||||||
|
*reference.Spec
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseReference converts a string to a Reference
|
||||||
|
func ParseReference(s string) (*Reference, error) {
|
||||||
|
spec, err := reference.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ref := Reference{&spec}
|
||||||
|
return &ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repo returns a reference's repo minus the hostname
|
||||||
|
func (ref *Reference) Repo() string {
|
||||||
|
return strings.TrimPrefix(strings.TrimPrefix(ref.Locator, ref.Hostname()), "/")
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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 TestReference(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
// bad ref
|
||||||
|
s := ""
|
||||||
|
_, err := ParseReference(s)
|
||||||
|
is.Error(err)
|
||||||
|
|
||||||
|
// good refs
|
||||||
|
s = "localhost:5000/mychart:latest"
|
||||||
|
ref, err := ParseReference(s)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal("localhost:5000", ref.Hostname())
|
||||||
|
is.Equal("mychart", ref.Repo())
|
||||||
|
is.Equal("localhost:5000/mychart", ref.Locator)
|
||||||
|
is.Equal("latest", ref.Object)
|
||||||
|
|
||||||
|
s = "my.host.com/my/nested/repo:1.2.3"
|
||||||
|
ref, err = ParseReference(s)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal("my.host.com", ref.Hostname())
|
||||||
|
is.Equal("my/nested/repo", ref.Repo())
|
||||||
|
is.Equal("my.host.com/my/nested/repo", ref.Locator)
|
||||||
|
is.Equal("1.2.3", ref.Object)
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
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 "k8s.io/helm/pkg/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/remotes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Resolver provides remotes based on a locator
|
||||||
|
Resolver struct {
|
||||||
|
remotes.Resolver
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in new issue