update vendor

Signed-off-by: Roland.Ma <rolandma@yunify.com>
This commit is contained in:
Roland.Ma
2021-08-11 07:10:14 +00:00
parent a18f72b565
commit ea8f47c73a
2901 changed files with 269317 additions and 43103 deletions

View 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
}
)

View 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
}

View 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
}
}

View 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
}

View 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
}
}

View 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,
}
}

View 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
}

View 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
}
)

View 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
}