Upgrade k8s package verison (#5358)

* upgrade k8s package version

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

* Script upgrade and code formatting.

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
This commit is contained in:
hongzhouzi
2022-11-15 14:56:38 +08:00
committed by GitHub
parent 5f91c1663a
commit 44167aa47a
3106 changed files with 321340 additions and 172080 deletions

View File

@@ -1,368 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
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

@@ -1,48 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"io"
)
type (
// CacheOption allows specifying various settings configurable by the user for overriding the defaults
// used when creating a new default cache
CacheOption func(*Cache)
)
// CacheOptDebug returns a function that sets the debug setting on cache options set
func CacheOptDebug(debug bool) CacheOption {
return func(cache *Cache) {
cache.debug = debug
}
}
// CacheOptWriter returns a function that sets the writer setting on cache options set
func CacheOptWriter(out io.Writer) CacheOption {
return func(cache *Cache) {
cache.out = out
}
}
// CacheOptRoot returns a function that sets the root directory setting on cache options set
func CacheOptRoot(rootDir string) CacheOption {
return func(cache *Cache) {
cache.rootDir = rootDir
}
}

View File

@@ -1,336 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"bytes"
"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

@@ -1,69 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"io"
)
type (
// ClientOption allows specifying various settings configurable by the user for overriding the defaults
// used when creating a new default client
ClientOption func(*Client)
)
// ClientOptDebug returns a function that sets the debug setting on client options set
func ClientOptDebug(debug bool) ClientOption {
return func(client *Client) {
client.debug = debug
}
}
// ClientOptWriter returns a function that sets the writer setting on client options set
func ClientOptWriter(out io.Writer) ClientOption {
return func(client *Client) {
client.out = out
}
}
// ClientOptResolver returns a function that sets the resolver setting on client options set
func ClientOptResolver(resolver *Resolver) ClientOption {
return func(client *Client) {
client.resolver = resolver
}
}
// ClientOptAuthorizer returns a function that sets the authorizer setting on client options set
func ClientOptAuthorizer(authorizer *Authorizer) ClientOption {
return func(client *Client) {
client.authorizer = authorizer
}
}
// ClientOptCache returns a function that sets the cache setting on a client options set
func ClientOptCache(cache *Cache) ClientOption {
return func(client *Client) {
client.cache = cache
}
}
// ClientOptCredentialsFile returns a function that sets the cache setting on a client options set
func ClientOptCredentialsFile(credentialsFile string) ClientOption {
return func(client *Client) {
client.credentialsFile = credentialsFile
}
}

View File

@@ -1,33 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
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

@@ -1,146 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"errors"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
)
var (
validPortRegEx = regexp.MustCompile(`^([1-9]\d{0,3}|0|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$`) // adapted from https://stackoverflow.com/a/12968117
// TODO: Currently we don't support digests, so we are only splitting on the
// colon. However, when we add support for digests, we'll need to use the
// regexp anyway to split on both colons and @, so leaving it like this for
// now
referenceDelimiter = regexp.MustCompile(`[:]`)
errEmptyRepo = errors.New("parsed repo was empty")
errTooManyColons = errors.New("ref may only contain a single colon character (:) unless specifying a port number")
)
type (
// Reference defines the main components of a reference specification
Reference struct {
Tag string
Repo string
}
)
// ParseReference converts a string to a Reference
func ParseReference(s string) (*Reference, error) {
if s == "" {
return nil, errEmptyRepo
}
// Split the components of the string on the colon or @, if it is more than 3,
// immediately return an error. Other validation will be performed later in
// the function
splitComponents := fixSplitComponents(referenceDelimiter.Split(s, -1))
if len(splitComponents) > 3 {
return nil, errTooManyColons
}
var ref *Reference
switch len(splitComponents) {
case 1:
ref = &Reference{Repo: splitComponents[0]}
case 2:
ref = &Reference{Repo: splitComponents[0], Tag: splitComponents[1]}
case 3:
ref = &Reference{Repo: strings.Join(splitComponents[:2], ":"), Tag: splitComponents[2]}
}
// ensure the reference is valid
err := ref.validate()
if err != nil {
return nil, err
}
return ref, nil
}
// FullName the full name of a reference (repo:tag)
func (ref *Reference) FullName() string {
if ref.Tag == "" {
return ref.Repo
}
return fmt.Sprintf("%s:%s", ref.Repo, ref.Tag)
}
// validate makes sure the ref meets our criteria
func (ref *Reference) validate() error {
err := ref.validateRepo()
if err != nil {
return err
}
return ref.validateNumColons()
}
// validateRepo checks that the Repo field is non-empty
func (ref *Reference) validateRepo() error {
if ref.Repo == "" {
return errEmptyRepo
}
// Makes sure the repo results in a parsable URL (similar to what is done
// with containerd reference parsing)
_, err := url.Parse("//" + ref.Repo)
return err
}
// validateNumColons ensures the ref only contains a single colon character (:)
// (or potentially two, there might be a port number specified i.e. :5000)
func (ref *Reference) validateNumColons() error {
if strings.Contains(ref.Tag, ":") {
return errTooManyColons
}
parts := strings.Split(ref.Repo, ":")
lastIndex := len(parts) - 1
if 1 < lastIndex {
return errTooManyColons
}
if 0 < lastIndex {
port := strings.Split(parts[lastIndex], "/")[0]
if !isValidPort(port) {
return errTooManyColons
}
}
return nil
}
// isValidPort returns whether or not a string looks like a valid port
func isValidPort(s string) bool {
return validPortRegEx.MatchString(s)
}
// fixSplitComponents this will modify reference parts based on presence of port
// Example: {localhost, 5000/x/y/z, 0.1.0} => {localhost:5000/x/y/z, 0.1.0}
func fixSplitComponents(c []string) []string {
if len(c) <= 1 {
return c
}
possiblePortParts := strings.Split(c[1], "/")
if _, err := strconv.Atoi(possiblePortParts[0]); err == nil {
components := []string{strings.Join(c[:2], ":")}
components = append(components, c[2:]...)
return components
}
return c
}

View File

@@ -1,28 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"github.com/containerd/containerd/remotes"
)
type (
// Resolver provides remotes based on a locator
Resolver struct {
remotes.Resolver
}
)

View File

@@ -1,66 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
import (
"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
}

View File

