28
vendor/helm.sh/helm/v3/internal/experimental/registry/authorizer.go
vendored
Normal file
28
vendor/helm.sh/helm/v3/internal/experimental/registry/authorizer.go
vendored
Normal file
@@ -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 "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"github.com/deislabs/oras/pkg/auth"
|
||||
)
|
||||
|
||||
type (
|
||||
// Authorizer handles registry auth operations
|
||||
Authorizer struct {
|
||||
auth.Client
|
||||
}
|
||||
)
|
||||
368
vendor/helm.sh/helm/v3/internal/experimental/registry/cache.go
vendored
Normal file
368
vendor/helm.sh/helm/v3/internal/experimental/registry/cache.go
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
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"
|
||||
orascontent "github.com/deislabs/oras/pkg/content"
|
||||
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"
|
||||
|
||||
"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
|
||||
}
|
||||
48
vendor/helm.sh/helm/v3/internal/experimental/registry/cache_opts.go
vendored
Normal file
48
vendor/helm.sh/helm/v3/internal/experimental/registry/cache_opts.go
vendored
Normal file
@@ -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 "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
|
||||
}
|
||||
}
|
||||
336
vendor/helm.sh/helm/v3/internal/experimental/registry/client.go
vendored
Normal file
336
vendor/helm.sh/helm/v3/internal/experimental/registry/client.go
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
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"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
auth "github.com/deislabs/oras/pkg/auth/docker"
|
||||
"github.com/deislabs/oras/pkg/content"
|
||||
"github.com/deislabs/oras/pkg/oras"
|
||||
"github.com/gosuri/uitable"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/helmpath"
|
||||
)
|
||||
|
||||
const (
|
||||
// CredentialsFileBasename is the filename for auth credentials file
|
||||
CredentialsFileBasename = "config.json"
|
||||
)
|
||||
|
||||
type (
|
||||
// Client works with OCI-compliant registries and local Helm chart cache
|
||||
Client struct {
|
||||
debug bool
|
||||
// path to repository config file e.g. ~/.docker/config.json
|
||||
credentialsFile string
|
||||
out io.Writer
|
||||
authorizer *Authorizer
|
||||
resolver *Resolver
|
||||
cache *Cache
|
||||
}
|
||||
)
|
||||
|
||||
// NewClient returns a new registry client with config
|
||||
func NewClient(opts ...ClientOption) (*Client, error) {
|
||||
client := &Client{
|
||||
out: ioutil.Discard,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(client)
|
||||
}
|
||||
// set defaults if fields are missing
|
||||
if client.credentialsFile == "" {
|
||||
client.credentialsFile = helmpath.CachePath("registry", CredentialsFileBasename)
|
||||
}
|
||||
if client.authorizer == nil {
|
||||
authClient, err := auth.NewClient(client.credentialsFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.authorizer = &Authorizer{
|
||||
Client: authClient,
|
||||
}
|
||||
}
|
||||
if client.resolver == nil {
|
||||
resolver, err := client.authorizer.Resolver(context.Background(), http.DefaultClient, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.resolver = &Resolver{
|
||||
Resolver: resolver,
|
||||
}
|
||||
}
|
||||
if client.cache == nil {
|
||||
cache, err := NewCache(
|
||||
CacheOptDebug(client.debug),
|
||||
CacheOptWriter(client.out),
|
||||
CacheOptRoot(helmpath.CachePath("registry", CacheRootDir)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.cache = cache
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Login logs into a registry
|
||||
func (c *Client) Login(hostname string, username string, password string, insecure bool) error {
|
||||
err := c.authorizer.Login(ctx(c.out, c.debug), hostname, username, password, insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "Login succeeded\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout logs out of a registry
|
||||
func (c *Client) Logout(hostname string) error {
|
||||
err := c.authorizer.Logout(ctx(c.out, c.debug), hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(c.out, "Logout succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushChart uploads a chart to a registry
|
||||
func (c *Client) PushChart(ref *Reference) error {
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", r.Name))
|
||||
}
|
||||
fmt.Fprintf(c.out, "The push refers to repository [%s]\n", r.Repo)
|
||||
c.printCacheRefSummary(r)
|
||||
layers := []ocispec.Descriptor{*r.ContentLayer}
|
||||
_, err = oras.Push(ctx(c.out, c.debug), c.resolver, r.Name, c.cache.Provider(), layers,
|
||||
oras.WithConfig(*r.Config), oras.WithNameValidation(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := ""
|
||||
numLayers := len(layers)
|
||||
if 1 < numLayers {
|
||||
s = "s"
|
||||
}
|
||||
fmt.Fprintf(c.out,
|
||||
"%s: pushed to remote (%d layer%s, %s total)\n", r.Tag, numLayers, s, byteCountBinary(r.Size))
|
||||
return nil
|
||||
}
|
||||
|
||||
// PullChart downloads a chart from a registry
|
||||
func (c *Client) PullChart(ref *Reference) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
if ref.Tag == "" {
|
||||
return buf, errors.New("tag explicitly required")
|
||||
}
|
||||
|
||||
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
|
||||
|
||||
store := content.NewMemoryStore()
|
||||
fullname := ref.FullName()
|
||||
_ = fullname
|
||||
_, layerDescriptors, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), store,
|
||||
oras.WithPullEmptyNameAllowed(),
|
||||
oras.WithAllowedMediaTypes(KnownMediaTypes()))
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
|
||||
numLayers := len(layerDescriptors)
|
||||
if numLayers < 1 {
|
||||
return buf, errors.New(
|
||||
fmt.Sprintf("manifest does not contain at least 1 layer (total: %d)", numLayers))
|
||||
}
|
||||
|
||||
var contentLayer *ocispec.Descriptor
|
||||
for _, layer := range layerDescriptors {
|
||||
layer := layer
|
||||
switch layer.MediaType {
|
||||
case HelmChartContentLayerMediaType:
|
||||
contentLayer = &layer
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if contentLayer == nil {
|
||||
return buf, errors.New(
|
||||
fmt.Sprintf("manifest does not contain a layer with mediatype %s",
|
||||
HelmChartContentLayerMediaType))
|
||||
}
|
||||
|
||||
_, b, ok := store.Get(*contentLayer)
|
||||
if !ok {
|
||||
return buf, errors.Errorf("Unable to retrieve blob with digest %s", contentLayer.Digest)
|
||||
}
|
||||
|
||||
buf = bytes.NewBuffer(b)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// PullChartToCache pulls a chart from an OCI Registry to the Registry Cache.
|
||||
// This function is needed for `helm chart pull`, which is experimental and will be deprecated soon.
|
||||
// Likewise, the Registry cache will soon be deprecated as will this function.
|
||||
func (c *Client) PullChartToCache(ref *Reference) error {
|
||||
if ref.Tag == "" {
|
||||
return errors.New("tag explicitly required")
|
||||
}
|
||||
existing, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
|
||||
manifest, _, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), c.cache.Ingester(),
|
||||
oras.WithPullEmptyNameAllowed(),
|
||||
oras.WithAllowedMediaTypes(KnownMediaTypes()),
|
||||
oras.WithContentProvideIngester(c.cache.ProvideIngester()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.cache.AddManifest(ref, &manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", r.Name))
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
if !existing.Exists {
|
||||
fmt.Fprintf(c.out, "Status: Downloaded newer chart for %s\n", ref.FullName())
|
||||
} else {
|
||||
fmt.Fprintf(c.out, "Status: Chart is up to date for %s\n", ref.FullName())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveChart stores a copy of chart in local cache
|
||||
func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error {
|
||||
r, err := c.cache.StoreReference(ref, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
err = c.cache.AddManifest(ref, r.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: saved\n", r.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadChart retrieves a chart object by reference
|
||||
func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) {
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !r.Exists {
|
||||
return nil, errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName()))
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
return r.Chart, nil
|
||||
}
|
||||
|
||||
// RemoveChart deletes a locally saved chart
|
||||
func (c *Client) RemoveChart(ref *Reference) error {
|
||||
r, err := c.cache.DeleteReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName()))
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: removed\n", r.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.getChartTableRows()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
table.AddRow(row...)
|
||||
}
|
||||
fmt.Fprintln(c.out, table.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// printCacheRefSummary prints out chart ref summary
|
||||
func (c *Client) printCacheRefSummary(r *CacheRefSummary) {
|
||||
fmt.Fprintf(c.out, "ref: %s\n", r.Name)
|
||||
fmt.Fprintf(c.out, "digest: %s\n", r.Manifest.Digest.Hex())
|
||||
fmt.Fprintf(c.out, "size: %s\n", byteCountBinary(r.Size))
|
||||
fmt.Fprintf(c.out, "name: %s\n", r.Chart.Metadata.Name)
|
||||
fmt.Fprintf(c.out, "version: %s\n", r.Chart.Metadata.Version)
|
||||
}
|
||||
|
||||
// getChartTableRows returns rows in uitable-friendly format
|
||||
func (c *Client) getChartTableRows() ([][]interface{}, error) {
|
||||
rr, err := c.cache.ListReferences()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refsMap := map[string]map[string]string{}
|
||||
for _, r := range rr {
|
||||
refsMap[r.Name] = map[string]string{
|
||||
"name": r.Chart.Metadata.Name,
|
||||
"version": r.Chart.Metadata.Version,
|
||||
"digest": shortDigest(r.Manifest.Digest.Hex()),
|
||||
"size": byteCountBinary(r.Size),
|
||||
"created": timeAgo(r.CreatedAt),
|
||||
}
|
||||
}
|
||||
// Sort and convert to format expected by uitable
|
||||
rows := 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 {
|
||||
rows[i] = make([]interface{}, 6)
|
||||
rows[i][0] = key
|
||||
ref := refsMap[key]
|
||||
for j, k := range []string{"name", "version", "digest", "size", "created"} {
|
||||
rows[i][j+1] = ref[k]
|
||||
}
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
69
vendor/helm.sh/helm/v3/internal/experimental/registry/client_opts.go
vendored
Normal file
69
vendor/helm.sh/helm/v3/internal/experimental/registry/client_opts.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
}
|
||||
33
vendor/helm.sh/helm/v3/internal/experimental/registry/constants.go
vendored
Normal file
33
vendor/helm.sh/helm/v3/internal/experimental/registry/constants.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
const (
|
||||
// HelmChartConfigMediaType is the reserved media type for the Helm chart manifest config
|
||||
HelmChartConfigMediaType = "application/vnd.cncf.helm.config.v1+json"
|
||||
|
||||
// HelmChartContentLayerMediaType is the reserved media type for Helm chart package content
|
||||
HelmChartContentLayerMediaType = "application/tar+gzip"
|
||||
)
|
||||
|
||||
// KnownMediaTypes returns a list of layer mediaTypes that the Helm client knows about
|
||||
func KnownMediaTypes() []string {
|
||||
return []string{
|
||||
HelmChartConfigMediaType,
|
||||
HelmChartContentLayerMediaType,
|
||||
}
|
||||
}
|
||||
146
vendor/helm.sh/helm/v3/internal/experimental/registry/reference.go
vendored
Normal file
146
vendor/helm.sh/helm/v3/internal/experimental/registry/reference.go
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
28
vendor/helm.sh/helm/v3/internal/experimental/registry/resolver.go
vendored
Normal file
28
vendor/helm.sh/helm/v3/internal/experimental/registry/resolver.go
vendored
Normal file
@@ -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 "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/remotes"
|
||||
)
|
||||
|
||||
type (
|
||||
// Resolver provides remotes based on a locator
|
||||
Resolver struct {
|
||||
remotes.Resolver
|
||||
}
|
||||
)
|
||||
66
vendor/helm.sh/helm/v3/internal/experimental/registry/util.go
vendored
Normal file
66
vendor/helm.sh/helm/v3/internal/experimental/registry/util.go
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
orascontext "github.com/deislabs/oras/pkg/context"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// 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])
|
||||
}
|
||||
|
||||
// shortDigest returns first 7 characters of a sha256 digest
|
||||
func shortDigest(digest string) string {
|
||||
if len(digest) == 64 {
|
||||
return digest[:7]
|
||||
}
|
||||
return digest
|
||||
}
|
||||
|
||||
// timeAgo returns a human-readable timestamp representing time that has passed
|
||||
func timeAgo(t time.Time) string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(t))
|
||||
}
|
||||
|
||||
// ctx retrieves a fresh context.
|
||||
// disable verbose logging coming from ORAS (unless debug is enabled)
|
||||
func ctx(out io.Writer, debug bool) context.Context {
|
||||
if !debug {
|
||||
return orascontext.Background()
|
||||
}
|
||||
ctx := orascontext.WithLoggerFromWriter(context.Background(), out)
|
||||
orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel)
|
||||
return ctx
|
||||
}
|
||||
Reference in New Issue
Block a user