@@ -18,6 +18,7 @@ package resolver
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
@@ -28,25 +29,25 @@ import (
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/gates"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
)
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
// Resolver resolves dependencies from semantic version ranges to a particular version.
type Resolver struct {
chartpath string
cachepath string
chartpath string
cachepath string
registryClient *registry.Client
}
// New creates a new resolver for a given chart and a given helm home.
func New(chartpath, cachepath string) *Resolver {
// New creates a new resolver for a given chart, helm home and registry client.
func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver {
return &Resolver{
chartpath: chartpath,
cachepath: cachepath,
chartpath: chartpath,
cachepath: cachepath,
registryClient: registryClient,
}
}
@@ -121,7 +122,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
var version string
var ok bool
found := true
if !strings.HasPrefix(d.Repository, "oci://") {
if !registry.IsOCI(d.Repository) {
repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName)))
if err != nil {
return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName)
@@ -134,9 +135,36 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
found = false
} else {
version = d.Version
if !FeatureGateOCI.IsEnabled() {
return nil, errors.Wrapf(FeatureGateOCI.Error(),
"repository %s is an OCI registry", d.Repository)
// Check to see if an explicit version has been provided
_, err := semver.NewVersion(version)
// Use an explicit version, otherwise search for tags
if err == nil {
vs = []*repo.ChartVersion{{
Metadata: &chart.Metadata{
Version: version,
},
}}
} else {
// Retrieve list of tags for repository
ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name)
tags, err := r.registryClient.Tags(ref)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve list of tags for repository %s", d.Repository)
}
vs = make(repo.ChartVersions, len(tags))
for ti, t := range tags {
// Mock chart version objects
version := &repo.ChartVersion{
Metadata: &chart.Metadata{
Version: t,
},
}
vs[ti] = version
}
}
}
@@ -148,7 +176,8 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
// The version are already sorted and hence the first one to satisfy the constraint is used
for _, ver := range vs {
v, err := semver.NewVersion(ver.Version)
if err != nil || len(ver.URLs) == 0 {
// OCI does not need URLs
if err != nil || (!registry.IsOCI(d.Repository) && len(ver.URLs) == 0) {
// Not a legit entry.
continue
}

View File

@@ -71,7 +71,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if err != nil {
return errors.Wrapf(err, "error evaluating symlink %s", path)
}
log.Printf("found symbolic link in path: %s resolves to %s", path, resolved)
log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved)
if info, err = os.Lstat(resolved); err != nil {
return err
}

View File

@@ -33,7 +33,6 @@ package fs
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -119,7 +118,7 @@ func CopyDir(src, dst string) error {
return errors.Wrapf(err, "cannot mkdir %s", dst)
}
entries, err := ioutil.ReadDir(src)
entries, err := os.ReadDir(src)
if err != nil {
return errors.Wrapf(err, "cannot read directory %s", dst)
}

View File

@@ -1,4 +1,4 @@
// +build !windows
//go:build !windows
/*
Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
/*
Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under

View File

@@ -29,7 +29,7 @@ var (
//
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
version = "v3.6"
version = "v3.9"
// metadata is extra build time data
metadata = ""

View File

@@ -32,12 +32,12 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/storage"
@@ -102,11 +102,11 @@ type Configuration struct {
// TODO: This function is badly in need of a refactor.
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
// This code has to do with writing files to disk.
func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) {
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{}
b := bytes.NewBuffer(nil)
caps, err := c.getCapabilities()
caps, err := cfg.getCapabilities()
if err != nil {
return hs, b, "", err
}
@@ -125,12 +125,12 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
// is mocked. It is not up to the template author to decide when the user wants to
// connect to the cluster. So when the user says to dry run, respect the user's
// wishes and do not connect to the cluster.
if !dryRun && c.RESTClientGetter != nil {
rest, err := c.RESTClientGetter.ToRESTConfig()
if !dryRun && cfg.RESTClientGetter != nil {
restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil {
return hs, b, "", err
}
files, err2 = engine.RenderWithClient(ch, values, rest)
files, err2 = engine.RenderWithClient(ch, values, restConfig)
} else {
files, err2 = engine.Render(ch, values)
}
@@ -236,11 +236,11 @@ type RESTClientGetter interface {
type DebugLog func(format string, v ...interface{})
// capabilities builds a Capabilities from discovery information.
func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if c.Capabilities != nil {
return c.Capabilities, nil
func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if cfg.Capabilities != nil {
return cfg.Capabilities, nil
}
dc, err := c.RESTClientGetter.ToDiscoveryClient()
dc, err := cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil {
return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
}
@@ -258,27 +258,28 @@ func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
apiVersions, err := GetVersionSet(dc)
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
c.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
c.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
cfg.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
} else {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
}
}
c.Capabilities = &chartutil.Capabilities{
cfg.Capabilities = &chartutil.Capabilities{
APIVersions: apiVersions,
KubeVersion: chartutil.KubeVersion{
Version: kubeVersion.GitVersion,
Major: kubeVersion.Major,
Minor: kubeVersion.Minor,
},
HelmVersion: chartutil.DefaultCapabilities.HelmVersion,
}
return c.Capabilities, nil
return cfg.Capabilities, nil
}
// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration
func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
conf, err := c.RESTClientGetter.ToRESTConfig()
func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
conf, err := cfg.RESTClientGetter.ToRESTConfig()
if err != nil {
return nil, errors.Wrap(err, "unable to generate config for kubernetes client")
}
@@ -290,20 +291,20 @@ func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
//
// If the configuration has a Timestamper on it, that will be used.
// Otherwise, this will use time.Now().
func (c *Configuration) Now() time.Time {
func (cfg *Configuration) Now() time.Time {
return Timestamper()
}
func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
}
if version <= 0 {
return c.Releases.Last(name)
return cfg.Releases.Last(name)
}
return c.Releases.Get(name, version)
return cfg.Releases.Get(name, version)
}
// GetVersionSet retrieves a set of available k8s API versions
@@ -355,14 +356,14 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.Version
}
// recordRelease with an update operation in case reuse has been set.
func (c *Configuration) recordRelease(r *release.Release) {
if err := c.Releases.Update(r); err != nil {
c.Log("warning: Failed to update release %s: %s", r.Name, err)
func (cfg *Configuration) recordRelease(r *release.Release) {
if err := cfg.Releases.Update(r); err != nil {
cfg.Log("warning: Failed to update release %s: %s", r.Name, err)
}
}
// Init initializes the action configuration
func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error {
func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error {
kc := kube.New(getter)
kc.Log = log
@@ -383,8 +384,8 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac
store = storage.Init(d)
case "memory":
var d *driver.Memory
if c.Releases != nil {
if mem, ok := c.Releases.Driver.(*driver.Memory); ok {
if cfg.Releases != nil {
if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok {
// This function can be called more than once (e.g., helm list --all-namespaces).
// If a memory driver was already initialized, re-use it but set the possibly new namespace.
// We re-use it in case some releases where already created in the existing memory driver.
@@ -411,10 +412,10 @@ func (c *Configuration) Init(getter genericclioptions.RESTClientGetter, namespac
panic("Unknown driver in HELM_DRIVER: " + helmDriver)
}
c.RESTClientGetter = getter
c.KubeClient = kc
c.Releases = store
c.Log = log
cfg.RESTClientGetter = getter
cfg.KubeClient = kc
cfg.Releases = store
cfg.Log = log
return nil
}

View File

@@ -1,63 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"fmt"
"io"
"path/filepath"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chartutil"
)
// ChartExport performs a chart export operation.
type ChartExport struct {
cfg *Configuration
Destination string
}
// NewChartExport creates a new ChartExport object with the given configuration.
func NewChartExport(cfg *Configuration) *ChartExport {
return &ChartExport{
cfg: cfg,
}
}
// Run executes the chart export operation
func (a *ChartExport) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
ch, err := a.cfg.RegistryClient.LoadChart(r)
if err != nil {
return err
}
// Save the chart to local destination directory
err = chartutil.SaveDir(ch, a.Destination)
if err != nil {
return err
}
d := filepath.Join(a.Destination, ch.Metadata.Name)
fmt.Fprintf(out, "Exported chart to %s/\n", d)
return nil
}

View File

@@ -1,38 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
)
// 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()
}

View File

@@ -1,44 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
"helm.sh/helm/v3/internal/experimental/registry"
)
// ChartPull performs a chart pull operation.
type ChartPull struct {
cfg *Configuration
}
// NewChartPull creates a new ChartPull object with the given configuration.
func NewChartPull(cfg *Configuration) *ChartPull {
return &ChartPull{
cfg: cfg,
}
}
// Run executes the chart pull operation
func (a *ChartPull) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
return a.cfg.RegistryClient.PullChartToCache(r)
}

View File

@@ -1,44 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
"helm.sh/helm/v3/internal/experimental/registry"
)
// ChartPush performs a chart push operation.
type ChartPush struct {
cfg *Configuration
}
// NewChartPush creates a new ChartPush object with the given configuration.
func NewChartPush(cfg *Configuration) *ChartPush {
return &ChartPush{
cfg: cfg,
}
}
// Run executes the chart push operation
func (a *ChartPush) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
return a.cfg.RegistryClient.PushChart(r)
}

View File

@@ -1,44 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
"helm.sh/helm/v3/internal/experimental/registry"
)
// ChartRemove performs a chart remove operation.
type ChartRemove struct {
cfg *Configuration
}
// NewChartRemove creates a new ChartRemove object with the given configuration.
func NewChartRemove(cfg *Configuration) *ChartRemove {
return &ChartRemove{
cfg: cfg,
}
}
// Run executes the chart remove operation
func (a *ChartRemove) Run(out io.Writer, ref string) error {
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
return a.cfg.RegistryClient.RemoveChart(r)
}

View File

@@ -1,51 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"io"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart"
)
// ChartSave performs a chart save operation.
type ChartSave struct {
cfg *Configuration
}
// NewChartSave creates a new ChartSave object with the given configuration.
func NewChartSave(cfg *Configuration) *ChartSave {
return &ChartSave{
cfg: cfg,
}
}
// Run executes the chart save operation
func (a *ChartSave) Run(out io.Writer, ch *chart.Chart, ref string) error {
r, err := registry.ParseReference(ref)
if err != nil {
return err
}
// If no tag is present, use the chart version
if r.Tag == "" {
r.Tag = ch.Metadata.Version
}
return a.cfg.RegistryClient.SaveChart(ch, r)
}

View File

@@ -37,11 +37,14 @@ type Dependency struct {
Verify bool
Keyring string
SkipRefresh bool
ColumnWidth uint
}
// NewDependency creates a new Dependency object with the given configuration.
func NewDependency() *Dependency {
return &Dependency{}
return &Dependency{
ColumnWidth: 80,
}
}
// List executes 'helm dependency list'.
@@ -181,7 +184,7 @@ func statArchiveForStatus(archive string, dep *chart.Dependency) string {
// printDependencies prints all of the dependencies in the yaml file.
func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) {
table := uitable.New()
table.MaxColWidth = 80
table.MaxColWidth = d.ColumnWidth
table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS")
for _, row := range c.Metadata.Dependencies {
table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c))
@@ -190,7 +193,7 @@ func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart
}
// printMissing prints warnings about charts that are present on disk, but are
// not in Charts.yaml.
// not in Chart.yaml.
func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) {
folder := filepath.Join(chartpath, "charts/*")
files, err := filepath.Glob(folder)

View File

@@ -18,6 +18,7 @@ package action
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/url"
@@ -25,6 +26,7 @@ import (
"path"
"path/filepath"
"strings"
"sync"
"text/template"
"time"
@@ -44,6 +46,7 @@ import (
"helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/repo"
@@ -51,13 +54,6 @@ import (
"helm.sh/helm/v3/pkg/storage/driver"
)
// releaseNameMaxLen is the maximum length of a release name.
//
// As of Kubernetes 1.4, the max limit on a name is 63 chars. We reserve 10 for
// charts to add data. Effectively, that gives us 53 chars.
// See https://github.com/helm/helm/issues/1528
const releaseNameMaxLen = 53
// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine
// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually
// wants to see this file after rendering in the status command. However, it must be a suffix
@@ -104,6 +100,8 @@ type Install struct {
// OutputDir/<ReleaseName>
UseReleaseName bool
PostRenderer postrender.PostRenderer
// Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex
}
// ChartPathOptions captures common options used for controlling chart paths
@@ -119,13 +117,20 @@ type ChartPathOptions struct {
Username string // --username
Verify bool // --verify
Version string // --version
// registryClient provides a registry client but is not added with
// options from a flag
registryClient *registry.Client
}
// NewInstall creates a new Install object with the given configuration.
func NewInstall(cfg *Configuration) *Install {
return &Install{
in := &Install{
cfg: cfg,
}
in.ChartPathOptions.registryClient = cfg.RegistryClient
return in
}
func (i *Install) installCRDs(crds []chart.CRD) error {
@@ -174,7 +179,14 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// Run executes the installation
//
// If DryRun is set to true, this will prepare the release, but not install it
func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
ctx := context.Background()
return i.RunWithContext(ctx, chrt, vals)
}
// Run executes the installation with Context
func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
// Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`)
if !i.ClientOnly {
if err := i.cfg.KubeClient.IsReachable(); err != nil {
@@ -186,6 +198,10 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return nil, err
}
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
// Pre-install anything in the crd/ directory. We do this before Helm
// contacts the upstream server and builds the capabilities object.
if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 {
@@ -214,10 +230,6 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
i.cfg.Log("API Version list given outside of client only mode, this list will be ignored")
}
if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
return nil, err
}
// Make sure if Atomic is set, that wait is set as well. This makes it so
// the user doesn't have to specify both
i.Wait = i.Wait || i.Atomic
@@ -227,7 +239,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return nil, err
}
//special case for helm template --is-upgrade
// special case for helm template --is-upgrade
isUpgrade := i.IsUpgrade && i.DryRun
options := chartutil.ReleaseOptions{
Name: i.ReleaseName,
@@ -331,11 +343,23 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
// not working.
return rel, err
}
rChan := make(chan resultMessage)
doneChan := make(chan struct{})
defer close(doneChan)
go i.performInstall(rChan, rel, toBeAdopted, resources)
go i.handleContext(ctx, rChan, doneChan, rel)
result := <-rChan
//start preformInstall go routine
return result.r, result.e
}
func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) {
// pre-install hooks
if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil {
return i.failRelease(rel, fmt.Errorf("failed pre-install: %s", err))
i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err))
return
}
}
@@ -344,29 +368,34 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
// to true, since that is basically an upgrade operation.
if len(toBeAdopted) == 0 && len(resources) > 0 {
if _, err := i.cfg.KubeClient.Create(resources); err != nil {
return i.failRelease(rel, err)
i.reportToRun(c, rel, err)
return
}
} else if len(resources) > 0 {
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil {
return i.failRelease(rel, err)
i.reportToRun(c, rel, err)
return
}
}
if i.Wait {
if i.WaitForJobs {
if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil {
return i.failRelease(rel, err)
i.reportToRun(c, rel, err)
return
}
} else {
if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil {
return i.failRelease(rel, err)
i.reportToRun(c, rel, err)
return
}
}
}
if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil {
return i.failRelease(rel, fmt.Errorf("failed post-install: %s", err))
i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err))
return
}
}
@@ -387,9 +416,25 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
i.cfg.Log("failed to record the release: %s", err)
}
return rel, nil
i.reportToRun(c, rel, nil)
}
func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) {
select {
case <-ctx.Done():
err := ctx.Err()
i.reportToRun(c, rel, err)
case <-done:
return
}
}
func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) {
i.Lock.Lock()
if err != nil {
rel, err = i.failRelease(rel, err)
}
c <- resultMessage{r: rel, e: err}
i.Lock.Unlock()
}
func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
if i.Atomic {
@@ -417,14 +462,10 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release
// - used by a deleted release, and i.Replace is false
func (i *Install) availableName() error {
start := i.ReleaseName
if start == "" {
return errors.New("name is required")
}
if len(start) > releaseNameMaxLen {
return errors.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen)
if err := chartutil.ValidateReleaseName(start); err != nil {
return errors.Wrapf(err, "release name %q", start)
}
if i.DryRun {
return nil
}
@@ -632,6 +673,12 @@ OUTER:
//
// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart.
func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) {
// If there is no registry client and the name is in an OCI registry return
// an error and a lookup will not occur.
if registry.IsOCI(name) && c.registryClient == nil {
return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name)
}
name = strings.TrimSpace(name)
version := strings.TrimSpace(c.Version)
@@ -662,7 +709,9 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
},
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
RegistryClient: c.registryClient,
}
if c.Verify {
dl.Verify = downloader.VerifyAlways
}
@@ -716,5 +765,6 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
if version != "" {
atVersion = fmt.Sprintf(" at version %q", version)
}
return filename, errors.Errorf("failed to download %q%s (hint: running `helm repo update` may help)", name, atVersion)
return filename, errors.Errorf("failed to download %q%s", name, atVersion)
}

View File

@@ -36,6 +36,7 @@ type Lint struct {
Strict bool
Namespace string
WithSubcharts bool
Quiet bool
}
// LintResult is the result of Lint
@@ -75,6 +76,16 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
return result
}
// HasWaringsOrErrors checks is LintResult has any warnings or errors
func HasWarningsOrErrors(result *LintResult) bool {
for _, msg := range result.Messages {
if msg.Severity > support.InfoSev {
return true
}
}
return false
}
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
var chartPath string
linter := support.Linter{}
@@ -96,7 +107,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric
return linter, errors.Wrap(err, "unable to extract tarball")
}
files, err := ioutil.ReadDir(tempDir)
files, err := os.ReadDir(tempDir)
if err != nil {
return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir)
}

View File

@@ -29,6 +29,7 @@ import (
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
)
@@ -86,18 +87,14 @@ func (p *Pull) Run(chartRef string) (string, error) {
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
},
RegistryClient: p.cfg.RegistryClient,
RepositoryConfig: p.Settings.RepositoryConfig,
RepositoryCache: p.Settings.RepositoryCache,
}
if strings.HasPrefix(chartRef, "oci://") {
if p.Version == "" {
return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
}
if registry.IsOCI(chartRef) {
c.Options = append(c.Options,
getter.WithRegistryClient(p.cfg.RegistryClient),
getter.WithTagName(p.Version))
getter.WithRegistryClient(p.cfg.RegistryClient))
}
if p.Verify {

View File

@@ -0,0 +1,70 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"strings"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/pusher"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/uploader"
)
// Push is the action for uploading a chart.
//
// It provides the implementation of 'helm push'.
type Push struct {
Settings *cli.EnvSettings
cfg *Configuration
}
// PushOpt is a type of function that sets options for a push action.
type PushOpt func(*Push)
// WithPushConfig sets the cfg field on the push configuration object.
func WithPushConfig(cfg *Configuration) PushOpt {
return func(p *Push) {
p.cfg = cfg
}
}
// NewPushWithOpts creates a new push, with configuration options.
func NewPushWithOpts(opts ...PushOpt) *Push {
p := &Push{}
for _, fn := range opts {
fn(p)
}
return p
}
// Run executes 'helm push' against the given chart archive.
func (p *Push) Run(chartRef string, remote string) (string, error) {
var out strings.Builder
c := uploader.ChartUploader{
Out: &out,
Pushers: pusher.All(p.Settings),
Options: []pusher.Option{},
}
if registry.IsOCI(remote) {
c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient))
}
return out.String(), c.UploadTo(chartRef, remote)
}

View File

@@ -18,6 +18,8 @@ package action
import (
"io"
"helm.sh/helm/v3/pkg/registry"
)
// RegistryLogin performs a registry login operation.
@@ -34,5 +36,8 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
// Run executes the registry login operation
func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, insecure bool) error {
return a.cfg.RegistryClient.Login(hostname, username, password, insecure)
return a.cfg.RegistryClient.Login(
hostname,
registry.LoginOptBasicAuth(username, password),
registry.LoginOptInsecure(insecure))
}

View File

@@ -164,6 +164,11 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name)
}
// It is safe to use "force" here because these are resources currently rendered by the chart.
err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true))
if err != nil {
return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release")
}
results, err := r.cfg.KubeClient.Update(current, target, r.Force)
if err != nil {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package action
import (
"bytes"
"fmt"
"strings"
@@ -41,6 +42,8 @@ const (
ShowValues ShowOutputFormat = "values"
// ShowReadme is the format which only shows the chart's README
ShowReadme ShowOutputFormat = "readme"
// ShowCRDs is the format which only shows the chart's CRDs
ShowCRDs ShowOutputFormat = "crds"
)
var readmeFileNames = []string{"readme.md", "readme.txt", "readme"}
@@ -61,12 +64,24 @@ type Show struct {
}
// NewShow creates a new Show object with the given configuration.
// Deprecated: Use NewShowWithConfig
// TODO Helm 4: Fold NewShowWithConfig back into NewShow
func NewShow(output ShowOutputFormat) *Show {
return &Show{
OutputFormat: output,
}
}
// NewShowWithConfig creates a new Show object with the given configuration.
func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show {
sh := &Show{
OutputFormat: output,
}
sh.ChartPathOptions.registryClient = cfg.RegistryClient
return sh
}
// Run executes 'helm show' against the given release.
func (s *Show) Run(chartpath string) (string, error) {
if s.chart == nil {
@@ -106,14 +121,25 @@ func (s *Show) Run(chartpath string) (string, error) {
}
if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll {
if s.OutputFormat == ShowAll {
fmt.Fprintln(&out, "---")
}
readme := findReadme(s.chart.Files)
if readme == nil {
return out.String(), nil
if readme != nil {
if s.OutputFormat == ShowAll {
fmt.Fprintln(&out, "---")
}
fmt.Fprintf(&out, "%s\n", readme.Data)
}
}
if s.OutputFormat == ShowCRDs || s.OutputFormat == ShowAll {
crds := s.chart.CRDObjects()
if len(crds) > 0 {
if s.OutputFormat == ShowAll && !bytes.HasPrefix(crds[0].File.Data, []byte("---")) {
fmt.Fprintln(&out, "---")
}
for _, crd := range crds {
fmt.Fprintf(&out, "%s\n", string(crd.File.Data))
}
}
fmt.Fprintf(&out, "%s\n", readme.Data)
}
return out.String(), nil
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
helmtime "helm.sh/helm/v3/pkg/time"
@@ -37,6 +38,7 @@ type Uninstall struct {
DisableHooks bool
DryRun bool
KeepHistory bool
Wait bool
Timeout time.Duration
Description string
}
@@ -110,13 +112,21 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
u.cfg.Log("uninstall: Failed to store updated release: %s", err)
}
kept, errs := u.deleteRelease(rel)
deletedResources, kept, errs := u.deleteRelease(rel)
if kept != "" {
kept = "These resources were kept due to the resource policy:\n" + kept
}
res.Info = kept
if u.Wait {
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceExt); ok {
if err := kubeClient.WaitForDelete(deletedResources, u.Timeout); err != nil {
errs = append(errs, err)
}
}
}
if !u.DisableHooks {
if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil {
errs = append(errs, err)
@@ -172,12 +182,12 @@ func joinErrors(errs []error) string {
return strings.Join(es, "; ")
}
// deleteRelease deletes the release and returns manifests that were kept in the deletion process
func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) {
// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process
func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) {
var errs []error
caps, err := u.cfg.getCapabilities()
if err != nil {
return rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")}
}
manifests := releaseutil.SplitManifests(rel.Manifest)
@@ -187,7 +197,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) {
// FIXME: One way to delete at this point would be to try a label-based
// deletion. The problem with this is that we could get a false positive
// and delete something that was not legitimately part of this release.
return rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")}
}
filesToKeep, filesToDelete := filterManifestsToKeep(files)
@@ -203,10 +213,10 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (string, []error) {
resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false)
if err != nil {
return "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")}
}
if len(resources) > 0 {
_, errs = u.cfg.KubeClient.Delete(resources)
}
return kept, errs
return resources, kept, errs
}

View File

@@ -21,6 +21,7 @@ import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/pkg/errors"
@@ -98,17 +99,35 @@ type Upgrade struct {
PostRenderer postrender.PostRenderer
// DisableOpenAPIValidation controls whether OpenAPI validation is enforced.
DisableOpenAPIValidation bool
// Get missing dependencies
DependencyUpdate bool
// Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex
}
type resultMessage struct {
r *release.Release
e error
}
// NewUpgrade creates a new Upgrade object with the given configuration.
func NewUpgrade(cfg *Configuration) *Upgrade {
return &Upgrade{
up := &Upgrade{
cfg: cfg,
}
up.ChartPathOptions.registryClient = cfg.RegistryClient
return up
}
// Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
ctx := context.Background()
return u.RunWithContext(ctx, name, chart, vals)
}
// RunWithContext executes the upgrade on the given release with context.
func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) {
if err := u.cfg.KubeClient.IsReachable(); err != nil {
return nil, err
}
@@ -129,7 +148,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
u.cfg.Releases.MaxHistory = u.MaxHistory
u.cfg.Log("performing update for %s", name)
res, err := u.performUpgrade(currentRelease, upgradedRelease)
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease)
if err != nil {
return res, err
}
@@ -241,7 +260,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return currentRelease, upgradedRelease, err
}
func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false)
if err != nil {
// Checking for removed Kubernetes API error so can provide a more informative error message to the user
@@ -304,11 +323,51 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err
}
rChan := make(chan resultMessage)
ctxChan := make(chan resultMessage)
doneChan := make(chan interface{})
defer close(doneChan)
go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease)
go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease)
select {
case result := <-rChan:
return result.r, result.e
case result := <-ctxChan:
return result.r, result.e
}
}
// Function used to lock the Mutex, this is important for the case when the atomic flag is set.
// In that case the upgrade will finish before the rollback is finished so it is necessary to wait for the rollback to finish.
// The rollback will be trigger by the function failRelease
func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Release, created kube.ResourceList, err error) {
u.Lock.Lock()
if err != nil {
rel, err = u.failRelease(rel, created, err)
}
c <- resultMessage{r: rel, e: err}
u.Lock.Unlock()
}
// Setup listener for SIGINT and SIGTERM
func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) {
select {
case <-ctx.Done():
err := ctx.Err()
// when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens.
u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err)
case <-done:
return
}
}
func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) {
// pre-upgrade hooks
if !u.DisableHooks {
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil {
return u.failRelease(upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err))
u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err))
return
}
} else {
u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name)
@@ -317,7 +376,8 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
results, err := u.cfg.KubeClient.Update(current, target, u.Force)
if err != nil {
u.cfg.recordRelease(originalRelease)
return u.failRelease(upgradedRelease, results.Created, err)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
return
}
if u.Recreate {
@@ -334,12 +394,14 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
if u.WaitForJobs {
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease)
return u.failRelease(upgradedRelease, results.Created, err)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
return
}
} else {
if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease)
return u.failRelease(upgradedRelease, results.Created, err)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
return
}
}
}
@@ -347,7 +409,8 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
// post-upgrade hooks
if !u.DisableHooks {
if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil {
return u.failRelease(upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err))
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err))
return
}
}
@@ -360,8 +423,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
} else {
upgradedRelease.Info.Description = "Upgrade complete"
}
return upgradedRelease, nil
u.reportToPerformUpgrade(c, upgradedRelease, nil, nil)
}
func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) {

View File

@@ -51,7 +51,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
if apierrors.IsNotFound(err) {
return nil
}
return errors.Wrap(err, "could not get information about the resource")
return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info))
}
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.

View File

@@ -28,21 +28,21 @@ import (
helmversion "helm.sh/helm/v3/internal/version"
)
const (
k8sVersionMajor = 1
k8sVersionMinor = 20
)
var (
// The Kubernetes version can be set by LDFLAGS. In order to do that the value
// must be a string.
k8sVersionMajor = "1"
k8sVersionMinor = "20"
// DefaultVersionSet is the default version set, which includes only Core V1 ("v1").
DefaultVersionSet = allKnownVersions()
// DefaultCapabilities is the default set of capabilities.
DefaultCapabilities = &Capabilities{
KubeVersion: KubeVersion{
Version: fmt.Sprintf("v%d.%d.0", k8sVersionMajor, k8sVersionMinor),
Major: strconv.Itoa(k8sVersionMajor),
Minor: strconv.Itoa(k8sVersionMinor),
Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor),
Major: k8sVersionMajor,
Minor: k8sVersionMinor,
},
APIVersions: DefaultVersionSet,
HelmVersion: helmversion.Get(),

View File

@@ -17,6 +17,7 @@ limitations under the License.
package chartutil
import (
"fmt"
"log"
"github.com/mitchellh/copystructure"
@@ -25,6 +26,13 @@ import (
"helm.sh/helm/v3/pkg/chart"
)
func concatPrefix(a, b string) string {
if a == "" {
return b
}
return fmt.Sprintf("%s.%s", a, b)
}
// CoalesceValues coalesces all of the values in a chart (and its subcharts).
//
// Values are coalesced together using the following rules:
@@ -45,19 +53,21 @@ func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, err
if valsCopy == nil {
valsCopy = make(map[string]interface{})
}
return coalesce(chrt, valsCopy)
return coalesce(log.Printf, chrt, valsCopy, "")
}
type printFn func(format string, v ...interface{})
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
//
// This is a helper function for CoalesceValues.
func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
coalesceValues(ch, dest)
return coalesceDeps(ch, dest)
func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) {
coalesceValues(printf, ch, dest, prefix)
return coalesceDeps(printf, ch, dest, prefix)
}
// coalesceDeps coalesces the dependencies of the given chart.
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) {
for _, subchart := range chrt.Dependencies() {
if c, ok := dest[subchart.Name()]; !ok {
// If dest doesn't already have the key, create it.
@@ -67,13 +77,14 @@ func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]in
}
if dv, ok := dest[subchart.Name()]; ok {
dvmap := dv.(map[string]interface{})
subPrefix := concatPrefix(prefix, chrt.Metadata.Name)
// Get globals out of dest and merge them into dvmap.
coalesceGlobals(dvmap, dest)
coalesceGlobals(printf, dvmap, dest, subPrefix)
// Now coalesce the rest of the values.
var err error
dest[subchart.Name()], err = coalesce(subchart, dvmap)
dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix)
if err != nil {
return dest, err
}
@@ -85,20 +96,20 @@ func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]in
// coalesceGlobals copies the globals out of src and merges them into dest.
//
// For convenience, returns dest.
func coalesceGlobals(dest, src map[string]interface{}) {
func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string) {
var dg, sg map[string]interface{}
if destglob, ok := dest[GlobalKey]; !ok {
dg = make(map[string]interface{})
} else if dg, ok = destglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
printf("warning: skipping globals because destination %s is not a table.", GlobalKey)
return
}
if srcglob, ok := src[GlobalKey]; !ok {
sg = make(map[string]interface{})
} else if sg, ok = srcglob.(map[string]interface{}); !ok {
log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey)
printf("warning: skipping globals because source %s is not a table.", GlobalKey)
return
}
@@ -114,22 +125,22 @@ func coalesceGlobals(dest, src map[string]interface{}) {
dg[key] = vv
} else {
if destvmap, ok := destv.(map[string]interface{}); !ok {
log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
} else {
// Basically, we reverse order of coalesce here to merge
// top-down.
CoalesceTables(vv, destvmap)
subPrefix := concatPrefix(prefix, key)
coalesceTablesFullKey(printf, vv, destvmap, subPrefix)
dg[key] = vv
continue
}
}
} else if dv, ok := dg[key]; ok && istable(dv) {
// It's not clear if this condition can actually ever trigger.
log.Printf("key %s is table. Skipping", key)
continue
printf("key %s is table. Skipping", key)
} else {
// TODO: Do we need to do any additional checking on the value?
dg[key] = val
}
// TODO: Do we need to do any additional checking on the value?
dg[key] = val
}
dest[GlobalKey] = dg
}
@@ -145,7 +156,8 @@ func copyMap(src map[string]interface{}) map[string]interface{} {
// coalesceValues builds up a values map for a particular chart.
//
// Values in v will override the values in the chart.
func coalesceValues(c *chart.Chart, v map[string]interface{}) {
func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, prefix string) {
subPrefix := concatPrefix(prefix, c.Metadata.Name)
for key, val := range c.Values {
if value, ok := v[key]; ok {
if value == nil {
@@ -158,15 +170,15 @@ func coalesceValues(c *chart.Chart, v map[string]interface{}) {
src, ok := val.(map[string]interface{})
if !ok {
// If the original value is nil, there is nothing to coalesce, so we don't print
// the warning but simply continue
// the warning
if val != nil {
log.Printf("warning: skipped value for %s: Not a table.", key)
printf("warning: skipped value for %s.%s: Not a table.", subPrefix, key)
}
continue
} else {
// Because v has higher precedence than nv, dest values override src
// values.
coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key))
}
// Because v has higher precedence than nv, dest values override src
// values.
CoalesceTables(dest, src)
}
} else {
// If the key is not in v, copy it from nv.
@@ -179,6 +191,13 @@ func coalesceValues(c *chart.Chart, v map[string]interface{}) {
//
// dest is considered authoritative.
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
return coalesceTablesFullKey(log.Printf, dst, src, "")
}
// coalesceTablesFullKey merges a source map into a destination map.
//
// dest is considered authoritative.
func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string) map[string]interface{} {
// When --reuse-values is set but there are no modifications yet, return new values
if src == nil {
return dst
@@ -189,18 +208,19 @@ func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
// Because dest has higher precedence than src, dest values override src
// values.
for key, val := range src {
fullkey := concatPrefix(prefix, key)
if dv, ok := dst[key]; ok && dv == nil {
delete(dst, key)
} else if !ok {
dst[key] = val
} else if istable(val) {
if istable(dv) {
CoalesceTables(dv.(map[string]interface{}), val.(map[string]interface{}))
coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey)
} else {
log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val)
}
} else if istable(dv) && val != nil {
log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
printf("warning: destination for %s is a table. Ignoring non-table value (%v)", fullkey, val)
}
}
return dst

View File

@@ -268,7 +268,7 @@ func processImportValues(c *chart.Chart) error {
}
// set the new values
c.Values = CoalesceTables(b, cvals)
c.Values = CoalesceTables(cvals, b)
return nil
}

View File

@@ -40,19 +40,24 @@ var (
errMissingName = errors.New("no name provided")
// errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New(fmt.Sprintf(
errInvalidName = fmt.Errorf(
"invalid release name, must match regex %s and the length must not be longer than 53",
validName.String()))
validName.String())
// errInvalidKubernetesName indicates that the name does not meet the Kubernetes
// restrictions on metadata names.
errInvalidKubernetesName = errors.New(fmt.Sprintf(
errInvalidKubernetesName = fmt.Errorf(
"invalid metadata name, must match regex %s and the length must not be longer than 253",
validName.String()))
validName.String())
)
const (
// maxNameLen is the maximum length Helm allows for a release name
// According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names)
// some resource names have a max length of 63 characters while others have a max
// length of 253 characters. As we cannot be sure the resources used in a chart, we
// therefore need to limit it to 63 chars and reserve 10 chars for additional part to name
// of the resource. The reason is that chart maintainers can use release name as part of
// the resource name (and some additional chars).
maxReleaseNameLen = 53
// maxMetadataNameLen is the maximum length Kubernetes allows for any name.
maxMetadataNameLen = 253

View File

@@ -81,7 +81,7 @@ func New() *EnvSettings {
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
}
@@ -180,6 +180,11 @@ func (s *EnvSettings) Namespace() string {
return "default"
}
// SetNamespace sets the namespace in the configuration
func (s *EnvSettings) SetNamespace(namespace string) {
s.namespace = namespace
}
// RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
return s.config

View File

@@ -23,14 +23,15 @@ import (
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
)
@@ -102,8 +103,9 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
}
name := filepath.Base(u.Path)
if u.Scheme == "oci" {
name = fmt.Sprintf("%s-%s.tgz", name, version)
if u.Scheme == registry.OCIScheme {
idx := strings.LastIndexByte(name, ':')
name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:])
}
destfile := filepath.Join(dest, name)
@@ -139,12 +141,46 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, nil
}
func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) {
var tag string
var err error
// Evaluate whether an explicit version has been provided. Otherwise, determine version to use
_, errSemVer := semver.NewVersion(version)
if errSemVer == nil {
tag = version
} else {
// Retrieve list of repository tags
tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme)))
if err != nil {
return nil, err
}
if len(tags) == 0 {
return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref)
}
// Determine if version provided
// If empty, try to get the highest available tag
// If exact version, try to find it
// If semver constraint string, try to find a match
tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version)
if err != nil {
return nil, err
}
}
u.Path = fmt.Sprintf("%s:%s", u.Path, tag)
return u, err
}
// ResolveChartVersion resolves a chart reference to a URL.
//
// It returns the URL and sets the ChartDownloader's Options that can fetch
// the URL using the appropriate Getter.
//
// A reference may be an HTTP URL, a 'reponame/chartname' reference, or a local path.
// A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname'
// reference, or a local path.
//
// A version is a SemVer string (1.2.3-beta.1+f334a6789).
//
@@ -159,6 +195,10 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
return nil, errors.Errorf("invalid chart URL format: %s", ref)
}
if registry.IsOCI(u.String()) {
return c.getOciURI(ref, version, u)
}
rf, err := loadRepoConfig(c.RepositoryConfig)
if err != nil {
return u, err

View File

@@ -34,7 +34,6 @@ import (
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/internal/resolver"
"helm.sh/helm/v3/internal/third_party/dep/fs"
"helm.sh/helm/v3/internal/urlutil"
@@ -43,6 +42,7 @@ import (
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
)
@@ -232,7 +232,7 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) {
//
// This returns a lock file, which has all of the dependencies normalized to a specific version.
func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) {
res := resolver.New(m.ChartPath, m.RepositoryCache)
res := resolver.New(m.ChartPath, m.RepositoryCache, m.RegistryClient)
return res.Resolve(req, repoNames)
}
@@ -249,22 +249,24 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
destPath := filepath.Join(m.ChartPath, "charts")
tmpPath := filepath.Join(m.ChartPath, "tmpcharts")
// Create 'charts' directory if it doesn't already exist.
if fi, err := os.Stat(destPath); err != nil {
// Check if 'charts' directory is not actally a directory. If it does not exist, create it.
if fi, err := os.Stat(destPath); err == nil {
if !fi.IsDir() {
return errors.Errorf("%q is not a directory", destPath)
}
} else if os.IsNotExist(err) {
if err := os.MkdirAll(destPath, 0755); err != nil {
return err
}
} else if !fi.IsDir() {
return errors.Errorf("%q is not a directory", destPath)
} else {
return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err)
}
if err := fs.RenameWithFallback(destPath, tmpPath); err != nil {
return errors.Wrap(err, "unable to move current charts to tmp dir")
}
if err := os.MkdirAll(destPath, 0755); err != nil {
// Prepare tmpPath
if err := os.MkdirAll(tmpPath, 0755); err != nil {
return err
}
defer os.RemoveAll(tmpPath)
fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps))
var saveError error
@@ -273,24 +275,25 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
// No repository means the chart is in charts directory
if dep.Repository == "" {
fmt.Fprintf(m.Out, "Dependency %s did not declare a repository. Assuming it exists in the charts directory\n", dep.Name)
chartPath := filepath.Join(tmpPath, dep.Name)
// NOTE: we are only validating the local dependency conforms to the constraints. No copying to tmpPath is necessary.
chartPath := filepath.Join(destPath, dep.Name)
ch, err := loader.LoadDir(chartPath)
if err != nil {
return fmt.Errorf("Unable to load chart: %v", err)
return fmt.Errorf("unable to load chart '%s': %v", chartPath, err)
}
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return fmt.Errorf("Dependency %s has an invalid version/constraint format: %s", dep.Name, err)
return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err)
}
v, err := semver.NewVersion(ch.Metadata.Version)
if err != nil {
return fmt.Errorf("Invalid version %s for dependency %s: %s", dep.Version, dep.Name, err)
return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err)
}
if !constraint.Check(v) {
saveError = fmt.Errorf("Dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version)
saveError = fmt.Errorf("dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version)
break
}
continue
@@ -299,7 +302,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
if m.Debug {
fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository)
}
ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version)
ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath)
if err != nil {
saveError = err
break
@@ -329,6 +332,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
Keyring: m.Keyring,
RepositoryConfig: m.RepositoryConfig,
RepositoryCache: m.RepositoryCache,
RegistryClient: m.RegistryClient,
Getters: m.Getters,
Options: []getter.Option{
getter.WithBasicAuth(username, password),
@@ -339,12 +343,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
}
version := ""
if strings.HasPrefix(churl, "oci://") {
if !resolver.FeatureGateOCI.IsEnabled() {
return errors.Wrapf(resolver.FeatureGateOCI.Error(),
"the repository %s is an OCI registry", churl)
}
if registry.IsOCI(churl) {
churl, version, err = parseOCIRef(churl)
if err != nil {
return errors.Wrapf(err, "could not parse OCI reference")
@@ -354,8 +353,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
getter.WithTagName(version))
}
_, _, err = dl.DownloadTo(churl, version, destPath)
if err != nil {
if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil {
saveError = errors.Wrapf(err, "could not download %s", churl)
break
}
@@ -363,36 +361,14 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
churls[churl] = struct{}{}
}
// TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins".
if saveError == nil {
fmt.Fprintln(m.Out, "Deleting outdated charts")
for _, dep := range deps {
// Chart from local charts directory stays in place
if dep.Repository != "" {
if err := m.safeDeleteDep(dep.Name, tmpPath); err != nil {
return err
}
}
}
if err := move(tmpPath, destPath); err != nil {
// now we can move all downloaded charts to destPath and delete outdated dependencies
if err := m.safeMoveDeps(deps, tmpPath, destPath); err != nil {
return err
}
if err := os.RemoveAll(tmpPath); err != nil {
return errors.Wrapf(err, "failed to remove %v", tmpPath)
}
} else {
fmt.Fprintln(m.Out, "Save error occurred: ", saveError)
fmt.Fprintln(m.Out, "Deleting newly downloaded charts, restoring pre-update state")
for _, dep := range deps {
if err := m.safeDeleteDep(dep.Name, destPath); err != nil {
return err
}
}
if err := os.RemoveAll(destPath); err != nil {
return errors.Wrapf(err, "failed to remove %v", destPath)
}
if err := fs.RenameWithFallback(tmpPath, destPath); err != nil {
return errors.Wrap(err, "unable to move current charts to tmp dir")
}
return saveError
}
return nil
@@ -410,36 +386,76 @@ func parseOCIRef(chartRef string) (string, string, error) {
return chartRef, tag, nil
}
// safeDeleteDep deletes any versions of the given dependency in the given directory.
// safeMoveDep moves all dependencies in the source and moves them into dest.
//
// It does this by first matching the file name to an expected pattern, then loading
// the file to verify that it is a chart with the same name as the given name.
// the file to verify that it is a chart.
//
// Because it requires tar file introspection, it is more intensive than a basic delete.
// Any charts in dest that do not exist in source are removed (barring local dependencies)
//
// Because it requires tar file introspection, it is more intensive than a basic move.
//
// This will only return errors that should stop processing entirely. Other errors
// will emit log messages or be ignored.
func (m *Manager) safeDeleteDep(name, dir string) error {
files, err := filepath.Glob(filepath.Join(dir, name+"-*.tgz"))
func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error {
existsInSourceDirectory := map[string]bool{}
isLocalDependency := map[string]bool{}
sourceFiles, err := os.ReadDir(source)
if err != nil {
// Only for ErrBadPattern
return err
}
for _, fname := range files {
ch, err := loader.LoadFile(fname)
if err != nil {
fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err)
// attempt to read destFiles; fail fast if we can't
destFiles, err := os.ReadDir(dest)
if err != nil {
return err
}
for _, dep := range deps {
if dep.Repository == "" {
isLocalDependency[dep.Name] = true
}
}
for _, file := range sourceFiles {
if file.IsDir() {
continue
}
if ch.Name() != name {
// This is not the file you are looking for.
filename := file.Name()
sourcefile := filepath.Join(source, filename)
destfile := filepath.Join(dest, filename)
existsInSourceDirectory[filename] = true
if _, err := loader.LoadFile(sourcefile); err != nil {
fmt.Fprintf(m.Out, "Could not verify %s for moving: %s (Skipping)", sourcefile, err)
continue
}
if err := os.Remove(fname); err != nil {
fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err)
// NOTE: no need to delete the dest; os.Rename replaces it.
if err := fs.RenameWithFallback(sourcefile, destfile); err != nil {
fmt.Fprintf(m.Out, "Unable to move %s to charts dir %s (Skipping)", sourcefile, err)
continue
}
}
fmt.Fprintln(m.Out, "Deleting outdated charts")
// find all files that exist in dest that do not exist in source; delete them (outdated dependencies)
for _, file := range destFiles {
if !file.IsDir() && !existsInSourceDirectory[file.Name()] {
fname := filepath.Join(dest, file.Name())
ch, err := loader.LoadFile(fname)
if err != nil {
fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err)
continue
}
// local dependency - skip
if isLocalDependency[ch.Name()] {
continue
}
if err := os.Remove(fname); err != nil {
fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err)
continue
}
}
}
return nil
}
@@ -457,7 +473,7 @@ func (m *Manager) hasAllRepos(deps []*chart.Dependency) error {
Loop:
for _, dd := range deps {
// If repo is from local path or OCI, continue
if strings.HasPrefix(dd.Repository, "file://") || strings.HasPrefix(dd.Repository, "oci://") {
if strings.HasPrefix(dd.Repository, "file://") || registry.IsOCI(dd.Repository) {
continue
}
@@ -558,8 +574,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
missing := []string{}
for _, dd := range deps {
// Don't map the repository, we don't need to download chart from charts directory
// When OCI is used there is no Helm repository
if dd.Repository == "" || strings.HasPrefix(dd.Repository, "oci://") {
if dd.Repository == "" {
continue
}
// if dep chart is from local path, verify the path is valid
@@ -575,7 +590,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
continue
}
if strings.HasPrefix(dd.Repository, "oci://") {
if registry.IsOCI(dd.Repository) {
reposMap[dd.Name] = dd.Repository
continue
}
@@ -689,7 +704,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error {
//
// If it finds a URL that is "relative", it will prepend the repoURL.
func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) {
if strings.HasPrefix(repoURL, "oci://") {
if registry.IsOCI(repoURL) {
return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil
}
@@ -783,6 +798,7 @@ func normalizeURL(baseURL, urlOrPath string) (string, error) {
return urlOrPath, errors.Wrap(err, "base URL failed to parse")
}
u2.RawPath = path.Join(u2.RawPath, urlOrPath)
u2.Path = path.Join(u2.Path, urlOrPath)
return u2.String(), nil
}
@@ -832,10 +848,8 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error {
return ioutil.WriteFile(dest, data, 0644)
}
// archive a dep chart from local directory and save it into charts/
func tarFromLocalDir(chartpath, name, repo, version string) (string, error) {
destPath := filepath.Join(chartpath, "charts")
// archive a dep chart from local directory and save it into destPath
func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) {
if !strings.HasPrefix(repo, "file://") {
return "", errors.Errorf("wrong format: chart %s repository %s", name, repo)
}
@@ -868,20 +882,6 @@ func tarFromLocalDir(chartpath, name, repo, version string) (string, error) {
return "", errors.Errorf("can't get a valid version for dependency %s", name)
}
// move files from tmppath to destpath
func move(tmpPath, destPath string) error {
files, _ := ioutil.ReadDir(tmpPath)
for _, file := range files {
filename := file.Name()
tmpfile := filepath.Join(tmpPath, filename)
destfile := filepath.Join(destPath, filename)
if err := fs.RenameWithFallback(tmpfile, destfile); err != nil {
return errors.Wrap(err, "unable to move local charts to charts dir")
}
}
return nil
}
// The prefix to use for cache keys created by the manager for repo names
const managerKeyPrefix = "helm-manager-"

View File

@@ -97,7 +97,7 @@ const warnStartDelim = "HELM_ERR_START"
const warnEndDelim = "HELM_ERR_END"
const recursionMaxNums = 1000
var warnRegex = regexp.MustCompile(warnStartDelim + `(.*)` + warnEndDelim)
var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim)
func warnWrap(warn string) string {
return warnStartDelim + warn + warnEndDelim
@@ -344,13 +344,20 @@ func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable {
//
// As it recurses, it also sets the values to be appropriate for the template
// scope.
func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) {
func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) map[string]interface{} {
subCharts := make(map[string]interface{})
chartMetaData := struct {
chart.Metadata
IsRoot bool
}{*c.Metadata, c.IsRoot()}
next := map[string]interface{}{
"Chart": c.Metadata,
"Chart": chartMetaData,
"Files": newFiles(c.Files),
"Release": vals["Release"],
"Capabilities": vals["Capabilities"],
"Values": make(chartutil.Values),
"Subcharts": subCharts,
}
// If there is a {{.Values.ThisChart}} in the parent metadata,
@@ -362,7 +369,7 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
}
for _, child := range c.Dependencies() {
recAllTpls(child, templates, next)
subCharts[child.Name()] = recAllTpls(child, templates, next)
}
newParentID := c.ChartFullPath()
@@ -376,6 +383,8 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
basePath: path.Join(newParentID, "templates"),
}
}
return next
}
// isTemplateValid returns true if the template is valid for the chart type

View File

@@ -63,7 +63,7 @@ func NewLookupFunction(config *rest.Config) lookupFunc {
}
return obj.UnstructuredContent(), nil
}
//this will return a list
// this will return a list
obj, err := client.List(context.Background(), metav1.ListOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
@@ -112,7 +112,7 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met
return res, err
}
for _, resource := range resList.APIResources {
//if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now.
// if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now.
if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") {
res = resource
res.Group = gvk.Group

View File

@@ -1,38 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gates
import (
"fmt"
"os"
)
// Gate is the name of the feature gate.
type Gate string
// String returns the string representation of this feature gate.
func (g Gate) String() string {
return string(g)
}
// IsEnabled determines whether a certain feature gate is enabled.
func (g Gate) IsEnabled() bool {
return os.Getenv(string(g)) != ""
}
func (g Gate) Error() error {
return fmt.Errorf("this feature has been marked as experimental and is not enabled by default. Please set %s=1 in your environment to use this feature", g.String())
}

View File

@@ -18,12 +18,13 @@ package getter
import (
"bytes"
"net/http"
"time"
"github.com/pkg/errors"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
)
// options are generic parameters to be provided to the getter during instantiation.
@@ -43,6 +44,7 @@ type options struct {
version string
registryClient *registry.Client
timeout time.Duration
transport *http.Transport
}
// Option allows specifying various settings configurable by the user for overriding the defaults
@@ -119,6 +121,13 @@ func WithUntar() Option {
}
}
// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default.
func WithTransport(transport *http.Transport) Option {
return func(opts *options) {
opts.transport = transport
}
}
// Getter is an interface to support GET to the specified URL.
type Getter interface {
// Get file content by url string
@@ -169,7 +178,7 @@ var httpProvider = Provider{
}
var ociProvider = Provider{
Schemes: []string{"oci"},
Schemes: []string{registry.OCIScheme},
New: NewOCIGetter,
}

View File

@@ -21,6 +21,7 @@ import (
"io"
"net/http"
"net/url"
"sync"
"github.com/pkg/errors"
@@ -31,10 +32,12 @@ import (
// HTTPGetter is the default HTTP(/S) backend handler
type HTTPGetter struct {
opts options
opts options
transport *http.Transport
once sync.Once
}
//Get performs a Get from repo.Getter and returns the body.
// Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
@@ -43,13 +46,11 @@ func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error)
}
func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
// Set a helm specific user agent so that a repo server and metrics can
// separate helm calls from other tools interacting with repos.
req, err := http.NewRequest("GET", href, nil)
req, err := http.NewRequest(http.MethodGet, href, nil)
if err != nil {
return buf, err
return nil, err
}
req.Header.Set("User-Agent", version.GetUserAgent())
@@ -61,11 +62,11 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
// with the basic auth is the one being fetched.
u1, err := url.Parse(g.opts.url)
if err != nil {
return buf, errors.Wrap(err, "Unable to parse getter URL")
return nil, errors.Wrap(err, "Unable to parse getter URL")
}
u2, err := url.Parse(href)
if err != nil {
return buf, errors.Wrap(err, "Unable to parse URL getting from")
return nil, errors.Wrap(err, "Unable to parse URL getting from")
}
// Host on URL (returned from url.Parse) contains the port if present.
@@ -84,14 +85,15 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
resp, err := client.Do(req)
if err != nil {
return buf, err
return nil, err
}
if resp.StatusCode != 200 {
return buf, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
}
buf := bytes.NewBuffer(nil)
_, err = io.Copy(buf, resp.Body)
resp.Body.Close()
return buf, err
}
@@ -107,10 +109,20 @@ func NewHTTPGetter(options ...Option) (Getter, error) {
}
func (g *HTTPGetter) httpClient() (*http.Client, error) {
transport := &http.Transport{
DisableCompression: true,
Proxy: http.ProxyFromEnvironment,
if g.opts.transport != nil {
return &http.Client{
Transport: g.opts.transport,
Timeout: g.opts.timeout,
}, nil
}
g.once.Do(func() {
g.transport = &http.Transport{
DisableCompression: true,
Proxy: http.ProxyFromEnvironment,
}
})
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
if err != nil {
@@ -124,21 +136,21 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
}
tlsConf.ServerName = sni
transport.TLSClientConfig = tlsConf
g.transport.TLSClientConfig = tlsConf
}
if g.opts.insecureSkipVerifyTLS {
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{
if g.transport.TLSClientConfig == nil {
g.transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
} else {
transport.TLSClientConfig.InsecureSkipVerify = true
g.transport.TLSClientConfig.InsecureSkipVerify = true
}
}
client := &http.Client{
Transport: transport,
Transport: g.transport,
Timeout: g.opts.timeout,
}

View File

@@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/registry"
)
// OCIGetter is the default HTTP(/S) backend handler
@@ -28,7 +28,7 @@ type OCIGetter struct {
opts options
}
//Get performs a Get from repo.Getter and returns the body.
// Get performs a Get from repo.Getter and returns the body.
func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
@@ -39,22 +39,26 @@ func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
client := g.opts.registryClient
ref := strings.TrimPrefix(href, "oci://")
if version := g.opts.version; version != "" {
ref = fmt.Sprintf("%s:%s", ref, version)
ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme))
var pullOpts []registry.PullOption
requestingProv := strings.HasSuffix(ref, ".prov")
if requestingProv {
ref = strings.TrimSuffix(ref, ".prov")
pullOpts = append(pullOpts,
registry.PullOptWithChart(false),
registry.PullOptWithProv(true))
}
r, err := registry.ParseReference(ref)
result, err := client.Pull(ref, pullOpts...)
if err != nil {
return nil, err
}
buf, err := client.PullChart(r)
if err != nil {
return nil, err
if requestingProv {
return bytes.NewBuffer(result.Prov.Data), nil
}
return buf, nil
return bytes.NewBuffer(result.Chart.Data), nil
}
// NewOCIGetter constructs a valid http/https client as a Getter

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build darwin
//go:build darwin
package helmpath

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows,!darwin
//go:build !windows && !darwin
package helmpath

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
//go:build windows
package helmpath

View File

@@ -162,6 +162,15 @@ func (c *Client) WaitWithJobs(resources ResourceList, timeout time.Duration) err
return w.waitForResources(resources)
}
// WaitForDelete wait up to the given timeout for the specified resources to be deleted.
func (c *Client) WaitForDelete(resources ResourceList, timeout time.Duration) error {
w := waiter{
log: c.Log,
timeout: timeout,
}
return w.waitForDeletedResources(resources)
}
func (c *Client) namespace() string {
if c.Namespace != "" {
return c.Namespace
@@ -183,7 +192,18 @@ func (c *Client) newBuilder() *resource.Builder {
// Build validates for Kubernetes objects and returns unstructured infos.
func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
schema, err := c.Factory.Validator(validate)
validationDirective := metav1.FieldValidationIgnore
if validate {
validationDirective = metav1.FieldValidationStrict
}
dynamicClient, err := c.Factory.DynamicClient()
if err != nil {
return nil, err
}
verifier := resource.NewQueryParamVerifier(dynamicClient, c.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
schema, err := c.Factory.Validator(validationDirective, verifier)
if err != nil {
return nil, err
}
@@ -255,7 +275,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
}
for _, info := range original.Difference(target) {
c.Log("Deleting %q in %s...", info.Name, info.Namespace)
c.Log("Deleting %s %q in namespace %s...", info.Mapping.GroupVersionKind.Kind, info.Name, info.Namespace)
if err := info.Get(); err != nil {
c.Log("Unable to get obj %q, err: %s", info.Name, err)
@@ -489,7 +509,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
}
if patch == nil || string(patch) == "{}" {
c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name)
c.Log("Looks like there are no changes for %s %q", kind, target.Name)
// This needs to happen to make sure that Helm has the latest info from the API
// Otherwise there will be no labels and other functions that use labels will panic
if err := target.Get(); err != nil {
@@ -498,6 +518,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
return nil
}
// send patch to server
c.Log("Patch %s %q in namespace %s", kind, target.Name, target.Namespace)
obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil)
if err != nil {
return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind)
@@ -636,6 +657,9 @@ func (c *Client) WaitAndGetCompletedPodPhase(name string, timeout time.Duration)
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
TimeoutSeconds: &to,
})
if err != nil {
return v1.PodUnknown, err
}
for event := range watcher.ResultChan() {
p, ok := event.Object.(*v1.Pod)

View File

@@ -18,6 +18,8 @@ package kube // import "helm.sh/helm/v3/pkg/kube"
import (
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/validation"
@@ -28,11 +30,19 @@ import (
type Factory interface {
// ToRawKubeConfigLoader return kubeconfig loader as-is
ToRawKubeConfigLoader() clientcmd.ClientConfig
// DynamicClient returns a dynamic client ready for use
DynamicClient() (dynamic.Interface, error)
// KubernetesClientSet gives you back an external clientset
KubernetesClientSet() (*kubernetes.Clientset, error)
// NewBuilder returns an object that assists in loading objects from both disk and the server
// and which implements the common patterns for CLI interactions with generic resources.
NewBuilder() *resource.Builder
// Returns a schema that can validate objects stored on disk.
Validator(validate bool) (validation.Schema, error)
Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error)
// OpenAPIGetter returns a getter for the openapi schema document
OpenAPIGetter() discovery.OpenAPISchemaInterface
}

View File

@@ -40,6 +40,7 @@ type FailingKubeClient struct {
BuildError error
BuildUnstructuredError error
WaitAndGetCompletedPodPhaseError error
WaitDuration time.Duration
}
// Create returns the configured error if set or prints
@@ -50,8 +51,9 @@ func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, e
return f.PrintingKubeClient.Create(resources)
}
// Wait returns the configured error if set or prints
// Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints.
func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error {
time.Sleep(f.WaitDuration)
if f.WaitError != nil {
return f.WaitError
}
@@ -63,7 +65,15 @@ func (f *FailingKubeClient) WaitWithJobs(resources kube.ResourceList, d time.Dur
if f.WaitError != nil {
return f.WaitError
}
return f.PrintingKubeClient.Wait(resources, d)
return f.PrintingKubeClient.WaitWithJobs(resources, d)
}
// WaitForDelete returns the configured error if set or prints
func (f *FailingKubeClient) WaitForDelete(resources kube.ResourceList, d time.Duration) error {
if f.WaitError != nil {
return f.WaitError
}
return f.PrintingKubeClient.WaitForDelete(resources, d)
}
// Delete returns the configured error if set or prints

View File

@@ -57,6 +57,11 @@ func (p *PrintingKubeClient) WaitWithJobs(resources kube.ResourceList, _ time.Du
return err
}
func (p *PrintingKubeClient) WaitForDelete(resources kube.ResourceList, _ time.Duration) error {
_, err := io.Copy(p.Out, bufferize(resources))
return err
}
// Delete implements KubeClient delete.
//
// It only prints out the content to be deleted.

View File

@@ -70,4 +70,13 @@ type Interface interface {
IsReachable() error
}
// InterfaceExt is introduced to avoid breaking backwards compatibility for Interface implementers.
//
// TODO Helm 4: Remove InterfaceExt and integrate its method(s) into the Interface.
type InterfaceExt interface {
// WaitForDelete wait up to the given timeout for the specified resources to be deleted.
WaitForDelete(resources ResourceList, timeout time.Duration) error
}
var _ Interface = (*Client)(nil)
var _ InterfaceExt = (*Client)(nil)

View File

@@ -227,7 +227,7 @@ func (c *ReadyChecker) jobReady(job *batchv1.Job) bool {
c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName())
return false
}
if job.Status.Succeeded < *job.Spec.Completions {
if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions {
c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName())
return false
}
@@ -377,7 +377,7 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
expectedReplicas := replicas - partition
// Make sure all the updated pods have been scheduled
if int(sts.Status.UpdatedReplicas) != expectedReplicas {
if int(sts.Status.UpdatedReplicas) < expectedReplicas {
c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas)
return false
}

View File

@@ -28,6 +28,7 @@ import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@@ -60,6 +61,24 @@ func (w *waiter) waitForResources(created ResourceList) error {
}, ctx.Done())
}
// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached
func (w *waiter) waitForDeletedResources(deleted ResourceList) error {
w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout)
ctx, cancel := context.WithTimeout(context.Background(), w.timeout)
defer cancel()
return wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
for _, v := range deleted {
err := v.Get()
if err == nil || !apierrors.IsNotFound(err) {
return false, err
}
}
return true, nil
}, ctx.Done())
}
// SelectorsForObject returns the pod label selector for a given object
//
// Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84

View File

@@ -18,6 +18,7 @@ package rules // import "helm.sh/helm/v3/pkg/lint/rules"
import (
"fmt"
"strconv"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -25,11 +26,12 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme"
)
const (
var (
// This should be set in the Makefile based on the version of client-go being imported.
// These constants will be overwritten with LDFLAGS
k8sVersionMajor = 1
k8sVersionMinor = 20
// These constants will be overwritten with LDFLAGS. The version components must be
// strings in order for LDFLAGS to set them.
k8sVersionMajor = "1"
k8sVersionMinor = "20"
)
// deprecatedAPIError indicates than an API is deprecated in Kubernetes
@@ -60,7 +62,16 @@ func validateNoDeprecations(resource *K8sYamlStruct) error {
}
return err
}
if !deprecation.IsDeprecated(runtimeObject, k8sVersionMajor, k8sVersionMinor) {
maj, err := strconv.Atoi(k8sVersionMajor)
if err != nil {
return err
}
min, err := strconv.Atoi(k8sVersionMinor)
if err != nil {
return err
}
if !deprecation.IsDeprecated(runtimeObject, maj, min) {
return nil
}
gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind)

View File

@@ -70,6 +70,12 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
Namespace: namespace,
}
// lint ignores import-values
// See https://github.com/helm/helm/issues/9658
if err := chartutil.ProcessDependencies(chart, values); err != nil {
return
}
cvals, err := chartutil.CoalesceValues(chart, values)
if err != nil {
return
@@ -113,7 +119,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463
// Check that all the templates have a matching value
//linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
// linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037
// linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate)))
@@ -146,6 +152,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct))
linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent))
}
}
}
@@ -293,6 +300,28 @@ func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error {
}
return nil
}
func validateListAnnotations(yamlStruct *K8sYamlStruct, manifest string) error {
if yamlStruct.Kind == "List" {
m := struct {
Items []struct {
Metadata struct {
Annotations map[string]string
}
}
}{}
if err := yaml.Unmarshal([]byte(manifest), &m); err != nil {
return validateYamlContent(err)
}
for _, i := range m.Items {
if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok {
return errors.New("Annotation 'helm.sh/resource-policy' within List objects are ignored")
}
}
}
return nil
}
// K8sYamlStruct stubs a Kubernetes YAML file.
//

View File

@@ -152,7 +152,7 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
}
if len(parts) == 0 || parts[0] == "" {
return "", nil, fmt.Errorf("No plugin command is applicable")
return "", nil, fmt.Errorf("no plugin command is applicable")
}
main := parts[0]

View File

@@ -27,23 +27,24 @@ import (
type execRender struct {
binaryPath string
args []string
}
// NewExec returns a PostRenderer implementation that calls the provided binary.
// It returns an error if the binary cannot be found. If the path does not
// contain any separators, it will search in $PATH, otherwise it will resolve
// any relative paths to a fully qualified path
func NewExec(binaryPath string) (PostRenderer, error) {
func NewExec(binaryPath string, args ...string) (PostRenderer, error) {
fullPath, err := getFullPath(binaryPath)
if err != nil {
return nil, err
}
return &execRender{fullPath}, nil
return &execRender{fullPath, args}, nil
}
// Run the configured binary for the post render
func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) {
cmd := exec.Command(p.binaryPath)
cmd := exec.Command(p.binaryPath, p.args...)
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err

View File

@@ -26,9 +26,9 @@ import (
"strings"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/clearsign"
"golang.org/x/crypto/openpgp/packet"
"golang.org/x/crypto/openpgp" //nolint
"golang.org/x/crypto/openpgp/clearsign" //nolint
"golang.org/x/crypto/openpgp/packet" //nolint
"sigs.k8s.io/yaml"
hapi "helm.sh/helm/v3/pkg/chart"
@@ -216,7 +216,7 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) {
b, err := messageBlock(chartpath)
if err != nil {
return "", nil
return "", err
}
// Sign the buffer
@@ -224,9 +224,24 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) {
if err != nil {
return "", err
}
_, err = io.Copy(w, b)
w.Close()
return out.String(), err
if err != nil {
// NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which
// actually does the PGP signing, and therefore is the part which uses the private key.
// In other words, if we call Close here, there's a risk that there's an attempt to use the
// private key to sign garbage data (since we know that io.Copy failed, `w` won't contain
// anything useful).
return "", errors.Wrap(err, "failed to write to clearsign encoder")
}
err = w.Close()
if err != nil {
return "", errors.Wrap(err, "failed to either sign or armor message block")
}
return out.String(), nil
}
// Verify checks a signature and verifies that it is legit for a chart.

View File

@@ -1,11 +1,10 @@
/*
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
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,
@@ -14,15 +13,9 @@ 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
}
)
/*
Package pusher provides a generalized tool for uploading data by scheme.
This provides a method by which the plugin system can load arbitrary protocol
handlers based upon a URL scheme.
*/
package pusher

View File

@@ -0,0 +1,104 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pusher
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/registry"
)
// OCIPusher is the default OCI backend handler
type OCIPusher struct {
opts options
}
// Push performs a Push from repo.Pusher.
func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error {
for _, opt := range options {
opt(&pusher.opts)
}
return pusher.push(chartRef, href)
}
func (pusher *OCIPusher) push(chartRef, href string) error {
stat, err := os.Stat(chartRef)
if err != nil {
if os.IsNotExist(err) {
return errors.Errorf("%s: no such file", chartRef)
}
return err
}
if stat.IsDir() {
return errors.New("cannot push directory, must provide chart archive (.tgz)")
}
meta, err := loader.Load(chartRef)
if err != nil {
return err
}
client := pusher.opts.registryClient
chartBytes, err := ioutil.ReadFile(chartRef)
if err != nil {
return err
}
var pushOpts []registry.PushOption
provRef := fmt.Sprintf("%s.prov", chartRef)
if _, err := os.Stat(provRef); err == nil {
provBytes, err := ioutil.ReadFile(provRef)
if err != nil {
return err
}
pushOpts = append(pushOpts, registry.PushOptProvData(provBytes))
}
ref := fmt.Sprintf("%s:%s",
path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name),
meta.Metadata.Version)
_, err = client.Push(chartBytes, ref, pushOpts...)
return err
}
// NewOCIPusher constructs a valid OCI client as a Pusher
func NewOCIPusher(ops ...Option) (Pusher, error) {
registryClient, err := registry.NewClient()
if err != nil {
return nil, err
}
client := OCIPusher{
opts: options{
registryClient: registryClient,
},
}
for _, opt := range ops {
opt(&client.opts)
}
return &client, nil
}

View File

@@ -0,0 +1,95 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pusher
import (
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
)
// options are generic parameters to be provided to the pusher during instantiation.
//
// Pushers may or may not ignore these parameters as they are passed in.
type options struct {
registryClient *registry.Client
}
// Option allows specifying various settings configurable by the user for overriding the defaults
// used when performing Push operations with the Pusher.
type Option func(*options)
// WithRegistryClient sets the registryClient option.
func WithRegistryClient(client *registry.Client) Option {
return func(opts *options) {
opts.registryClient = client
}
}
// Pusher is an interface to support upload to the specified URL.
type Pusher interface {
// Push file content by url string
Push(chartRef, url string, options ...Option) error
}
// Constructor is the function for every pusher which creates a specific instance
// according to the configuration
type Constructor func(options ...Option) (Pusher, error)
// Provider represents any pusher and the schemes that it supports.
type Provider struct {
Schemes []string
New Constructor
}
// Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool {
for _, i := range p.Schemes {
if i == scheme {
return true
}
}
return false
}
// Providers is a collection of Provider objects.
type Providers []Provider
// ByScheme returns a Provider that handles the given scheme.
//
// If no provider handles this scheme, this will return an error.
func (p Providers) ByScheme(scheme string) (Pusher, error) {
for _, pp := range p {
if pp.Provides(scheme) {
return pp.New()
}
}
return nil, errors.Errorf("scheme %q not supported", scheme)
}
var ociProvider = Provider{
Schemes: []string{registry.OCIScheme},
New: NewOCIPusher,
}
// All finds all of the registered pushers as a list of Provider instances.
// Currently, just the built-in pushers are collected.
func All(settings *cli.EnvSettings) Providers {
result := Providers{ociProvider}
return result
}

View File

@@ -0,0 +1,629 @@
/*
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/pkg/registry"
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"sort"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/containerd/containerd/remotes"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"oras.land/oras-go/pkg/auth"
dockerauth "oras.land/oras-go/pkg/auth/docker"
"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
"oras.land/oras-go/pkg/registry"
registryremote "oras.land/oras-go/pkg/registry/remote"
registryauth "oras.land/oras-go/pkg/registry/remote/auth"
"helm.sh/helm/v3/internal/version"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/helmpath"
)
// See https://github.com/helm/helm/issues/10166
const registryUnderscoreMessage = `
OCI artifact references (e.g. tags) do not support the plus sign (+). To support
storing semantic versions, Helm adopts the convention of changing plus (+) to
an underscore (_) in chart version tags when pushing to a registry and back to
a plus (+) when pulling from a registry.`
type (
// Client works with OCI-compliant registries
Client struct {
debug bool
// path to repository config file e.g. ~/.docker/config.json
credentialsFile string
out io.Writer
authorizer auth.Client
registryAuthorizer *registryauth.Client
resolver remotes.Resolver
}
// ClientOption allows specifying various settings configurable by the user for overriding the defaults
// used when creating a new default client
ClientOption func(*Client)
)
// NewClient returns a new registry client with config
func NewClient(options ...ClientOption) (*Client, error) {
client := &Client{
out: ioutil.Discard,
}
for _, option := range options {
option(client)
}
if client.credentialsFile == "" {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
}
if client.authorizer == nil {
authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile)
if err != nil {
return nil, err
}
client.authorizer = authClient
}
if client.resolver == nil {
headers := http.Header{}
headers.Set("User-Agent", version.GetUserAgent())
opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)}
resolver, err := client.authorizer.ResolverWithOpts(opts...)
if err != nil {
return nil, err
}
client.resolver = resolver
}
if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{
Header: http.Header{
"User-Agent": {version.GetUserAgent()},
},
Cache: registryauth.DefaultCache,
Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) {
dockerClient, ok := client.authorizer.(*dockerauth.Client)
if !ok {
return registryauth.EmptyCredential, errors.New("unable to obtain docker client")
}
username, password, err := dockerClient.Credential(reg)
if err != nil {
return registryauth.EmptyCredential, errors.New("unable to retrieve credentials")
}
// A blank returned username and password value is a bearer token
if username == "" && password != "" {
return registryauth.Credential{
RefreshToken: password,
}, nil
}
return registryauth.Credential{
Username: username,
Password: password,
}, nil
},
}
}
return client, nil
}
// 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
}
}
// ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set
func ClientOptCredentialsFile(credentialsFile string) ClientOption {
return func(client *Client) {
client.credentialsFile = credentialsFile
}
}
type (
// LoginOption allows specifying various settings on login
LoginOption func(*loginOperation)
loginOperation struct {
username string
password string
insecure bool
}
)
// Login logs into a registry
func (c *Client) Login(host string, options ...LoginOption) error {
operation := &loginOperation{}
for _, option := range options {
option(operation)
}
authorizerLoginOpts := []auth.LoginOption{
auth.WithLoginContext(ctx(c.out, c.debug)),
auth.WithLoginHostname(host),
auth.WithLoginUsername(operation.username),
auth.WithLoginSecret(operation.password),
auth.WithLoginUserAgent(version.GetUserAgent()),
}
if operation.insecure {
authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure())
}
if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil {
return err
}
fmt.Fprintln(c.out, "Login Succeeded")
return nil
}
// LoginOptBasicAuth returns a function that sets the username/password settings on login
func LoginOptBasicAuth(username string, password string) LoginOption {
return func(operation *loginOperation) {
operation.username = username
operation.password = password
}
}
// LoginOptInsecure returns a function that sets the insecure setting on login
func LoginOptInsecure(insecure bool) LoginOption {
return func(operation *loginOperation) {
operation.insecure = insecure
}
}
type (
// LogoutOption allows specifying various settings on logout
LogoutOption func(*logoutOperation)
logoutOperation struct{}
)
// Logout logs out of a registry
func (c *Client) Logout(host string, opts ...LogoutOption) error {
operation := &logoutOperation{}
for _, opt := range opts {
opt(operation)
}
if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil {
return err
}
fmt.Fprintf(c.out, "Removing login credentials for %s\n", host)
return nil
}
type (
// PullOption allows specifying various settings on pull
PullOption func(*pullOperation)
// PullResult is the result returned upon successful pull.
PullResult struct {
Manifest *descriptorPullSummary `json:"manifest"`
Config *descriptorPullSummary `json:"config"`
Chart *descriptorPullSummaryWithMeta `json:"chart"`
Prov *descriptorPullSummary `json:"prov"`
Ref string `json:"ref"`
}
descriptorPullSummary struct {
Data []byte `json:"-"`
Digest string `json:"digest"`
Size int64 `json:"size"`
}
descriptorPullSummaryWithMeta struct {
descriptorPullSummary
Meta *chart.Metadata `json:"meta"`
}
pullOperation struct {
withChart bool
withProv bool
ignoreMissingProv bool
}
)
// Pull downloads a chart from a registry
func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
parsedRef, err := parseReference(ref)
if err != nil {
return nil, err
}
operation := &pullOperation{
withChart: true, // By default, always download the chart layer
}
for _, option := range options {
option(operation)
}
if !operation.withChart && !operation.withProv {
return nil, errors.New(
"must specify at least one layer to pull (chart/prov)")
}
memoryStore := content.NewMemory()
allowedMediaTypes := []string{
ConfigMediaType,
}
minNumDescriptors := 1 // 1 for the config
if operation.withChart {
minNumDescriptors++
allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType)
}
if operation.withProv {
if !operation.ignoreMissingProv {
minNumDescriptors++
}
allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType)
}
var descriptors, layers []ocispec.Descriptor
registryStore := content.Registry{Resolver: c.resolver}
manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "",
oras.WithPullEmptyNameAllowed(),
oras.WithAllowedMediaTypes(allowedMediaTypes),
oras.WithLayerDescriptors(func(l []ocispec.Descriptor) {
layers = l
}))
if err != nil {
return nil, err
}
descriptors = append(descriptors, manifest)
descriptors = append(descriptors, layers...)
numDescriptors := len(descriptors)
if numDescriptors < minNumDescriptors {
return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d",
minNumDescriptors, numDescriptors)
}
var configDescriptor *ocispec.Descriptor
var chartDescriptor *ocispec.Descriptor
var provDescriptor *ocispec.Descriptor
for _, descriptor := range descriptors {
d := descriptor
switch d.MediaType {
case ConfigMediaType:
configDescriptor = &d
case ChartLayerMediaType:
chartDescriptor = &d
case ProvLayerMediaType:
provDescriptor = &d
case LegacyChartLayerMediaType:
chartDescriptor = &d
fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType)
}
}
if configDescriptor == nil {
return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType)
}
if operation.withChart && chartDescriptor == nil {
return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s",
ChartLayerMediaType)
}
var provMissing bool
if operation.withProv && provDescriptor == nil {
if operation.ignoreMissingProv {
provMissing = true
} else {
return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s",
ProvLayerMediaType)
}
}
result := &PullResult{
Manifest: &descriptorPullSummary{
Digest: manifest.Digest.String(),
Size: manifest.Size,
},
Config: &descriptorPullSummary{
Digest: configDescriptor.Digest.String(),
Size: configDescriptor.Size,
},
Chart: &descriptorPullSummaryWithMeta{},
Prov: &descriptorPullSummary{},
Ref: parsedRef.String(),
}
var getManifestErr error
if _, manifestData, ok := memoryStore.Get(manifest); !ok {
getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest)
} else {
result.Manifest.Data = manifestData
}
if getManifestErr != nil {
return nil, getManifestErr
}
var getConfigDescriptorErr error
if _, configData, ok := memoryStore.Get(*configDescriptor); !ok {
getConfigDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest)
} else {
result.Config.Data = configData
var meta *chart.Metadata
if err := json.Unmarshal(configData, &meta); err != nil {
return nil, err
}
result.Chart.Meta = meta
}
if getConfigDescriptorErr != nil {
return nil, getConfigDescriptorErr
}
if operation.withChart {
var getChartDescriptorErr error
if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok {
getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest)
} else {
result.Chart.Data = chartData
result.Chart.Digest = chartDescriptor.Digest.String()
result.Chart.Size = chartDescriptor.Size
}
if getChartDescriptorErr != nil {
return nil, getChartDescriptorErr
}
}
if operation.withProv && !provMissing {
var getProvDescriptorErr error
if _, provData, ok := memoryStore.Get(*provDescriptor); !ok {
getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest)
} else {
result.Prov.Data = provData
result.Prov.Digest = provDescriptor.Digest.String()
result.Prov.Size = provDescriptor.Size
}
if getProvDescriptorErr != nil {
return nil, getProvDescriptorErr
}
}
fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref)
fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest)
if strings.Contains(result.Ref, "_") {
fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref)
fmt.Fprint(c.out, registryUnderscoreMessage+"\n")
}
return result, nil
}
// PullOptWithChart returns a function that sets the withChart setting on pull
func PullOptWithChart(withChart bool) PullOption {
return func(operation *pullOperation) {
operation.withChart = withChart
}
}
// PullOptWithProv returns a function that sets the withProv setting on pull
func PullOptWithProv(withProv bool) PullOption {
return func(operation *pullOperation) {
operation.withProv = withProv
}
}
// PullOptIgnoreMissingProv returns a function that sets the ignoreMissingProv setting on pull
func PullOptIgnoreMissingProv(ignoreMissingProv bool) PullOption {
return func(operation *pullOperation) {
operation.ignoreMissingProv = ignoreMissingProv
}
}
type (
// PushOption allows specifying various settings on push
PushOption func(*pushOperation)
// PushResult is the result returned upon successful push.
PushResult struct {
Manifest *descriptorPushSummary `json:"manifest"`
Config *descriptorPushSummary `json:"config"`
Chart *descriptorPushSummaryWithMeta `json:"chart"`
Prov *descriptorPushSummary `json:"prov"`
Ref string `json:"ref"`
}
descriptorPushSummary struct {
Digest string `json:"digest"`
Size int64 `json:"size"`
}
descriptorPushSummaryWithMeta struct {
descriptorPushSummary
Meta *chart.Metadata `json:"meta"`
}
pushOperation struct {
provData []byte
strictMode bool
}
)
// Push uploads a chart to a registry.
func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResult, error) {
parsedRef, err := parseReference(ref)
if err != nil {
return nil, err
}
operation := &pushOperation{
strictMode: true, // By default, enable strict mode
}
for _, option := range options {
option(operation)
}
meta, err := extractChartMeta(data)
if err != nil {
return nil, err
}
if operation.strictMode {
if !strings.HasSuffix(ref, fmt.Sprintf("/%s:%s", meta.Name, meta.Version)) {
return nil, errors.New(
"strict mode enabled, ref basename and tag must match the chart name and version")
}
}
memoryStore := content.NewMemory()
chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data)
if err != nil {
return nil, err
}
configData, err := json.Marshal(meta)
if err != nil {
return nil, err
}
configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData)
if err != nil {
return nil, err
}
descriptors := []ocispec.Descriptor{chartDescriptor}
var provDescriptor ocispec.Descriptor
if operation.provData != nil {
provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData)
if err != nil {
return nil, err
}
descriptors = append(descriptors, provDescriptor)
}
manifestData, manifest, err := content.GenerateManifest(&configDescriptor, nil, descriptors...)
if err != nil {
return nil, err
}
if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil {
return nil, err
}
registryStore := content.Registry{Resolver: c.resolver}
_, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.String(), registryStore, "",
oras.WithNameValidation(nil))
if err != nil {
return nil, err
}
chartSummary := &descriptorPushSummaryWithMeta{
Meta: meta,
}
chartSummary.Digest = chartDescriptor.Digest.String()
chartSummary.Size = chartDescriptor.Size
result := &PushResult{
Manifest: &descriptorPushSummary{
Digest: manifest.Digest.String(),
Size: manifest.Size,
},
Config: &descriptorPushSummary{
Digest: configDescriptor.Digest.String(),
Size: configDescriptor.Size,
},
Chart: chartSummary,
Prov: &descriptorPushSummary{}, // prevent nil references
Ref: parsedRef.String(),
}
if operation.provData != nil {
result.Prov = &descriptorPushSummary{
Digest: provDescriptor.Digest.String(),
Size: provDescriptor.Size,
}
}
fmt.Fprintf(c.out, "Pushed: %s\n", result.Ref)
fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest)
if strings.Contains(parsedRef.Reference, "_") {
fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref)
fmt.Fprint(c.out, registryUnderscoreMessage+"\n")
}
return result, err
}
// PushOptProvData returns a function that sets the prov bytes setting on push
func PushOptProvData(provData []byte) PushOption {
return func(operation *pushOperation) {
operation.provData = provData
}
}
// PushOptStrictMode returns a function that sets the strictMode setting on push
func PushOptStrictMode(strictMode bool) PushOption {
return func(operation *pushOperation) {
operation.strictMode = strictMode
}
}
// Tags provides a sorted list all semver compliant tags for a given repository
func (c *Client) Tags(ref string) ([]string, error) {
parsedReference, err := registry.ParseReference(ref)
if err != nil {
return nil, err
}
repository := registryremote.Repository{
Reference: parsedReference,
Client: c.registryAuthorizer,
}
var registryTags []string
for {
registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository)
if err != nil {
// Fallback to http based request
if !repository.PlainHTTP && strings.Contains(err.Error(), "server gave HTTP response") {
repository.PlainHTTP = true
continue
}
return nil, err
}
break
}
var tagVersions []*semver.Version
for _, tag := range registryTags {
// Change underscore (_) back to plus (+) for Helm
// See https://github.com/helm/helm/issues/10166
tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+"))
if err == nil {
tagVersions = append(tagVersions, tagVersion)
}
}
// Sort the collection
sort.Sort(sort.Reverse(semver.Collection(tagVersions)))
tags := make([]string, len(tagVersions))
for iTv, tv := range tagVersions {
tags[iTv] = tv.String()
}
return tags, nil
}

View File

@@ -0,0 +1,37 @@
/*
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/pkg/registry"
const (
// OCIScheme is the URL scheme for OCI-based requests
OCIScheme = "oci"
// CredentialsFileBasename is the filename for auth credentials file
CredentialsFileBasename = "registry/config.json"
// ConfigMediaType is the reserved media type for the Helm chart manifest config
ConfigMediaType = "application/vnd.cncf.helm.config.v1+json"
// ChartLayerMediaType is the reserved media type for Helm chart package content
ChartLayerMediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
// ProvLayerMediaType is the reserved media type for Helm chart provenance files
ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov"
// LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content.
LegacyChartLayerMediaType = "application/tar+gzip"
)

View File

@@ -0,0 +1,131 @@
/*
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/pkg/registry"
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
orascontext "oras.land/oras-go/pkg/context"
"oras.land/oras-go/pkg/registry"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
// IsOCI determines whether or not a URL is to be treated as an OCI URL
func IsOCI(url string) bool {
return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
}
// ContainsTag determines whether a tag is found in a provided list of tags
func ContainsTag(tags []string, tag string) bool {
for _, t := range tags {
if tag == t {
return true
}
}
return false
}
func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) {
var constraint *semver.Constraints
if versionString == "" {
// If string is empty, set wildcard constraint
constraint, _ = semver.NewConstraint("*")
} else {
// when customer input exact version, check whether have exact match
// one first
for _, v := range tags {
if versionString == v {
return v, nil
}
}
// Otherwise set constraint to the string given
var err error
constraint, err = semver.NewConstraint(versionString)
if err != nil {
return "", err
}
}
// Otherwise try to find the first available version matching the string,
// in case it is a constraint
for _, v := range tags {
test, err := semver.NewVersion(v)
if err != nil {
continue
}
if constraint.Check(test) {
return v, nil
}
}
return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString)
}
// extractChartMeta is used to extract a chart metadata from a byte array
func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
ch, err := loader.LoadArchive(bytes.NewReader(chartData))
if err != nil {
return nil, err
}
return ch.Metadata, nil
}
// 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
}
// parseReference will parse and validate the reference, and clean tags when
// applicable tags are only cleaned when plus (+) signs are present, and are
// converted to underscores (_) before pushing
// See https://github.com/helm/helm/issues/10166
func parseReference(raw string) (registry.Reference, error) {
// The sole possible reference modification is replacing plus (+) signs
// present in tags with underscores (_). To do this properly, we first
// need to identify a tag, and then pass it on to the reference parser
// NOTE: Passing immediately to the reference parser will fail since (+)
// signs are an invalid tag character, and simply replacing all plus (+)
// occurrences could invalidate other portions of the URI
parts := strings.Split(raw, ":")
if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") {
tag := parts[len(parts)-1]
if tag != "" {
// Replace any plus (+) signs with known underscore (_) conversion
newTag := strings.ReplaceAll(tag, "+", "_")
raw = strings.ReplaceAll(raw, tag, newTag)
}
}
return registry.ParseReference(raw)
}

View File

@@ -61,6 +61,7 @@ var InstallOrder KindSortOrder = []string{
"StatefulSet",
"Job",
"CronJob",
"IngressClass",
"Ingress",
"APIService",
}
@@ -71,6 +72,7 @@ var InstallOrder KindSortOrder = []string{
var UninstallOrder KindSortOrder = []string{
"APIService",
"Ingress",
"IngressClass",
"Service",
"CronJob",
"Job",

View File

@@ -50,6 +50,8 @@ var (
ErrNoChartVersion = errors.New("no chart version found")
// ErrNoChartName indicates that a chart with the given name is not found.
ErrNoChartName = errors.New("no chart name found")
// ErrEmptyIndexYaml indicates that the content of index.yaml is empty.
ErrEmptyIndexYaml = errors.New("empty index.yaml file")
)
// ChartVersions is a list of versioned chart references.
@@ -326,6 +328,11 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
func loadIndex(data []byte, source string) (*IndexFile, error) {
i := &IndexFile{}
if len(data) == 0 {
return i, ErrEmptyIndexYaml
}
if err := yaml.UnmarshalStrict(data, i); err != nil {
return i, err
}

View File

@@ -55,8 +55,8 @@ func (s *Storage) Get(name string, version int) (*rspb.Release, error) {
}
// Create creates a new storage entry holding the release. An
// error is returned if the storage driver failed to store the
// release, or a release with identical an key already exists.
// error is returned if the storage driver fails to store the
// release, or a release with an identical key already exists.
func (s *Storage) Create(rls *rspb.Release) error {
s.Log("creating release %q", makeKey(rls.Name, rls.Version))
if s.MaxHistory > 0 {
@@ -177,7 +177,7 @@ func (s *Storage) removeLeastRecent(name string, max int) error {
relutil.SortByRevision(h)
lastDeployed, err := s.Deployed(name)
if err != nil {
if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) {
return err
}

View File

@@ -0,0 +1,58 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package uploader
import (
"fmt"
"io"
"net/url"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/pusher"
"helm.sh/helm/v3/pkg/registry"
)
// ChartUploader handles uploading a chart.
type ChartUploader struct {
// Out is the location to write warning and info messages.
Out io.Writer
// Pusher collection for the operation
Pushers pusher.Providers
// Options provide parameters to be passed along to the Pusher being initialized.
Options []pusher.Option
// RegistryClient is a client for interacting with registries.
RegistryClient *registry.Client
}
// UploadTo uploads a chart. Depending on the settings, it may also upload a provenance file.
func (c *ChartUploader) UploadTo(ref, remote string) error {
u, err := url.Parse(remote)
if err != nil {
return errors.Errorf("invalid chart URL format: %s", remote)
}
if u.Scheme == "" {
return fmt.Errorf("scheme prefix missing from remote (e.g. \"%s://\")", registry.OCIScheme)
}
p, err := c.Pushers.ByScheme(u.Scheme)
if err != nil {
return err
}
return p.Push(ref, u.String(), c.Options...)
}

View File

@@ -13,8 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*Package gates provides a general tool for working with experimental feature gates.
/*Package uploader provides a library for uploading charts.
This provides convenience methods where the user can determine if certain experimental features are enabled.
This package contains tools for uploading charts to registries.
*/
package gates
package uploader