28
vendor/helm.sh/helm/v3/internal/experimental/registry/authorizer.go
vendored
Normal file
28
vendor/helm.sh/helm/v3/internal/experimental/registry/authorizer.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"github.com/deislabs/oras/pkg/auth"
|
||||
)
|
||||
|
||||
type (
|
||||
// Authorizer handles registry auth operations
|
||||
Authorizer struct {
|
||||
auth.Client
|
||||
}
|
||||
)
|
||||
368
vendor/helm.sh/helm/v3/internal/experimental/registry/cache.go
vendored
Normal file
368
vendor/helm.sh/helm/v3/internal/experimental/registry/cache.go
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
orascontent "github.com/deislabs/oras/pkg/content"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheRootDir is the root directory for a cache
|
||||
CacheRootDir = "cache"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cache handles local/in-memory storage of Helm charts, compliant with OCI Layout
|
||||
Cache struct {
|
||||
debug bool
|
||||
out io.Writer
|
||||
rootDir string
|
||||
ociStore *orascontent.OCIStore
|
||||
memoryStore *orascontent.Memorystore
|
||||
}
|
||||
|
||||
// CacheRefSummary contains as much info as available describing a chart reference in cache
|
||||
// Note: fields here are sorted by the order in which they are set in FetchReference method
|
||||
CacheRefSummary struct {
|
||||
Name string
|
||||
Repo string
|
||||
Tag string
|
||||
Exists bool
|
||||
Manifest *ocispec.Descriptor
|
||||
Config *ocispec.Descriptor
|
||||
ContentLayer *ocispec.Descriptor
|
||||
Size int64
|
||||
Digest digest.Digest
|
||||
CreatedAt time.Time
|
||||
Chart *chart.Chart
|
||||
}
|
||||
)
|
||||
|
||||
// NewCache returns a new OCI Layout-compliant cache with config
|
||||
func NewCache(opts ...CacheOption) (*Cache, error) {
|
||||
cache := &Cache{
|
||||
out: ioutil.Discard,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(cache)
|
||||
}
|
||||
// validate
|
||||
if cache.rootDir == "" {
|
||||
return nil, errors.New("must set cache root dir on initialization")
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// FetchReference retrieves a chart ref from cache
|
||||
func (cache *Cache) FetchReference(ref *Reference) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := CacheRefSummary{
|
||||
Name: ref.FullName(),
|
||||
Repo: ref.Repo,
|
||||
Tag: ref.Tag,
|
||||
}
|
||||
for _, desc := range cache.ociStore.ListReferences() {
|
||||
if desc.Annotations[ocispec.AnnotationRefName] == r.Name {
|
||||
r.Exists = true
|
||||
manifestBytes, err := cache.fetchBlob(&desc)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
var manifest ocispec.Manifest
|
||||
err = json.Unmarshal(manifestBytes, &manifest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Manifest = &desc
|
||||
r.Config = &manifest.Config
|
||||
numLayers := len(manifest.Layers)
|
||||
if numLayers != 1 {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest does not contain exactly 1 layer (total: %d)", numLayers))
|
||||
}
|
||||
var contentLayer *ocispec.Descriptor
|
||||
for _, layer := range manifest.Layers {
|
||||
switch layer.MediaType {
|
||||
case HelmChartContentLayerMediaType:
|
||||
contentLayer = &layer
|
||||
}
|
||||
}
|
||||
if contentLayer == nil {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest does not contain a layer with mediatype %s", HelmChartContentLayerMediaType))
|
||||
}
|
||||
if contentLayer.Size == 0 {
|
||||
return &r, errors.New(
|
||||
fmt.Sprintf("manifest layer with mediatype %s is of size 0", HelmChartContentLayerMediaType))
|
||||
}
|
||||
r.ContentLayer = contentLayer
|
||||
info, err := cache.ociStore.Info(ctx(cache.out, cache.debug), contentLayer.Digest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Size = info.Size
|
||||
r.Digest = info.Digest
|
||||
r.CreatedAt = info.CreatedAt
|
||||
contentBytes, err := cache.fetchBlob(contentLayer)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
ch, err := loader.LoadArchive(bytes.NewBuffer(contentBytes))
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Chart = ch
|
||||
}
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// StoreReference stores a chart ref in cache
|
||||
func (cache *Cache) StoreReference(ref *Reference, ch *chart.Chart) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := CacheRefSummary{
|
||||
Name: ref.FullName(),
|
||||
Repo: ref.Repo,
|
||||
Tag: ref.Tag,
|
||||
Chart: ch,
|
||||
}
|
||||
existing, _ := cache.FetchReference(ref)
|
||||
r.Exists = existing.Exists
|
||||
config, _, err := cache.saveChartConfig(ch)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Config = config
|
||||
contentLayer, _, err := cache.saveChartContentLayer(ch)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.ContentLayer = contentLayer
|
||||
info, err := cache.ociStore.Info(ctx(cache.out, cache.debug), contentLayer.Digest)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Size = info.Size
|
||||
r.Digest = info.Digest
|
||||
r.CreatedAt = info.CreatedAt
|
||||
manifest, _, err := cache.saveChartManifest(config, contentLayer)
|
||||
if err != nil {
|
||||
return &r, err
|
||||
}
|
||||
r.Manifest = manifest
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// DeleteReference deletes a chart ref from cache
|
||||
// TODO: garbage collection, only manifest removed
|
||||
func (cache *Cache) DeleteReference(ref *Reference) (*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := cache.FetchReference(ref)
|
||||
if err != nil || !r.Exists {
|
||||
return r, err
|
||||
}
|
||||
cache.ociStore.DeleteReference(r.Name)
|
||||
err = cache.ociStore.SaveIndex()
|
||||
return r, err
|
||||
}
|
||||
|
||||
// ListReferences lists all chart refs in a cache
|
||||
func (cache *Cache) ListReferences() ([]*CacheRefSummary, error) {
|
||||
if err := cache.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rr []*CacheRefSummary
|
||||
for _, desc := range cache.ociStore.ListReferences() {
|
||||
name := desc.Annotations[ocispec.AnnotationRefName]
|
||||
if name == "" {
|
||||
if cache.debug {
|
||||
fmt.Fprintf(cache.out, "warning: found manifest without name: %s", desc.Digest.Hex())
|
||||
}
|
||||
continue
|
||||
}
|
||||
ref, err := ParseReference(name)
|
||||
if err != nil {
|
||||
return rr, err
|
||||
}
|
||||
r, err := cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return rr, err
|
||||
}
|
||||
rr = append(rr, r)
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
// AddManifest provides a manifest to the cache index.json
|
||||
func (cache *Cache) AddManifest(ref *Reference, manifest *ocispec.Descriptor) error {
|
||||
if err := cache.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
cache.ociStore.AddReference(ref.FullName(), *manifest)
|
||||
err := cache.ociStore.SaveIndex()
|
||||
return err
|
||||
}
|
||||
|
||||
// Provider provides a valid containerd Provider
|
||||
func (cache *Cache) Provider() content.Provider {
|
||||
return content.Provider(cache.ociStore)
|
||||
}
|
||||
|
||||
// Ingester provides a valid containerd Ingester
|
||||
func (cache *Cache) Ingester() content.Ingester {
|
||||
return content.Ingester(cache.ociStore)
|
||||
}
|
||||
|
||||
// ProvideIngester provides a valid oras ProvideIngester
|
||||
func (cache *Cache) ProvideIngester() orascontent.ProvideIngester {
|
||||
return orascontent.ProvideIngester(cache.ociStore)
|
||||
}
|
||||
|
||||
// init creates files needed necessary for OCI layout store
|
||||
func (cache *Cache) init() error {
|
||||
if cache.ociStore == nil {
|
||||
ociStore, err := orascontent.NewOCIStore(cache.rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cache.ociStore = ociStore
|
||||
cache.memoryStore = orascontent.NewMemoryStore()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveChartConfig stores the Chart.yaml as json blob and returns a descriptor
|
||||
func (cache *Cache) saveChartConfig(ch *chart.Chart) (*ocispec.Descriptor, bool, error) {
|
||||
configBytes, err := json.Marshal(ch.Metadata)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
configExists, err := cache.storeBlob(configBytes)
|
||||
if err != nil {
|
||||
return nil, configExists, err
|
||||
}
|
||||
descriptor := cache.memoryStore.Add("", HelmChartConfigMediaType, configBytes)
|
||||
return &descriptor, configExists, nil
|
||||
}
|
||||
|
||||
// saveChartContentLayer stores the chart as tarball blob and returns a descriptor
|
||||
func (cache *Cache) saveChartContentLayer(ch *chart.Chart) (*ocispec.Descriptor, bool, error) {
|
||||
destDir := filepath.Join(cache.rootDir, ".build")
|
||||
os.MkdirAll(destDir, 0755)
|
||||
tmpFile, err := chartutil.Save(ch, destDir)
|
||||
defer os.Remove(tmpFile)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to save")
|
||||
}
|
||||
contentBytes, err := ioutil.ReadFile(tmpFile)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
contentExists, err := cache.storeBlob(contentBytes)
|
||||
if err != nil {
|
||||
return nil, contentExists, err
|
||||
}
|
||||
descriptor := cache.memoryStore.Add("", HelmChartContentLayerMediaType, contentBytes)
|
||||
return &descriptor, contentExists, nil
|
||||
}
|
||||
|
||||
// saveChartManifest stores the chart manifest as json blob and returns a descriptor
|
||||
func (cache *Cache) saveChartManifest(config *ocispec.Descriptor, contentLayer *ocispec.Descriptor) (*ocispec.Descriptor, bool, error) {
|
||||
manifest := ocispec.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
Config: *config,
|
||||
Layers: []ocispec.Descriptor{*contentLayer},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
manifestExists, err := cache.storeBlob(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, manifestExists, err
|
||||
}
|
||||
descriptor := ocispec.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageManifest,
|
||||
Digest: digest.FromBytes(manifestBytes),
|
||||
Size: int64(len(manifestBytes)),
|
||||
}
|
||||
return &descriptor, manifestExists, nil
|
||||
}
|
||||
|
||||
// storeBlob stores a blob on filesystem
|
||||
func (cache *Cache) storeBlob(blobBytes []byte) (bool, error) {
|
||||
var exists bool
|
||||
writer, err := cache.ociStore.Store.Writer(ctx(cache.out, cache.debug),
|
||||
content.WithRef(digest.FromBytes(blobBytes).Hex()))
|
||||
if err != nil {
|
||||
return exists, err
|
||||
}
|
||||
_, err = writer.Write(blobBytes)
|
||||
if err != nil {
|
||||
return exists, err
|
||||
}
|
||||
err = writer.Commit(ctx(cache.out, cache.debug), 0, writer.Digest())
|
||||
if err != nil {
|
||||
if !errdefs.IsAlreadyExists(err) {
|
||||
return exists, err
|
||||
}
|
||||
exists = true
|
||||
}
|
||||
err = writer.Close()
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// fetchBlob retrieves a blob from filesystem
|
||||
func (cache *Cache) fetchBlob(desc *ocispec.Descriptor) ([]byte, error) {
|
||||
reader, err := cache.ociStore.ReaderAt(ctx(cache.out, cache.debug), *desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
bytes := make([]byte, desc.Size)
|
||||
_, err = reader.ReadAt(bytes, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
48
vendor/helm.sh/helm/v3/internal/experimental/registry/cache_opts.go
vendored
Normal file
48
vendor/helm.sh/helm/v3/internal/experimental/registry/cache_opts.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// CacheOption allows specifying various settings configurable by the user for overriding the defaults
|
||||
// used when creating a new default cache
|
||||
CacheOption func(*Cache)
|
||||
)
|
||||
|
||||
// CacheOptDebug returns a function that sets the debug setting on cache options set
|
||||
func CacheOptDebug(debug bool) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.debug = debug
|
||||
}
|
||||
}
|
||||
|
||||
// CacheOptWriter returns a function that sets the writer setting on cache options set
|
||||
func CacheOptWriter(out io.Writer) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.out = out
|
||||
}
|
||||
}
|
||||
|
||||
// CacheOptRoot returns a function that sets the root directory setting on cache options set
|
||||
func CacheOptRoot(rootDir string) CacheOption {
|
||||
return func(cache *Cache) {
|
||||
cache.rootDir = rootDir
|
||||
}
|
||||
}
|
||||
336
vendor/helm.sh/helm/v3/internal/experimental/registry/client.go
vendored
Normal file
336
vendor/helm.sh/helm/v3/internal/experimental/registry/client.go
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
auth "github.com/deislabs/oras/pkg/auth/docker"
|
||||
"github.com/deislabs/oras/pkg/content"
|
||||
"github.com/deislabs/oras/pkg/oras"
|
||||
"github.com/gosuri/uitable"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/helmpath"
|
||||
)
|
||||
|
||||
const (
|
||||
// CredentialsFileBasename is the filename for auth credentials file
|
||||
CredentialsFileBasename = "config.json"
|
||||
)
|
||||
|
||||
type (
|
||||
// Client works with OCI-compliant registries and local Helm chart cache
|
||||
Client struct {
|
||||
debug bool
|
||||
// path to repository config file e.g. ~/.docker/config.json
|
||||
credentialsFile string
|
||||
out io.Writer
|
||||
authorizer *Authorizer
|
||||
resolver *Resolver
|
||||
cache *Cache
|
||||
}
|
||||
)
|
||||
|
||||
// NewClient returns a new registry client with config
|
||||
func NewClient(opts ...ClientOption) (*Client, error) {
|
||||
client := &Client{
|
||||
out: ioutil.Discard,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(client)
|
||||
}
|
||||
// set defaults if fields are missing
|
||||
if client.credentialsFile == "" {
|
||||
client.credentialsFile = helmpath.CachePath("registry", CredentialsFileBasename)
|
||||
}
|
||||
if client.authorizer == nil {
|
||||
authClient, err := auth.NewClient(client.credentialsFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.authorizer = &Authorizer{
|
||||
Client: authClient,
|
||||
}
|
||||
}
|
||||
if client.resolver == nil {
|
||||
resolver, err := client.authorizer.Resolver(context.Background(), http.DefaultClient, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.resolver = &Resolver{
|
||||
Resolver: resolver,
|
||||
}
|
||||
}
|
||||
if client.cache == nil {
|
||||
cache, err := NewCache(
|
||||
CacheOptDebug(client.debug),
|
||||
CacheOptWriter(client.out),
|
||||
CacheOptRoot(helmpath.CachePath("registry", CacheRootDir)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.cache = cache
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Login logs into a registry
|
||||
func (c *Client) Login(hostname string, username string, password string, insecure bool) error {
|
||||
err := c.authorizer.Login(ctx(c.out, c.debug), hostname, username, password, insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "Login succeeded\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logout logs out of a registry
|
||||
func (c *Client) Logout(hostname string) error {
|
||||
err := c.authorizer.Logout(ctx(c.out, c.debug), hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(c.out, "Logout succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushChart uploads a chart to a registry
|
||||
func (c *Client) PushChart(ref *Reference) error {
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", r.Name))
|
||||
}
|
||||
fmt.Fprintf(c.out, "The push refers to repository [%s]\n", r.Repo)
|
||||
c.printCacheRefSummary(r)
|
||||
layers := []ocispec.Descriptor{*r.ContentLayer}
|
||||
_, err = oras.Push(ctx(c.out, c.debug), c.resolver, r.Name, c.cache.Provider(), layers,
|
||||
oras.WithConfig(*r.Config), oras.WithNameValidation(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := ""
|
||||
numLayers := len(layers)
|
||||
if 1 < numLayers {
|
||||
s = "s"
|
||||
}
|
||||
fmt.Fprintf(c.out,
|
||||
"%s: pushed to remote (%d layer%s, %s total)\n", r.Tag, numLayers, s, byteCountBinary(r.Size))
|
||||
return nil
|
||||
}
|
||||
|
||||
// PullChart downloads a chart from a registry
|
||||
func (c *Client) PullChart(ref *Reference) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
if ref.Tag == "" {
|
||||
return buf, errors.New("tag explicitly required")
|
||||
}
|
||||
|
||||
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
|
||||
|
||||
store := content.NewMemoryStore()
|
||||
fullname := ref.FullName()
|
||||
_ = fullname
|
||||
_, layerDescriptors, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), store,
|
||||
oras.WithPullEmptyNameAllowed(),
|
||||
oras.WithAllowedMediaTypes(KnownMediaTypes()))
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
|
||||
numLayers := len(layerDescriptors)
|
||||
if numLayers < 1 {
|
||||
return buf, errors.New(
|
||||
fmt.Sprintf("manifest does not contain at least 1 layer (total: %d)", numLayers))
|
||||
}
|
||||
|
||||
var contentLayer *ocispec.Descriptor
|
||||
for _, layer := range layerDescriptors {
|
||||
layer := layer
|
||||
switch layer.MediaType {
|
||||
case HelmChartContentLayerMediaType:
|
||||
contentLayer = &layer
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if contentLayer == nil {
|
||||
return buf, errors.New(
|
||||
fmt.Sprintf("manifest does not contain a layer with mediatype %s",
|
||||
HelmChartContentLayerMediaType))
|
||||
}
|
||||
|
||||
_, b, ok := store.Get(*contentLayer)
|
||||
if !ok {
|
||||
return buf, errors.Errorf("Unable to retrieve blob with digest %s", contentLayer.Digest)
|
||||
}
|
||||
|
||||
buf = bytes.NewBuffer(b)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// PullChartToCache pulls a chart from an OCI Registry to the Registry Cache.
|
||||
// This function is needed for `helm chart pull`, which is experimental and will be deprecated soon.
|
||||
// Likewise, the Registry cache will soon be deprecated as will this function.
|
||||
func (c *Client) PullChartToCache(ref *Reference) error {
|
||||
if ref.Tag == "" {
|
||||
return errors.New("tag explicitly required")
|
||||
}
|
||||
existing, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo)
|
||||
manifest, _, err := oras.Pull(ctx(c.out, c.debug), c.resolver, ref.FullName(), c.cache.Ingester(),
|
||||
oras.WithPullEmptyNameAllowed(),
|
||||
oras.WithAllowedMediaTypes(KnownMediaTypes()),
|
||||
oras.WithContentProvideIngester(c.cache.ProvideIngester()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.cache.AddManifest(ref, &manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", r.Name))
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
if !existing.Exists {
|
||||
fmt.Fprintf(c.out, "Status: Downloaded newer chart for %s\n", ref.FullName())
|
||||
} else {
|
||||
fmt.Fprintf(c.out, "Status: Chart is up to date for %s\n", ref.FullName())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveChart stores a copy of chart in local cache
|
||||
func (c *Client) SaveChart(ch *chart.Chart, ref *Reference) error {
|
||||
r, err := c.cache.StoreReference(ref, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
err = c.cache.AddManifest(ref, r.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: saved\n", r.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadChart retrieves a chart object by reference
|
||||
func (c *Client) LoadChart(ref *Reference) (*chart.Chart, error) {
|
||||
r, err := c.cache.FetchReference(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !r.Exists {
|
||||
return nil, errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName()))
|
||||
}
|
||||
c.printCacheRefSummary(r)
|
||||
return r.Chart, nil
|
||||
}
|
||||
|
||||
// RemoveChart deletes a locally saved chart
|
||||
func (c *Client) RemoveChart(ref *Reference) error {
|
||||
r, err := c.cache.DeleteReference(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Exists {
|
||||
return errors.New(fmt.Sprintf("Chart not found: %s", ref.FullName()))
|
||||
}
|
||||
fmt.Fprintf(c.out, "%s: removed\n", r.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintChartTable prints a list of locally stored charts
|
||||
func (c *Client) PrintChartTable() error {
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = 60
|
||||
table.AddRow("REF", "NAME", "VERSION", "DIGEST", "SIZE", "CREATED")
|
||||
rows, err := c.getChartTableRows()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
table.AddRow(row...)
|
||||
}
|
||||
fmt.Fprintln(c.out, table.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// printCacheRefSummary prints out chart ref summary
|
||||
func (c *Client) printCacheRefSummary(r *CacheRefSummary) {
|
||||
fmt.Fprintf(c.out, "ref: %s\n", r.Name)
|
||||
fmt.Fprintf(c.out, "digest: %s\n", r.Manifest.Digest.Hex())
|
||||
fmt.Fprintf(c.out, "size: %s\n", byteCountBinary(r.Size))
|
||||
fmt.Fprintf(c.out, "name: %s\n", r.Chart.Metadata.Name)
|
||||
fmt.Fprintf(c.out, "version: %s\n", r.Chart.Metadata.Version)
|
||||
}
|
||||
|
||||
// getChartTableRows returns rows in uitable-friendly format
|
||||
func (c *Client) getChartTableRows() ([][]interface{}, error) {
|
||||
rr, err := c.cache.ListReferences()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refsMap := map[string]map[string]string{}
|
||||
for _, r := range rr {
|
||||
refsMap[r.Name] = map[string]string{
|
||||
"name": r.Chart.Metadata.Name,
|
||||
"version": r.Chart.Metadata.Version,
|
||||
"digest": shortDigest(r.Manifest.Digest.Hex()),
|
||||
"size": byteCountBinary(r.Size),
|
||||
"created": timeAgo(r.CreatedAt),
|
||||
}
|
||||
}
|
||||
// Sort and convert to format expected by uitable
|
||||
rows := make([][]interface{}, len(refsMap))
|
||||
keys := make([]string, 0, len(refsMap))
|
||||
for key := range refsMap {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, key := range keys {
|
||||
rows[i] = make([]interface{}, 6)
|
||||
rows[i][0] = key
|
||||
ref := refsMap[key]
|
||||
for j, k := range []string{"name", "version", "digest", "size", "created"} {
|
||||
rows[i][j+1] = ref[k]
|
||||
}
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
69
vendor/helm.sh/helm/v3/internal/experimental/registry/client_opts.go
vendored
Normal file
69
vendor/helm.sh/helm/v3/internal/experimental/registry/client_opts.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// ClientOption allows specifying various settings configurable by the user for overriding the defaults
|
||||
// used when creating a new default client
|
||||
ClientOption func(*Client)
|
||||
)
|
||||
|
||||
// ClientOptDebug returns a function that sets the debug setting on client options set
|
||||
func ClientOptDebug(debug bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.debug = debug
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptWriter returns a function that sets the writer setting on client options set
|
||||
func ClientOptWriter(out io.Writer) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.out = out
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptResolver returns a function that sets the resolver setting on client options set
|
||||
func ClientOptResolver(resolver *Resolver) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.resolver = resolver
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptAuthorizer returns a function that sets the authorizer setting on client options set
|
||||
func ClientOptAuthorizer(authorizer *Authorizer) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.authorizer = authorizer
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptCache returns a function that sets the cache setting on a client options set
|
||||
func ClientOptCache(cache *Cache) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.cache = cache
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptCredentialsFile returns a function that sets the cache setting on a client options set
|
||||
func ClientOptCredentialsFile(credentialsFile string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.credentialsFile = credentialsFile
|
||||
}
|
||||
}
|
||||
33
vendor/helm.sh/helm/v3/internal/experimental/registry/constants.go
vendored
Normal file
33
vendor/helm.sh/helm/v3/internal/experimental/registry/constants.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
const (
|
||||
// HelmChartConfigMediaType is the reserved media type for the Helm chart manifest config
|
||||
HelmChartConfigMediaType = "application/vnd.cncf.helm.config.v1+json"
|
||||
|
||||
// HelmChartContentLayerMediaType is the reserved media type for Helm chart package content
|
||||
HelmChartContentLayerMediaType = "application/tar+gzip"
|
||||
)
|
||||
|
||||
// KnownMediaTypes returns a list of layer mediaTypes that the Helm client knows about
|
||||
func KnownMediaTypes() []string {
|
||||
return []string{
|
||||
HelmChartConfigMediaType,
|
||||
HelmChartContentLayerMediaType,
|
||||
}
|
||||
}
|
||||
146
vendor/helm.sh/helm/v3/internal/experimental/registry/reference.go
vendored
Normal file
146
vendor/helm.sh/helm/v3/internal/experimental/registry/reference.go
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
validPortRegEx = regexp.MustCompile(`^([1-9]\d{0,3}|0|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$`) // adapted from https://stackoverflow.com/a/12968117
|
||||
// TODO: Currently we don't support digests, so we are only splitting on the
|
||||
// colon. However, when we add support for digests, we'll need to use the
|
||||
// regexp anyway to split on both colons and @, so leaving it like this for
|
||||
// now
|
||||
referenceDelimiter = regexp.MustCompile(`[:]`)
|
||||
errEmptyRepo = errors.New("parsed repo was empty")
|
||||
errTooManyColons = errors.New("ref may only contain a single colon character (:) unless specifying a port number")
|
||||
)
|
||||
|
||||
type (
|
||||
// Reference defines the main components of a reference specification
|
||||
Reference struct {
|
||||
Tag string
|
||||
Repo string
|
||||
}
|
||||
)
|
||||
|
||||
// ParseReference converts a string to a Reference
|
||||
func ParseReference(s string) (*Reference, error) {
|
||||
if s == "" {
|
||||
return nil, errEmptyRepo
|
||||
}
|
||||
// Split the components of the string on the colon or @, if it is more than 3,
|
||||
// immediately return an error. Other validation will be performed later in
|
||||
// the function
|
||||
splitComponents := fixSplitComponents(referenceDelimiter.Split(s, -1))
|
||||
if len(splitComponents) > 3 {
|
||||
return nil, errTooManyColons
|
||||
}
|
||||
|
||||
var ref *Reference
|
||||
switch len(splitComponents) {
|
||||
case 1:
|
||||
ref = &Reference{Repo: splitComponents[0]}
|
||||
case 2:
|
||||
ref = &Reference{Repo: splitComponents[0], Tag: splitComponents[1]}
|
||||
case 3:
|
||||
ref = &Reference{Repo: strings.Join(splitComponents[:2], ":"), Tag: splitComponents[2]}
|
||||
}
|
||||
|
||||
// ensure the reference is valid
|
||||
err := ref.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// FullName the full name of a reference (repo:tag)
|
||||
func (ref *Reference) FullName() string {
|
||||
if ref.Tag == "" {
|
||||
return ref.Repo
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ref.Repo, ref.Tag)
|
||||
}
|
||||
|
||||
// validate makes sure the ref meets our criteria
|
||||
func (ref *Reference) validate() error {
|
||||
|
||||
err := ref.validateRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ref.validateNumColons()
|
||||
}
|
||||
|
||||
// validateRepo checks that the Repo field is non-empty
|
||||
func (ref *Reference) validateRepo() error {
|
||||
if ref.Repo == "" {
|
||||
return errEmptyRepo
|
||||
}
|
||||
// Makes sure the repo results in a parsable URL (similar to what is done
|
||||
// with containerd reference parsing)
|
||||
_, err := url.Parse("//" + ref.Repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// validateNumColons ensures the ref only contains a single colon character (:)
|
||||
// (or potentially two, there might be a port number specified i.e. :5000)
|
||||
func (ref *Reference) validateNumColons() error {
|
||||
if strings.Contains(ref.Tag, ":") {
|
||||
return errTooManyColons
|
||||
}
|
||||
parts := strings.Split(ref.Repo, ":")
|
||||
lastIndex := len(parts) - 1
|
||||
if 1 < lastIndex {
|
||||
return errTooManyColons
|
||||
}
|
||||
if 0 < lastIndex {
|
||||
port := strings.Split(parts[lastIndex], "/")[0]
|
||||
if !isValidPort(port) {
|
||||
return errTooManyColons
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidPort returns whether or not a string looks like a valid port
|
||||
func isValidPort(s string) bool {
|
||||
return validPortRegEx.MatchString(s)
|
||||
}
|
||||
|
||||
// fixSplitComponents this will modify reference parts based on presence of port
|
||||
// Example: {localhost, 5000/x/y/z, 0.1.0} => {localhost:5000/x/y/z, 0.1.0}
|
||||
func fixSplitComponents(c []string) []string {
|
||||
if len(c) <= 1 {
|
||||
return c
|
||||
}
|
||||
possiblePortParts := strings.Split(c[1], "/")
|
||||
if _, err := strconv.Atoi(possiblePortParts[0]); err == nil {
|
||||
components := []string{strings.Join(c[:2], ":")}
|
||||
components = append(components, c[2:]...)
|
||||
return components
|
||||
}
|
||||
return c
|
||||
}
|
||||
28
vendor/helm.sh/helm/v3/internal/experimental/registry/resolver.go
vendored
Normal file
28
vendor/helm.sh/helm/v3/internal/experimental/registry/resolver.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/remotes"
|
||||
)
|
||||
|
||||
type (
|
||||
// Resolver provides remotes based on a locator
|
||||
Resolver struct {
|
||||
remotes.Resolver
|
||||
}
|
||||
)
|
||||
66
vendor/helm.sh/helm/v3/internal/experimental/registry/util.go
vendored
Normal file
66
vendor/helm.sh/helm/v3/internal/experimental/registry/util.go
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
orascontext "github.com/deislabs/oras/pkg/context"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// byteCountBinary produces a human-readable file size
|
||||
func byteCountBinary(b int64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
// shortDigest returns first 7 characters of a sha256 digest
|
||||
func shortDigest(digest string) string {
|
||||
if len(digest) == 64 {
|
||||
return digest[:7]
|
||||
}
|
||||
return digest
|
||||
}
|
||||
|
||||
// timeAgo returns a human-readable timestamp representing time that has passed
|
||||
func timeAgo(t time.Time) string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(t))
|
||||
}
|
||||
|
||||
// ctx retrieves a fresh context.
|
||||
// disable verbose logging coming from ORAS (unless debug is enabled)
|
||||
func ctx(out io.Writer, debug bool) context.Context {
|
||||
if !debug {
|
||||
return orascontext.Background()
|
||||
}
|
||||
ctx := orascontext.WithLoggerFromWriter(context.Background(), out)
|
||||
orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel)
|
||||
return ctx
|
||||
}
|
||||
@@ -29,8 +29,7 @@ var (
|
||||
//
|
||||
// Increment major number for new feature additions and behavioral changes.
|
||||
// Increment minor number for bug fixes and performance enhancements.
|
||||
// Increment patch number for critical fixes to existing releases.
|
||||
version = "v3.3"
|
||||
version = "v3.5"
|
||||
|
||||
// metadata is extra build time data
|
||||
metadata = ""
|
||||
|
||||
4
vendor/helm.sh/helm/v3/pkg/chart/chart.go
vendored
4
vendor/helm.sh/helm/v3/pkg/chart/chart.go
vendored
@@ -17,6 +17,7 @@ package chart
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -26,6 +27,9 @@ const APIVersionV1 = "v1"
|
||||
// APIVersionV2 is the API version number for version 2.
|
||||
const APIVersionV2 = "v2"
|
||||
|
||||
// aliasNameFormat defines the characters that are legal in an alias name.
|
||||
var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
|
||||
|
||||
// Chart is a helm package that contains metadata, a default config, zero or more
|
||||
// optionally parameterizable templates, and zero or more charts (dependencies).
|
||||
type Chart struct {
|
||||
|
||||
7
vendor/helm.sh/helm/v3/pkg/chart/errors.go
vendored
7
vendor/helm.sh/helm/v3/pkg/chart/errors.go
vendored
@@ -15,9 +15,16 @@ limitations under the License.
|
||||
|
||||
package chart
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ValidationError represents a data validation error.
|
||||
type ValidationError string
|
||||
|
||||
func (v ValidationError) Error() string {
|
||||
return "validation: " + string(v)
|
||||
}
|
||||
|
||||
// ValidationErrorf takes a message and formatting options and creates a ValidationError
|
||||
func ValidationErrorf(msg string, args ...interface{}) ValidationError {
|
||||
return ValidationError(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
19
vendor/helm.sh/helm/v3/pkg/chart/loader/load.go
vendored
19
vendor/helm.sh/helm/v3/pkg/chart/loader/load.go
vendored
@@ -73,10 +73,11 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
|
||||
c := new(chart.Chart)
|
||||
subcharts := make(map[string][]*BufferedFile)
|
||||
|
||||
// do not rely on assumed ordering of files in the chart and crash
|
||||
// if Chart.yaml was not coming early enough to initialize metadata
|
||||
for _, f := range files {
|
||||
c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data})
|
||||
switch {
|
||||
case f.Name == "Chart.yaml":
|
||||
if f.Name == "Chart.yaml" {
|
||||
if c.Metadata == nil {
|
||||
c.Metadata = new(chart.Metadata)
|
||||
}
|
||||
@@ -89,6 +90,13 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
|
||||
if c.Metadata.APIVersion == "" {
|
||||
c.Metadata.APIVersion = chart.APIVersionV1
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range files {
|
||||
switch {
|
||||
case f.Name == "Chart.yaml":
|
||||
// already processed
|
||||
continue
|
||||
case f.Name == "Chart.lock":
|
||||
c.Lock = new(chart.Lock)
|
||||
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
|
||||
@@ -123,6 +131,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
|
||||
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
|
||||
return c, errors.Wrap(err, "cannot load requirements.lock")
|
||||
}
|
||||
if c.Metadata == nil {
|
||||
c.Metadata = new(chart.Metadata)
|
||||
}
|
||||
if c.Metadata.APIVersion == chart.APIVersionV1 {
|
||||
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
|
||||
}
|
||||
@@ -143,6 +154,10 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Metadata == nil {
|
||||
return c, errors.New("Chart.yaml file is missing")
|
||||
}
|
||||
|
||||
if err := c.Validate(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
19
vendor/helm.sh/helm/v3/pkg/chart/metadata.go
vendored
19
vendor/helm.sh/helm/v3/pkg/chart/metadata.go
vendored
@@ -81,6 +81,15 @@ func (md *Metadata) Validate() error {
|
||||
if !isValidChartType(md.Type) {
|
||||
return ValidationError("chart.metadata.type must be application or library")
|
||||
}
|
||||
|
||||
// Aliases need to be validated here to make sure that the alias name does
|
||||
// not contain any illegal characters.
|
||||
for _, dependency := range md.Dependencies {
|
||||
if err := validateDependency(dependency); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO validate valid semver here?
|
||||
return nil
|
||||
}
|
||||
@@ -92,3 +101,13 @@ func isValidChartType(in string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateDependency checks for common problems with the dependency datastructure in
|
||||
// the chart. This check must be done at load time before the dependency's charts are
|
||||
// loaded.
|
||||
func validateDependency(dep *Dependency) error {
|
||||
if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) {
|
||||
return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
104
vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go
vendored
Normal file
104
vendor/helm.sh/helm/v3/pkg/chartutil/capabilities.go
vendored
Normal 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 chartutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
|
||||
helmversion "helm.sh/helm/v3/internal/version"
|
||||
)
|
||||
|
||||
const (
|
||||
k8sVersionMajor = 1
|
||||
k8sVersionMinor = 20
|
||||
)
|
||||
|
||||
var (
|
||||
// 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),
|
||||
},
|
||||
APIVersions: DefaultVersionSet,
|
||||
HelmVersion: helmversion.Get(),
|
||||
}
|
||||
)
|
||||
|
||||
// Capabilities describes the capabilities of the Kubernetes cluster.
|
||||
type Capabilities struct {
|
||||
// KubeVersion is the Kubernetes version.
|
||||
KubeVersion KubeVersion
|
||||
// APIversions are supported Kubernetes API versions.
|
||||
APIVersions VersionSet
|
||||
// HelmVersion is the build information for this helm version
|
||||
HelmVersion helmversion.BuildInfo
|
||||
}
|
||||
|
||||
// KubeVersion is the Kubernetes version.
|
||||
type KubeVersion struct {
|
||||
Version string // Kubernetes version
|
||||
Major string // Kubernetes major version
|
||||
Minor string // Kubernetes minor version
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (kv *KubeVersion) String() string { return kv.Version }
|
||||
|
||||
// GitVersion returns the Kubernetes version string.
|
||||
//
|
||||
// Deprecated: use KubeVersion.Version.
|
||||
func (kv *KubeVersion) GitVersion() string { return kv.Version }
|
||||
|
||||
// VersionSet is a set of Kubernetes API versions.
|
||||
type VersionSet []string
|
||||
|
||||
// Has returns true if the version string is in the set.
|
||||
//
|
||||
// vs.Has("apps/v1")
|
||||
func (v VersionSet) Has(apiVersion string) bool {
|
||||
for _, x := range v {
|
||||
if x == apiVersion {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func allKnownVersions() VersionSet {
|
||||
// We should register the built in extension APIs as well so CRDs are
|
||||
// supported in the default version set. This has caused problems with `helm
|
||||
// template` in the past, so let's be safe
|
||||
apiextensionsv1beta1.AddToScheme(scheme.Scheme)
|
||||
apiextensionsv1.AddToScheme(scheme.Scheme)
|
||||
|
||||
groups := scheme.Scheme.PrioritizedVersionsAllGroups()
|
||||
vs := make(VersionSet, 0, len(groups))
|
||||
for _, gv := range groups {
|
||||
vs = append(vs, gv.String())
|
||||
}
|
||||
return vs
|
||||
}
|
||||
93
vendor/helm.sh/helm/v3/pkg/chartutil/chartfile.go
vendored
Normal file
93
vendor/helm.sh/helm/v3/pkg/chartutil/chartfile.go
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// LoadChartfile loads a Chart.yaml file into a *chart.Metadata.
|
||||
func LoadChartfile(filename string) (*chart.Metadata, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y := new(chart.Metadata)
|
||||
err = yaml.Unmarshal(b, y)
|
||||
return y, err
|
||||
}
|
||||
|
||||
// SaveChartfile saves the given metadata as a Chart.yaml file at the given path.
|
||||
//
|
||||
// 'filename' should be the complete path and filename ('foo/Chart.yaml')
|
||||
func SaveChartfile(filename string, cf *chart.Metadata) error {
|
||||
// Pull out the dependencies of a v1 Chart, since there's no way
|
||||
// to tell the serializer to skip a field for just this use case
|
||||
savedDependencies := cf.Dependencies
|
||||
if cf.APIVersion == chart.APIVersionV1 {
|
||||
cf.Dependencies = nil
|
||||
}
|
||||
out, err := yaml.Marshal(cf)
|
||||
if cf.APIVersion == chart.APIVersionV1 {
|
||||
cf.Dependencies = savedDependencies
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filename, out, 0644)
|
||||
}
|
||||
|
||||
// IsChartDir validate a chart directory.
|
||||
//
|
||||
// Checks for a valid Chart.yaml.
|
||||
func IsChartDir(dirName string) (bool, error) {
|
||||
if fi, err := os.Stat(dirName); err != nil {
|
||||
return false, err
|
||||
} else if !fi.IsDir() {
|
||||
return false, errors.Errorf("%q is not a directory", dirName)
|
||||
}
|
||||
|
||||
chartYaml := filepath.Join(dirName, ChartfileName)
|
||||
if _, err := os.Stat(chartYaml); os.IsNotExist(err) {
|
||||
return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName)
|
||||
}
|
||||
|
||||
chartYamlContent, err := ioutil.ReadFile(chartYaml)
|
||||
if err != nil {
|
||||
return false, errors.Errorf("cannot read %s in directory %q", ChartfileName, dirName)
|
||||
}
|
||||
|
||||
chartContent := new(chart.Metadata)
|
||||
if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if chartContent == nil {
|
||||
return false, errors.Errorf("chart metadata (%s) missing", ChartfileName)
|
||||
}
|
||||
if chartContent.Name == "" {
|
||||
return false, errors.Errorf("invalid chart (%s): name must not be empty", ChartfileName)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
207
vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go
vendored
Normal file
207
vendor/helm.sh/helm/v3/pkg/chartutil/coalesce.go
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// CoalesceValues coalesces all of the values in a chart (and its subcharts).
|
||||
//
|
||||
// Values are coalesced together using the following rules:
|
||||
//
|
||||
// - Values in a higher level chart always override values in a lower-level
|
||||
// dependency chart
|
||||
// - Scalar values and arrays are replaced, maps are merged
|
||||
// - A chart has access to all of the variables for it, as well as all of
|
||||
// the values destined for its dependencies.
|
||||
func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) {
|
||||
v, err := copystructure.Copy(vals)
|
||||
if err != nil {
|
||||
return vals, err
|
||||
}
|
||||
|
||||
valsCopy := v.(map[string]interface{})
|
||||
// if we have an empty map, make sure it is initialized
|
||||
if valsCopy == nil {
|
||||
valsCopy = make(map[string]interface{})
|
||||
}
|
||||
return coalesce(chrt, valsCopy)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// coalesceDeps coalesces the dependencies of the given chart.
|
||||
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (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.
|
||||
dest[subchart.Name()] = make(map[string]interface{})
|
||||
} else if !istable(c) {
|
||||
return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), c)
|
||||
}
|
||||
if dv, ok := dest[subchart.Name()]; ok {
|
||||
dvmap := dv.(map[string]interface{})
|
||||
|
||||
// Get globals out of dest and merge them into dvmap.
|
||||
coalesceGlobals(dvmap, dest)
|
||||
|
||||
// Now coalesce the rest of the values.
|
||||
var err error
|
||||
dest[subchart.Name()], err = coalesce(subchart, dvmap)
|
||||
if err != nil {
|
||||
return dest, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// coalesceGlobals copies the globals out of src and merges them into dest.
|
||||
//
|
||||
// For convenience, returns dest.
|
||||
func coalesceGlobals(dest, src map[string]interface{}) {
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
|
||||
// reverses that decision. It may somehow be possible to introduce a loop
|
||||
// here, but I haven't found a way. So for the time being, let's allow
|
||||
// tables in globals.
|
||||
for key, val := range sg {
|
||||
if istable(val) {
|
||||
vv := copyMap(val.(map[string]interface{}))
|
||||
if destv, ok := dg[key]; !ok {
|
||||
// Here there is no merge. We're just adding.
|
||||
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)
|
||||
} else {
|
||||
// Basically, we reverse order of coalesce here to merge
|
||||
// top-down.
|
||||
CoalesceTables(vv, destvmap)
|
||||
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
|
||||
}
|
||||
// TODO: Do we need to do any additional checking on the value?
|
||||
dg[key] = val
|
||||
}
|
||||
dest[GlobalKey] = dg
|
||||
}
|
||||
|
||||
func copyMap(src map[string]interface{}) map[string]interface{} {
|
||||
m := make(map[string]interface{}, len(src))
|
||||
for k, v := range src {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
for key, val := range c.Values {
|
||||
if value, ok := v[key]; ok {
|
||||
if value == nil {
|
||||
// When the YAML value is null, we remove the value's key.
|
||||
// This allows Helm's various sources of values (value files or --set) to
|
||||
// remove incompatible keys from any previous chart, file, or set values.
|
||||
delete(v, key)
|
||||
} else if dest, ok := value.(map[string]interface{}); ok {
|
||||
// if v[key] is a table, merge nv's val table into v[key].
|
||||
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
|
||||
if val != nil {
|
||||
log.Printf("warning: skipped value for %s: Not a table.", key)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 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.
|
||||
v[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CoalesceTables merges a source map into a destination map.
|
||||
//
|
||||
// dest is considered authoritative.
|
||||
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
|
||||
// When --reuse-values is set but there are no modifications yet, return new values
|
||||
if src == nil {
|
||||
return dst
|
||||
}
|
||||
if dst == nil {
|
||||
return src
|
||||
}
|
||||
// Because dest has higher precedence than src, dest values override src
|
||||
// values.
|
||||
for key, val := range src {
|
||||
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{}))
|
||||
} else {
|
||||
log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val)
|
||||
}
|
||||
} else if istable(dv) && val != nil {
|
||||
log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
34
vendor/helm.sh/helm/v3/pkg/chartutil/compatible.go
vendored
Normal file
34
vendor/helm.sh/helm/v3/pkg/chartutil/compatible.go
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import "github.com/Masterminds/semver/v3"
|
||||
|
||||
// IsCompatibleRange compares a version to a constraint.
|
||||
// It returns true if the version matches the constraint, and false in all other cases.
|
||||
func IsCompatibleRange(constraint, ver string) bool {
|
||||
sv, err := semver.NewVersion(ver)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(constraint)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return c.Check(sv)
|
||||
}
|
||||
664
vendor/helm.sh/helm/v3/pkg/chartutil/create.go
vendored
Normal file
664
vendor/helm.sh/helm/v3/pkg/chartutil/create.go
vendored
Normal file
@@ -0,0 +1,664 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// chartName is a regular expression for testing the supplied name of a chart.
|
||||
// This regular expression is probably stricter than it needs to be. We can relax it
|
||||
// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be
|
||||
// problematic.
|
||||
var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
|
||||
|
||||
const (
|
||||
// ChartfileName is the default Chart file name.
|
||||
ChartfileName = "Chart.yaml"
|
||||
// ValuesfileName is the default values file name.
|
||||
ValuesfileName = "values.yaml"
|
||||
// SchemafileName is the default values schema file name.
|
||||
SchemafileName = "values.schema.json"
|
||||
// TemplatesDir is the relative directory name for templates.
|
||||
TemplatesDir = "templates"
|
||||
// ChartsDir is the relative directory name for charts dependencies.
|
||||
ChartsDir = "charts"
|
||||
// TemplatesTestsDir is the relative directory name for tests.
|
||||
TemplatesTestsDir = TemplatesDir + sep + "tests"
|
||||
// IgnorefileName is the name of the Helm ignore file.
|
||||
IgnorefileName = ".helmignore"
|
||||
// IngressFileName is the name of the example ingress file.
|
||||
IngressFileName = TemplatesDir + sep + "ingress.yaml"
|
||||
// DeploymentName is the name of the example deployment file.
|
||||
DeploymentName = TemplatesDir + sep + "deployment.yaml"
|
||||
// ServiceName is the name of the example service file.
|
||||
ServiceName = TemplatesDir + sep + "service.yaml"
|
||||
// ServiceAccountName is the name of the example serviceaccount file.
|
||||
ServiceAccountName = TemplatesDir + sep + "serviceaccount.yaml"
|
||||
// HorizontalPodAutoscalerName is the name of the example hpa file.
|
||||
HorizontalPodAutoscalerName = TemplatesDir + sep + "hpa.yaml"
|
||||
// NotesName is the name of the example NOTES.txt file.
|
||||
NotesName = TemplatesDir + sep + "NOTES.txt"
|
||||
// HelpersName is the name of the example helpers file.
|
||||
HelpersName = TemplatesDir + sep + "_helpers.tpl"
|
||||
// TestConnectionName is the name of the example test file.
|
||||
TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml"
|
||||
)
|
||||
|
||||
// maxChartNameLength is lower than the limits we know of with certain file systems,
|
||||
// and with certain Kubernetes fields.
|
||||
const maxChartNameLength = 250
|
||||
|
||||
const sep = string(filepath.Separator)
|
||||
|
||||
const defaultChartfile = `apiVersion: v2
|
||||
name: %s
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
`
|
||||
|
||||
const defaultValues = `# Default values for %s.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
`
|
||||
|
||||
const defaultIgnore = `# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
`
|
||||
|
||||
const defaultIngress = `{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "<CHARTNAME>.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultDeployment = `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "<CHARTNAME>.fullname" . }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "<CHARTNAME>.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultService = `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "<CHARTNAME>.fullname" . }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "<CHARTNAME>.selectorLabels" . | nindent 4 }}
|
||||
`
|
||||
|
||||
const defaultServiceAccount = `{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "<CHARTNAME>.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "<CHARTNAME>.fullname" . }}
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "<CHARTNAME>.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultNotes = `1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "<CHARTNAME>.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "<CHARTNAME>.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "<CHARTNAME>.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "<CHARTNAME>.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultHelpers = `{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.labels" -}}
|
||||
helm.sh/chart: {{ include "<CHARTNAME>.chart" . }}
|
||||
{{ include "<CHARTNAME>.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "<CHARTNAME>.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "<CHARTNAME>.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
const defaultTestConnection = `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "<CHARTNAME>.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "<CHARTNAME>.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
`
|
||||
|
||||
// Stderr is an io.Writer to which error messages can be written
|
||||
//
|
||||
// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward
|
||||
// compatibility.
|
||||
var Stderr io.Writer = os.Stderr
|
||||
|
||||
// CreateFrom creates a new chart, but scaffolds it from the src chart.
|
||||
func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
|
||||
schart, err := loader.Load(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not load %s", src)
|
||||
}
|
||||
|
||||
schart.Metadata = chartfile
|
||||
|
||||
var updatedTemplates []*chart.File
|
||||
|
||||
for _, template := range schart.Templates {
|
||||
newData := transform(string(template.Data), schart.Name())
|
||||
updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData})
|
||||
}
|
||||
|
||||
schart.Templates = updatedTemplates
|
||||
b, err := yaml.Marshal(schart.Values)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading values file")
|
||||
}
|
||||
|
||||
var m map[string]interface{}
|
||||
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
|
||||
return errors.Wrap(err, "transforming values file")
|
||||
}
|
||||
schart.Values = m
|
||||
|
||||
// SaveDir looks for the file values.yaml when saving rather than the values
|
||||
// key in order to preserve the comments in the YAML. The name placeholder
|
||||
// needs to be replaced on that file.
|
||||
for _, f := range schart.Raw {
|
||||
if f.Name == ValuesfileName {
|
||||
f.Data = transform(string(f.Data), schart.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return SaveDir(schart, dest)
|
||||
}
|
||||
|
||||
// Create creates a new chart in a directory.
|
||||
//
|
||||
// Inside of dir, this will create a directory based on the name of
|
||||
// chartfile.Name. It will then write the Chart.yaml into this directory and
|
||||
// create the (empty) appropriate directories.
|
||||
//
|
||||
// The returned string will point to the newly created directory. It will be
|
||||
// an absolute path, even if the provided base directory was relative.
|
||||
//
|
||||
// If dir does not exist, this will return an error.
|
||||
// If Chart.yaml or any directories cannot be created, this will return an
|
||||
// error. In such a case, this will attempt to clean up by removing the
|
||||
// new chart directory.
|
||||
func Create(name, dir string) (string, error) {
|
||||
|
||||
// Sanity-check the name of a chart so user doesn't create one that causes problems.
|
||||
if err := validateChartName(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
if fi, err := os.Stat(path); err != nil {
|
||||
return path, err
|
||||
} else if !fi.IsDir() {
|
||||
return path, errors.Errorf("no such directory %s", path)
|
||||
}
|
||||
|
||||
cdir := filepath.Join(path, name)
|
||||
if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
|
||||
return cdir, errors.Errorf("file %s already exists and is not a directory", cdir)
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
path string
|
||||
content []byte
|
||||
}{
|
||||
{
|
||||
// Chart.yaml
|
||||
path: filepath.Join(cdir, ChartfileName),
|
||||
content: []byte(fmt.Sprintf(defaultChartfile, name)),
|
||||
},
|
||||
{
|
||||
// values.yaml
|
||||
path: filepath.Join(cdir, ValuesfileName),
|
||||
content: []byte(fmt.Sprintf(defaultValues, name)),
|
||||
},
|
||||
{
|
||||
// .helmignore
|
||||
path: filepath.Join(cdir, IgnorefileName),
|
||||
content: []byte(defaultIgnore),
|
||||
},
|
||||
{
|
||||
// ingress.yaml
|
||||
path: filepath.Join(cdir, IngressFileName),
|
||||
content: transform(defaultIngress, name),
|
||||
},
|
||||
{
|
||||
// deployment.yaml
|
||||
path: filepath.Join(cdir, DeploymentName),
|
||||
content: transform(defaultDeployment, name),
|
||||
},
|
||||
{
|
||||
// service.yaml
|
||||
path: filepath.Join(cdir, ServiceName),
|
||||
content: transform(defaultService, name),
|
||||
},
|
||||
{
|
||||
// serviceaccount.yaml
|
||||
path: filepath.Join(cdir, ServiceAccountName),
|
||||
content: transform(defaultServiceAccount, name),
|
||||
},
|
||||
{
|
||||
// hpa.yaml
|
||||
path: filepath.Join(cdir, HorizontalPodAutoscalerName),
|
||||
content: transform(defaultHorizontalPodAutoscaler, name),
|
||||
},
|
||||
{
|
||||
// NOTES.txt
|
||||
path: filepath.Join(cdir, NotesName),
|
||||
content: transform(defaultNotes, name),
|
||||
},
|
||||
{
|
||||
// _helpers.tpl
|
||||
path: filepath.Join(cdir, HelpersName),
|
||||
content: transform(defaultHelpers, name),
|
||||
},
|
||||
{
|
||||
// test-connection.yaml
|
||||
path: filepath.Join(cdir, TestConnectionName),
|
||||
content: transform(defaultTestConnection, name),
|
||||
},
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if _, err := os.Stat(file.path); err == nil {
|
||||
// There is no handle to a preferred output stream here.
|
||||
fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path)
|
||||
}
|
||||
if err := writeFile(file.path, file.content); err != nil {
|
||||
return cdir, err
|
||||
}
|
||||
}
|
||||
// Need to add the ChartsDir explicitly as it does not contain any file OOTB
|
||||
if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil {
|
||||
return cdir, err
|
||||
}
|
||||
return cdir, nil
|
||||
}
|
||||
|
||||
// transform performs a string replacement of the specified source for
|
||||
// a given key with the replacement string
|
||||
func transform(src, replacement string) []byte {
|
||||
return []byte(strings.ReplaceAll(src, "<CHARTNAME>", replacement))
|
||||
}
|
||||
|
||||
func writeFile(name string, content []byte) error {
|
||||
if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(name, content, 0644)
|
||||
}
|
||||
|
||||
func validateChartName(name string) error {
|
||||
if name == "" || len(name) > maxChartNameLength {
|
||||
return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength)
|
||||
}
|
||||
if !chartName.MatchString(name) {
|
||||
return fmt.Errorf("chart name must match the regular expression %q", chartName.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
285
vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go
vendored
Normal file
285
vendor/helm.sh/helm/v3/pkg/chartutil/dependencies.go
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// ProcessDependencies checks through this chart's dependencies, processing accordingly.
|
||||
func ProcessDependencies(c *chart.Chart, v Values) error {
|
||||
if err := processDependencyEnabled(c, v, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return processDependencyImportValues(c)
|
||||
}
|
||||
|
||||
// processDependencyConditions disables charts based on condition path value in values
|
||||
func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) {
|
||||
if reqs == nil {
|
||||
return
|
||||
}
|
||||
for _, r := range reqs {
|
||||
for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") {
|
||||
if len(c) > 0 {
|
||||
// retrieve value
|
||||
vv, err := cvals.PathValue(cpath + c)
|
||||
if err == nil {
|
||||
// if not bool, warn
|
||||
if bv, ok := vv.(bool); ok {
|
||||
r.Enabled = bv
|
||||
break
|
||||
} else {
|
||||
log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
|
||||
}
|
||||
} else if _, ok := err.(ErrNoValue); !ok {
|
||||
// this is a real error
|
||||
log.Printf("Warning: PathValue returned error %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processDependencyTags disables charts based on tags in values
|
||||
func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
|
||||
if reqs == nil {
|
||||
return
|
||||
}
|
||||
vt, err := cvals.Table("tags")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, r := range reqs {
|
||||
var hasTrue, hasFalse bool
|
||||
for _, k := range r.Tags {
|
||||
if b, ok := vt[k]; ok {
|
||||
// if not bool, warn
|
||||
if bv, ok := b.(bool); ok {
|
||||
if bv {
|
||||
hasTrue = true
|
||||
} else {
|
||||
hasFalse = true
|
||||
}
|
||||
} else {
|
||||
log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasTrue && hasFalse {
|
||||
r.Enabled = false
|
||||
} else if hasTrue || !hasTrue && !hasFalse {
|
||||
r.Enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart {
|
||||
for _, c := range charts {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
if c.Name() != dep.Name {
|
||||
continue
|
||||
}
|
||||
if !IsCompatibleRange(dep.Version, c.Metadata.Version) {
|
||||
continue
|
||||
}
|
||||
|
||||
out := *c
|
||||
md := *c.Metadata
|
||||
out.Metadata = &md
|
||||
|
||||
if dep.Alias != "" {
|
||||
md.Name = dep.Alias
|
||||
}
|
||||
return &out
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processDependencyEnabled removes disabled charts from dependencies
|
||||
func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error {
|
||||
if c.Metadata.Dependencies == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var chartDependencies []*chart.Chart
|
||||
// If any dependency is not a part of Chart.yaml
|
||||
// then this should be added to chartDependencies.
|
||||
// However, if the dependency is already specified in Chart.yaml
|
||||
// we should not add it, as it would be anyways processed from Chart.yaml
|
||||
|
||||
Loop:
|
||||
for _, existing := range c.Dependencies() {
|
||||
for _, req := range c.Metadata.Dependencies {
|
||||
if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
chartDependencies = append(chartDependencies, existing)
|
||||
}
|
||||
|
||||
for _, req := range c.Metadata.Dependencies {
|
||||
if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
|
||||
chartDependencies = append(chartDependencies, chartDependency)
|
||||
}
|
||||
if req.Alias != "" {
|
||||
req.Name = req.Alias
|
||||
}
|
||||
}
|
||||
c.SetDependencies(chartDependencies...)
|
||||
|
||||
// set all to true
|
||||
for _, lr := range c.Metadata.Dependencies {
|
||||
lr.Enabled = true
|
||||
}
|
||||
cvals, err := CoalesceValues(c, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// flag dependencies as enabled/disabled
|
||||
processDependencyTags(c.Metadata.Dependencies, cvals)
|
||||
processDependencyConditions(c.Metadata.Dependencies, cvals, path)
|
||||
// make a map of charts to remove
|
||||
rm := map[string]struct{}{}
|
||||
for _, r := range c.Metadata.Dependencies {
|
||||
if !r.Enabled {
|
||||
// remove disabled chart
|
||||
rm[r.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
// don't keep disabled charts in new slice
|
||||
cd := []*chart.Chart{}
|
||||
copy(cd, c.Dependencies()[:0])
|
||||
for _, n := range c.Dependencies() {
|
||||
if _, ok := rm[n.Metadata.Name]; !ok {
|
||||
cd = append(cd, n)
|
||||
}
|
||||
}
|
||||
// don't keep disabled charts in metadata
|
||||
cdMetadata := []*chart.Dependency{}
|
||||
copy(cdMetadata, c.Metadata.Dependencies[:0])
|
||||
for _, n := range c.Metadata.Dependencies {
|
||||
if _, ok := rm[n.Name]; !ok {
|
||||
cdMetadata = append(cdMetadata, n)
|
||||
}
|
||||
}
|
||||
|
||||
// recursively call self to process sub dependencies
|
||||
for _, t := range cd {
|
||||
subpath := path + t.Metadata.Name + "."
|
||||
if err := processDependencyEnabled(t, cvals, subpath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// set the correct dependencies in metadata
|
||||
c.Metadata.Dependencies = nil
|
||||
c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...)
|
||||
c.SetDependencies(cd...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// pathToMap creates a nested map given a YAML path in dot notation.
|
||||
func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
|
||||
if path == "." {
|
||||
return data
|
||||
}
|
||||
return set(parsePath(path), data)
|
||||
}
|
||||
|
||||
func set(path []string, data map[string]interface{}) map[string]interface{} {
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
cur := data
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
cur = map[string]interface{}{path[i]: cur}
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
|
||||
func processImportValues(c *chart.Chart) error {
|
||||
if c.Metadata.Dependencies == nil {
|
||||
return nil
|
||||
}
|
||||
// combine chart values and empty config to get Values
|
||||
cvals, err := CoalesceValues(c, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b := make(map[string]interface{})
|
||||
// import values from each dependency if specified in import-values
|
||||
for _, r := range c.Metadata.Dependencies {
|
||||
var outiv []interface{}
|
||||
for _, riv := range r.ImportValues {
|
||||
switch iv := riv.(type) {
|
||||
case map[string]interface{}:
|
||||
child := iv["child"].(string)
|
||||
parent := iv["parent"].(string)
|
||||
|
||||
outiv = append(outiv, map[string]string{
|
||||
"child": child,
|
||||
"parent": parent,
|
||||
})
|
||||
|
||||
// get child table
|
||||
vv, err := cvals.Table(r.Name + "." + child)
|
||||
if err != nil {
|
||||
log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
|
||||
continue
|
||||
}
|
||||
// create value map from child to be merged into parent
|
||||
b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap()))
|
||||
case string:
|
||||
child := "exports." + iv
|
||||
outiv = append(outiv, map[string]string{
|
||||
"child": child,
|
||||
"parent": ".",
|
||||
})
|
||||
vm, err := cvals.Table(r.Name + "." + child)
|
||||
if err != nil {
|
||||
log.Printf("Warning: ImportValues missing table: %v", err)
|
||||
continue
|
||||
}
|
||||
b = CoalesceTables(b, vm.AsMap())
|
||||
}
|
||||
}
|
||||
// set our formatted import values
|
||||
r.ImportValues = outiv
|
||||
}
|
||||
|
||||
// set the new values
|
||||
c.Values = CoalesceTables(b, cvals)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processDependencyImportValues imports specified chart values from child to parent.
|
||||
func processDependencyImportValues(c *chart.Chart) error {
|
||||
for _, d := range c.Dependencies() {
|
||||
// recurse
|
||||
if err := processDependencyImportValues(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return processImportValues(c)
|
||||
}
|
||||
44
vendor/helm.sh/helm/v3/pkg/chartutil/doc.go
vendored
Normal file
44
vendor/helm.sh/helm/v3/pkg/chartutil/doc.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*Package chartutil contains tools for working with charts.
|
||||
|
||||
Charts are described in the chart package (pkg/chart).
|
||||
This package provides utilities for serializing and deserializing charts.
|
||||
|
||||
A chart can be represented on the file system in one of two ways:
|
||||
|
||||
- As a directory that contains a Chart.yaml file and other chart things.
|
||||
- As a tarred gzipped file containing a directory that then contains a
|
||||
Chart.yaml file.
|
||||
|
||||
This package provides utilities for working with those file formats.
|
||||
|
||||
The preferred way of loading a chart is using 'loader.Load`:
|
||||
|
||||
chart, err := loader.Load(filename)
|
||||
|
||||
This will attempt to discover whether the file at 'filename' is a directory or
|
||||
a chart archive. It will then load accordingly.
|
||||
|
||||
For accepting raw compressed tar file data from an io.Reader, the
|
||||
'loader.LoadArchive()' will read in the data, uncompress it, and unpack it
|
||||
into a Chart.
|
||||
|
||||
When creating charts in memory, use the 'helm.sh/helm/pkg/chart'
|
||||
package directly.
|
||||
*/
|
||||
package chartutil // import "helm.sh/helm/v3/pkg/chartutil"
|
||||
35
vendor/helm.sh/helm/v3/pkg/chartutil/errors.go
vendored
Normal file
35
vendor/helm.sh/helm/v3/pkg/chartutil/errors.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrNoTable indicates that a chart does not have a matching table.
|
||||
type ErrNoTable struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func (e ErrNoTable) Error() string { return fmt.Sprintf("%q is not a table", e.Key) }
|
||||
|
||||
// ErrNoValue indicates that Values does not contain a key with a value
|
||||
type ErrNoValue struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) }
|
||||
91
vendor/helm.sh/helm/v3/pkg/chartutil/expand.go
vendored
Normal file
91
vendor/helm.sh/helm/v3/pkg/chartutil/expand.go
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
// Expand uncompresses and extracts a chart into the specified directory.
|
||||
func Expand(dir string, r io.Reader) error {
|
||||
files, err := loader.LoadArchiveFiles(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the name of the chart
|
||||
var chartName string
|
||||
for _, file := range files {
|
||||
if file.Name == "Chart.yaml" {
|
||||
ch := &chart.Metadata{}
|
||||
if err := yaml.Unmarshal(file.Data, ch); err != nil {
|
||||
return errors.Wrap(err, "cannot load Chart.yaml")
|
||||
}
|
||||
chartName = ch.Name
|
||||
}
|
||||
}
|
||||
if chartName == "" {
|
||||
return errors.New("chart name not specified")
|
||||
}
|
||||
|
||||
// Find the base directory
|
||||
chartdir, err := securejoin.SecureJoin(dir, chartName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy all files verbatim. We don't parse these files because parsing can remove
|
||||
// comments.
|
||||
for _, file := range files {
|
||||
outpath, err := securejoin.SecureJoin(chartdir, file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure the necessary subdirs get created.
|
||||
basedir := filepath.Dir(outpath)
|
||||
if err := os.MkdirAll(basedir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(outpath, file.Data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandFile expands the src file into the dest directory.
|
||||
func ExpandFile(dest, src string) error {
|
||||
h, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.Close()
|
||||
return Expand(dest, h)
|
||||
}
|
||||
87
vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go
vendored
Normal file
87
vendor/helm.sh/helm/v3/pkg/chartutil/jsonschema.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// ValidateAgainstSchema checks that values does not violate the structure laid out in schema
|
||||
func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error {
|
||||
var sb strings.Builder
|
||||
if chrt.Schema != nil {
|
||||
err := ValidateAgainstSingleSchema(values, chrt.Schema)
|
||||
if err != nil {
|
||||
sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name()))
|
||||
sb.WriteString(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// For each dependency, recursively call this function with the coalesced values
|
||||
for _, subchart := range chrt.Dependencies() {
|
||||
subchartValues := values[subchart.Name()].(map[string]interface{})
|
||||
if err := ValidateAgainstSchema(subchart, subchartValues); err != nil {
|
||||
sb.WriteString(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if sb.Len() > 0 {
|
||||
return errors.New(sb.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
|
||||
func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) error {
|
||||
valuesData, err := yaml.Marshal(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valuesJSON, err := yaml.YAMLToJSON(valuesData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(valuesJSON, []byte("null")) {
|
||||
valuesJSON = []byte("{}")
|
||||
}
|
||||
schemaLoader := gojsonschema.NewBytesLoader(schemaJSON)
|
||||
valuesLoader := gojsonschema.NewBytesLoader(valuesJSON)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, valuesLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
var sb strings.Builder
|
||||
for _, desc := range result.Errors() {
|
||||
sb.WriteString(fmt.Sprintf("- %s\n", desc))
|
||||
}
|
||||
return errors.New(sb.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
244
vendor/helm.sh/helm/v3/pkg/chartutil/save.go
vendored
Normal file
244
vendor/helm.sh/helm/v3/pkg/chartutil/save.go
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
|
||||
|
||||
// SaveDir saves a chart as files in a directory.
|
||||
//
|
||||
// This takes the chart name, and creates a new subdirectory inside of the given dest
|
||||
// directory, writing the chart's contents to that subdirectory.
|
||||
func SaveDir(c *chart.Chart, dest string) error {
|
||||
// Create the chart directory
|
||||
outdir := filepath.Join(dest, c.Name())
|
||||
if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
|
||||
return errors.Errorf("file %s already exists and is not a directory", outdir)
|
||||
}
|
||||
if err := os.MkdirAll(outdir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the chart file.
|
||||
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save values.yaml
|
||||
for _, f := range c.Raw {
|
||||
if f.Name == ValuesfileName {
|
||||
vf := filepath.Join(outdir, ValuesfileName)
|
||||
if err := writeFile(vf, f.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save values.schema.json if it exists
|
||||
if c.Schema != nil {
|
||||
filename := filepath.Join(outdir, SchemafileName)
|
||||
if err := writeFile(filename, c.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save templates and files
|
||||
for _, o := range [][]*chart.File{c.Templates, c.Files} {
|
||||
for _, f := range o {
|
||||
n := filepath.Join(outdir, f.Name)
|
||||
if err := writeFile(n, f.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save dependencies
|
||||
base := filepath.Join(outdir, ChartsDir)
|
||||
for _, dep := range c.Dependencies() {
|
||||
// Here, we write each dependency as a tar file.
|
||||
if _, err := Save(dep, base); err != nil {
|
||||
return errors.Wrapf(err, "saving %s", dep.ChartFullPath())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save creates an archived chart to the given directory.
|
||||
//
|
||||
// This takes an existing chart and a destination directory.
|
||||
//
|
||||
// If the directory is /foo, and the chart is named bar, with version 1.0.0, this
|
||||
// will generate /foo/bar-1.0.0.tgz.
|
||||
//
|
||||
// This returns the absolute path to the chart archive file.
|
||||
func Save(c *chart.Chart, outDir string) (string, error) {
|
||||
if err := c.Validate(); err != nil {
|
||||
return "", errors.Wrap(err, "chart validation")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version)
|
||||
filename = filepath.Join(outDir, filename)
|
||||
dir := filepath.Dir(filename)
|
||||
if stat, err := os.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err2 := os.MkdirAll(dir, 0755); err2 != nil {
|
||||
return "", err2
|
||||
}
|
||||
} else {
|
||||
return "", errors.Wrapf(err, "stat %s", dir)
|
||||
}
|
||||
} else if !stat.IsDir() {
|
||||
return "", errors.Errorf("is not a directory: %s", dir)
|
||||
}
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Wrap in gzip writer
|
||||
zipper := gzip.NewWriter(f)
|
||||
zipper.Header.Extra = headerBytes
|
||||
zipper.Header.Comment = "Helm"
|
||||
|
||||
// Wrap in tar writer
|
||||
twriter := tar.NewWriter(zipper)
|
||||
rollback := false
|
||||
defer func() {
|
||||
twriter.Close()
|
||||
zipper.Close()
|
||||
f.Close()
|
||||
if rollback {
|
||||
os.Remove(filename)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := writeTarContents(twriter, c, ""); err != nil {
|
||||
rollback = true
|
||||
return filename, err
|
||||
}
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
|
||||
base := filepath.Join(prefix, c.Name())
|
||||
|
||||
// Pull out the dependencies of a v1 Chart, since there's no way
|
||||
// to tell the serializer to skip a field for just this use case
|
||||
savedDependencies := c.Metadata.Dependencies
|
||||
if c.Metadata.APIVersion == chart.APIVersionV1 {
|
||||
c.Metadata.Dependencies = nil
|
||||
}
|
||||
// Save Chart.yaml
|
||||
cdata, err := yaml.Marshal(c.Metadata)
|
||||
if c.Metadata.APIVersion == chart.APIVersionV1 {
|
||||
c.Metadata.Dependencies = savedDependencies
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save Chart.lock
|
||||
// TODO: remove the APIVersion check when APIVersionV1 is not used anymore
|
||||
if c.Metadata.APIVersion == chart.APIVersionV2 {
|
||||
if c.Lock != nil {
|
||||
ldata, err := yaml.Marshal(c.Lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save values.yaml
|
||||
for _, f := range c.Raw {
|
||||
if f.Name == ValuesfileName {
|
||||
if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save values.schema.json if it exists
|
||||
if c.Schema != nil {
|
||||
if !json.Valid(c.Schema) {
|
||||
return errors.New("Invalid JSON in " + SchemafileName)
|
||||
}
|
||||
if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save templates
|
||||
for _, f := range c.Templates {
|
||||
n := filepath.Join(base, f.Name)
|
||||
if err := writeToTar(out, n, f.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save files
|
||||
for _, f := range c.Files {
|
||||
n := filepath.Join(base, f.Name)
|
||||
if err := writeToTar(out, n, f.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save dependencies
|
||||
for _, dep := range c.Dependencies() {
|
||||
if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeToTar writes a single file to a tar archive.
|
||||
func writeToTar(out *tar.Writer, name string, body []byte) error {
|
||||
// TODO: Do we need to create dummy parent directory names if none exist?
|
||||
h := &tar.Header{
|
||||
Name: filepath.ToSlash(name),
|
||||
Mode: 0644,
|
||||
Size: int64(len(body)),
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
if err := out.WriteHeader(h); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := out.Write(body)
|
||||
return err
|
||||
}
|
||||
104
vendor/helm.sh/helm/v3/pkg/chartutil/validate_name.go
vendored
Normal file
104
vendor/helm.sh/helm/v3/pkg/chartutil/validate_name.go
vendored
Normal 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 chartutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// validName is a regular expression for resource names.
|
||||
//
|
||||
// According to the Kubernetes help text, the regular expression it uses is:
|
||||
//
|
||||
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
|
||||
//
|
||||
// This follows the above regular expression (but requires a full string match, not partial).
|
||||
//
|
||||
// The Kubernetes documentation is here, though it is not entirely correct:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
|
||||
|
||||
var (
|
||||
// errMissingName indicates that a release (name) was not provided.
|
||||
errMissingName = errors.New("no name provided")
|
||||
|
||||
// errInvalidName indicates that an invalid release name was provided
|
||||
errInvalidName = errors.New(fmt.Sprintf(
|
||||
"invalid release name, must match regex %s and the length must not be longer than 53",
|
||||
validName.String()))
|
||||
|
||||
// errInvalidKubernetesName indicates that the name does not meet the Kubernetes
|
||||
// restrictions on metadata names.
|
||||
errInvalidKubernetesName = errors.New(fmt.Sprintf(
|
||||
"invalid metadata name, must match regex %s and the length must not be longer than 253",
|
||||
validName.String()))
|
||||
)
|
||||
|
||||
const (
|
||||
// maxNameLen is the maximum length Helm allows for a release name
|
||||
maxReleaseNameLen = 53
|
||||
// maxMetadataNameLen is the maximum length Kubernetes allows for any name.
|
||||
maxMetadataNameLen = 253
|
||||
)
|
||||
|
||||
// ValidateReleaseName performs checks for an entry for a Helm release name
|
||||
//
|
||||
// For Helm to allow a name, it must be below a certain character count (53) and also match
|
||||
// a reguar expression.
|
||||
//
|
||||
// According to the Kubernetes help text, the regular expression it uses is:
|
||||
//
|
||||
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
|
||||
//
|
||||
// This follows the above regular expression (but requires a full string match, not partial).
|
||||
//
|
||||
// The Kubernetes documentation is here, though it is not entirely correct:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
func ValidateReleaseName(name string) error {
|
||||
// This case is preserved for backwards compatibility
|
||||
if name == "" {
|
||||
return errMissingName
|
||||
|
||||
}
|
||||
if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
|
||||
return errInvalidName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMetadataName validates the name field of a Kubernetes metadata object.
|
||||
//
|
||||
// Empty strings, strings longer than 253 chars, or strings that don't match the regexp
|
||||
// will fail.
|
||||
//
|
||||
// According to the Kubernetes help text, the regular expression it uses is:
|
||||
//
|
||||
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
|
||||
//
|
||||
// This follows the above regular expression (but requires a full string match, not partial).
|
||||
//
|
||||
// The Kubernetes documentation is here, though it is not entirely correct:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
func ValidateMetadataName(name string) error {
|
||||
if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) {
|
||||
return errInvalidKubernetesName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
212
vendor/helm.sh/helm/v3/pkg/chartutil/values.go
vendored
Normal file
212
vendor/helm.sh/helm/v3/pkg/chartutil/values.go
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
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 chartutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// GlobalKey is the name of the Values key that is used for storing global vars.
|
||||
const GlobalKey = "global"
|
||||
|
||||
// Values represents a collection of chart values.
|
||||
type Values map[string]interface{}
|
||||
|
||||
// YAML encodes the Values into a YAML string.
|
||||
func (v Values) YAML() (string, error) {
|
||||
b, err := yaml.Marshal(v)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
// Table gets a table (YAML subsection) from a Values object.
|
||||
//
|
||||
// The table is returned as a Values.
|
||||
//
|
||||
// Compound table names may be specified with dots:
|
||||
//
|
||||
// foo.bar
|
||||
//
|
||||
// The above will be evaluated as "The table bar inside the table
|
||||
// foo".
|
||||
//
|
||||
// An ErrNoTable is returned if the table does not exist.
|
||||
func (v Values) Table(name string) (Values, error) {
|
||||
table := v
|
||||
var err error
|
||||
|
||||
for _, n := range parsePath(name) {
|
||||
if table, err = tableLookup(table, n); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return table, err
|
||||
}
|
||||
|
||||
// AsMap is a utility function for converting Values to a map[string]interface{}.
|
||||
//
|
||||
// It protects against nil map panics.
|
||||
func (v Values) AsMap() map[string]interface{} {
|
||||
if v == nil || len(v) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Encode writes serialized Values information to the given io.Writer.
|
||||
func (v Values) Encode(w io.Writer) error {
|
||||
out, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func tableLookup(v Values, simple string) (Values, error) {
|
||||
v2, ok := v[simple]
|
||||
if !ok {
|
||||
return v, ErrNoTable{simple}
|
||||
}
|
||||
if vv, ok := v2.(map[string]interface{}); ok {
|
||||
return vv, nil
|
||||
}
|
||||
|
||||
// This catches a case where a value is of type Values, but doesn't (for some
|
||||
// reason) match the map[string]interface{}. This has been observed in the
|
||||
// wild, and might be a result of a nil map of type Values.
|
||||
if vv, ok := v2.(Values); ok {
|
||||
return vv, nil
|
||||
}
|
||||
|
||||
return Values{}, ErrNoTable{simple}
|
||||
}
|
||||
|
||||
// ReadValues will parse YAML byte data into a Values.
|
||||
func ReadValues(data []byte) (vals Values, err error) {
|
||||
err = yaml.Unmarshal(data, &vals)
|
||||
if len(vals) == 0 {
|
||||
vals = Values{}
|
||||
}
|
||||
return vals, err
|
||||
}
|
||||
|
||||
// ReadValuesFile will parse a YAML file into a map of values.
|
||||
func ReadValuesFile(filename string) (Values, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
return ReadValues(data)
|
||||
}
|
||||
|
||||
// ReleaseOptions represents the additional release options needed
|
||||
// for the composition of the final values struct
|
||||
type ReleaseOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Revision int
|
||||
IsUpgrade bool
|
||||
IsInstall bool
|
||||
}
|
||||
|
||||
// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
|
||||
//
|
||||
// This takes both ReleaseOptions and Capabilities to merge into the render values.
|
||||
func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) {
|
||||
if caps == nil {
|
||||
caps = DefaultCapabilities
|
||||
}
|
||||
top := map[string]interface{}{
|
||||
"Chart": chrt.Metadata,
|
||||
"Capabilities": caps,
|
||||
"Release": map[string]interface{}{
|
||||
"Name": options.Name,
|
||||
"Namespace": options.Namespace,
|
||||
"IsUpgrade": options.IsUpgrade,
|
||||
"IsInstall": options.IsInstall,
|
||||
"Revision": options.Revision,
|
||||
"Service": "Helm",
|
||||
},
|
||||
}
|
||||
|
||||
vals, err := CoalesceValues(chrt, chrtVals)
|
||||
if err != nil {
|
||||
return top, err
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchema(chrt, vals); err != nil {
|
||||
errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s"
|
||||
return top, fmt.Errorf(errFmt, err.Error())
|
||||
}
|
||||
|
||||
top["Values"] = vals
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
|
||||
func istable(v interface{}) bool {
|
||||
_, ok := v.(map[string]interface{})
|
||||
return ok
|
||||
}
|
||||
|
||||
// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
|
||||
// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
|
||||
// Given the following YAML data the value at path "chapter.one.title" is "Loomings".
|
||||
//
|
||||
// chapter:
|
||||
// one:
|
||||
// title: "Loomings"
|
||||
func (v Values) PathValue(path string) (interface{}, error) {
|
||||
if path == "" {
|
||||
return nil, errors.New("YAML path cannot be empty")
|
||||
}
|
||||
return v.pathValue(parsePath(path))
|
||||
}
|
||||
|
||||
func (v Values) pathValue(path []string) (interface{}, error) {
|
||||
if len(path) == 1 {
|
||||
// if exists must be root key not table
|
||||
if _, ok := v[path[0]]; ok && !istable(v[path[0]]) {
|
||||
return v[path[0]], nil
|
||||
}
|
||||
return nil, ErrNoValue{path[0]}
|
||||
}
|
||||
|
||||
key, path := path[len(path)-1], path[:len(path)-1]
|
||||
// get our table for table path
|
||||
t, err := v.Table(joinPath(path...))
|
||||
if err != nil {
|
||||
return nil, ErrNoValue{key}
|
||||
}
|
||||
// check table for key and ensure value is not a table
|
||||
if k, ok := t[key]; ok && !istable(k) {
|
||||
return k, nil
|
||||
}
|
||||
return nil, ErrNoValue{key}
|
||||
}
|
||||
|
||||
func parsePath(key string) []string { return strings.Split(key, ".") }
|
||||
|
||||
func joinPath(path ...string) string { return strings.Join(path, ".") }
|
||||
56
vendor/helm.sh/helm/v3/pkg/cli/environment.go
vendored
56
vendor/helm.sh/helm/v3/pkg/cli/environment.go
vendored
@@ -26,6 +26,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
@@ -33,6 +34,9 @@ import (
|
||||
"helm.sh/helm/v3/pkg/helmpath"
|
||||
)
|
||||
|
||||
// defaultMaxHistory sets the maximum number of releases to 0: unlimited
|
||||
const defaultMaxHistory = 10
|
||||
|
||||
// EnvSettings describes all of the environment settings.
|
||||
type EnvSettings struct {
|
||||
namespace string
|
||||
@@ -44,8 +48,14 @@ type EnvSettings struct {
|
||||
KubeContext string
|
||||
// Bearer KubeToken used for authentication
|
||||
KubeToken string
|
||||
// Username to impersonate for the operation
|
||||
KubeAsUser string
|
||||
// Groups to impersonate for the operation, multiple groups parsed from a comma delimited list
|
||||
KubeAsGroups []string
|
||||
// Kubernetes API Server Endpoint for authentication
|
||||
KubeAPIServer string
|
||||
// Custom certificate authority file.
|
||||
KubeCaFile string
|
||||
// Debug indicates whether or not Helm is running in Debug mode.
|
||||
Debug bool
|
||||
// RegistryConfig is the path to the registry config file.
|
||||
@@ -56,14 +66,20 @@ type EnvSettings struct {
|
||||
RepositoryCache string
|
||||
// PluginsDirectory is the path to the plugins directory.
|
||||
PluginsDirectory string
|
||||
// MaxHistory is the max release history maintained.
|
||||
MaxHistory int
|
||||
}
|
||||
|
||||
func New() *EnvSettings {
|
||||
env := &EnvSettings{
|
||||
namespace: os.Getenv("HELM_NAMESPACE"),
|
||||
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
|
||||
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
|
||||
KubeToken: os.Getenv("HELM_KUBETOKEN"),
|
||||
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
|
||||
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
|
||||
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")),
|
||||
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
|
||||
@@ -73,11 +89,14 @@ func New() *EnvSettings {
|
||||
|
||||
// bind to kubernetes config flags
|
||||
env.config = &genericclioptions.ConfigFlags{
|
||||
Namespace: &env.namespace,
|
||||
Context: &env.KubeContext,
|
||||
BearerToken: &env.KubeToken,
|
||||
APIServer: &env.KubeAPIServer,
|
||||
KubeConfig: &env.KubeConfig,
|
||||
Namespace: &env.namespace,
|
||||
Context: &env.KubeContext,
|
||||
BearerToken: &env.KubeToken,
|
||||
APIServer: &env.KubeAPIServer,
|
||||
CAFile: &env.KubeCaFile,
|
||||
KubeConfig: &env.KubeConfig,
|
||||
Impersonate: &env.KubeAsUser,
|
||||
ImpersonateGroup: &env.KubeAsGroups,
|
||||
}
|
||||
return env
|
||||
}
|
||||
@@ -88,7 +107,10 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
|
||||
fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use")
|
||||
fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication")
|
||||
fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation")
|
||||
fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
|
||||
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
|
||||
fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection")
|
||||
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
|
||||
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
|
||||
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
|
||||
@@ -102,6 +124,26 @@ func envOr(name, def string) string {
|
||||
return def
|
||||
}
|
||||
|
||||
func envIntOr(name string, def int) int {
|
||||
if name == "" {
|
||||
return def
|
||||
}
|
||||
envVal := envOr(name, strconv.Itoa(def))
|
||||
ret, err := strconv.Atoi(envVal)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func envCSV(name string) (ls []string) {
|
||||
trimmed := strings.Trim(os.Getenv(name), ", ")
|
||||
if trimmed != "" {
|
||||
ls = strings.Split(trimmed, ",")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *EnvSettings) EnvVars() map[string]string {
|
||||
envvars := map[string]string{
|
||||
"HELM_BIN": os.Args[0],
|
||||
@@ -114,11 +156,15 @@ func (s *EnvSettings) EnvVars() map[string]string {
|
||||
"HELM_REPOSITORY_CACHE": s.RepositoryCache,
|
||||
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
|
||||
"HELM_NAMESPACE": s.Namespace(),
|
||||
"HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory),
|
||||
|
||||
// broken, these are populated from helm flags and not kubeconfig.
|
||||
"HELM_KUBECONTEXT": s.KubeContext,
|
||||
"HELM_KUBETOKEN": s.KubeToken,
|
||||
"HELM_KUBEASUSER": s.KubeAsUser,
|
||||
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
|
||||
"HELM_KUBEAPISERVER": s.KubeAPIServer,
|
||||
"HELM_KUBECAFILE": s.KubeCaFile,
|
||||
}
|
||||
if s.KubeConfig != "" {
|
||||
envvars["KUBECONFIG"] = s.KubeConfig
|
||||
|
||||
29
vendor/helm.sh/helm/v3/pkg/getter/getter.go
vendored
29
vendor/helm.sh/helm/v3/pkg/getter/getter.go
vendored
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
@@ -33,10 +34,13 @@ type options struct {
|
||||
certFile string
|
||||
keyFile string
|
||||
caFile string
|
||||
unTar bool
|
||||
insecureSkipVerifyTLS bool
|
||||
username string
|
||||
password string
|
||||
userAgent string
|
||||
version string
|
||||
registryClient *registry.Client
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
@@ -90,6 +94,24 @@ func WithTimeout(timeout time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithTagName(tagname string) Option {
|
||||
return func(opts *options) {
|
||||
opts.version = tagname
|
||||
}
|
||||
}
|
||||
|
||||
func WithRegistryClient(client *registry.Client) Option {
|
||||
return func(opts *options) {
|
||||
opts.registryClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func WithUntar() Option {
|
||||
return func(opts *options) {
|
||||
opts.unTar = true
|
||||
}
|
||||
}
|
||||
|
||||
// Getter is an interface to support GET to the specified URL.
|
||||
type Getter interface {
|
||||
// Get file content by url string
|
||||
@@ -139,11 +161,16 @@ var httpProvider = Provider{
|
||||
New: NewHTTPGetter,
|
||||
}
|
||||
|
||||
var ociProvider = Provider{
|
||||
Schemes: []string{"oci"},
|
||||
New: NewOCIGetter,
|
||||
}
|
||||
|
||||
// All finds all of the registered getters as a list of Provider instances.
|
||||
// Currently, the built-in getters and the discovered plugins with downloader
|
||||
// notations are collected.
|
||||
func All(settings *cli.EnvSettings) Providers {
|
||||
result := Providers{httpProvider}
|
||||
result := Providers{httpProvider, ociProvider}
|
||||
pluginDownloaders, _ := collectPlugins(settings)
|
||||
result = append(result, pluginDownloaders...)
|
||||
return result
|
||||
|
||||
@@ -111,10 +111,13 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
|
||||
}
|
||||
|
||||
if g.opts.insecureSkipVerifyTLS {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
if transport.TLSClientConfig == nil {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
} else {
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
|
||||
69
vendor/helm.sh/helm/v3/pkg/getter/ocigetter.go
vendored
Normal file
69
vendor/helm.sh/helm/v3/pkg/getter/ocigetter.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package getter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
)
|
||||
|
||||
// OCIGetter is the default HTTP(/S) backend handler
|
||||
type OCIGetter struct {
|
||||
opts options
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
return g.get(href)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
r, err := registry.ParseReference(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := client.PullChart(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// NewOCIGetter constructs a valid http/https client as a Getter
|
||||
func NewOCIGetter(options ...Option) (Getter, error) {
|
||||
var client OCIGetter
|
||||
|
||||
for _, opt := range options {
|
||||
opt(&client.opts)
|
||||
}
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
50
vendor/helm.sh/helm/v3/pkg/kube/client.go
vendored
50
vendor/helm.sh/helm/v3/pkg/kube/client.go
vendored
@@ -35,6 +35,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -42,6 +43,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
cachetools "k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
@@ -59,6 +61,8 @@ type Client struct {
|
||||
Log func(string, ...interface{})
|
||||
// Namespace allows to bypass the kubeconfig file for the choice of the namespace
|
||||
Namespace string
|
||||
|
||||
kubeClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
var addToScheme sync.Once
|
||||
@@ -86,9 +90,19 @@ func New(getter genericclioptions.RESTClientGetter) *Client {
|
||||
|
||||
var nopLogger = func(_ string, _ ...interface{}) {}
|
||||
|
||||
// getKubeClient get or create a new KubernetesClientSet
|
||||
func (c *Client) getKubeClient() (*kubernetes.Clientset, error) {
|
||||
var err error
|
||||
if c.kubeClient == nil {
|
||||
c.kubeClient, err = c.Factory.KubernetesClientSet()
|
||||
}
|
||||
|
||||
return c.kubeClient, err
|
||||
}
|
||||
|
||||
// IsReachable tests connectivity to the cluster
|
||||
func (c *Client) IsReachable() error {
|
||||
client, err := c.Factory.KubernetesClientSet()
|
||||
client, err := c.getKubeClient()
|
||||
if err == genericclioptions.ErrEmptyConfig {
|
||||
// re-replace kubernetes ErrEmptyConfig error with a friendy error
|
||||
// moar workarounds for Kubernetes API breaking.
|
||||
@@ -114,7 +128,7 @@ func (c *Client) Create(resources ResourceList) (*Result, error) {
|
||||
|
||||
// Wait up to the given timeout for the specified resources to be ready
|
||||
func (c *Client) Wait(resources ResourceList, timeout time.Duration) error {
|
||||
cs, err := c.Factory.KubernetesClientSet()
|
||||
cs, err := c.getKubeClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -123,7 +137,21 @@ func (c *Client) Wait(resources ResourceList, timeout time.Duration) error {
|
||||
log: c.Log,
|
||||
timeout: timeout,
|
||||
}
|
||||
return w.waitForResources(resources)
|
||||
return w.waitForResources(resources, false)
|
||||
}
|
||||
|
||||
// WaitWithJobs wait up to the given timeout for the specified resources to be ready, including jobs.
|
||||
func (c *Client) WaitWithJobs(resources ResourceList, timeout time.Duration) error {
|
||||
cs, err := c.getKubeClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := waiter{
|
||||
c: cs,
|
||||
log: c.Log,
|
||||
timeout: timeout,
|
||||
}
|
||||
return w.waitForResources(resources, true)
|
||||
}
|
||||
|
||||
func (c *Client) namespace() string {
|
||||
@@ -160,7 +188,7 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
|
||||
}
|
||||
|
||||
// Update takes the current list of objects and target list of objects and
|
||||
// creates resources that don't already exists, updates resources that have been
|
||||
// creates resources that don't already exist, updates resources that have been
|
||||
// modified in the target configuration, and deletes resources from the current
|
||||
// configuration that are not present in the target configuration. If an error
|
||||
// occurs, a Result will still be returned with the error, containing all
|
||||
@@ -177,7 +205,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
|
||||
}
|
||||
|
||||
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||
if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
|
||||
if _, err := helper.Get(info.Namespace, info.Name); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return errors.Wrap(err, "could not get information about the resource")
|
||||
}
|
||||
@@ -373,7 +401,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
|
||||
|
||||
// Fetch the current object for the three way merge
|
||||
helper := resource.NewHelper(target.Client, target.Mapping)
|
||||
currentObj, err := helper.Get(target.Namespace, target.Name, target.Export)
|
||||
currentObj, err := helper.Get(target.Namespace, target.Name)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name)
|
||||
}
|
||||
@@ -478,7 +506,7 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err
|
||||
|
||||
ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
_, err = watchtools.ListWatchUntil(ctx, lw, func(e watch.Event) (bool, error) {
|
||||
_, err = watchtools.UntilWithSync(ctx, lw, &unstructured.Unstructured{}, nil, func(e watch.Event) (bool, error) {
|
||||
// Make sure the incoming object is versioned as we use unstructured
|
||||
// objects when we build manifests
|
||||
obj := convertWithMapper(e.Object, info.Mapping)
|
||||
@@ -542,14 +570,14 @@ func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error
|
||||
|
||||
switch o.Status.Phase {
|
||||
case v1.PodSucceeded:
|
||||
fmt.Printf("Pod %s succeeded\n", o.Name)
|
||||
c.Log("Pod %s succeeded", o.Name)
|
||||
return true, nil
|
||||
case v1.PodFailed:
|
||||
return true, errors.Errorf("pod %s failed", o.Name)
|
||||
case v1.PodPending:
|
||||
fmt.Printf("Pod %s pending\n", o.Name)
|
||||
c.Log("Pod %s pending", o.Name)
|
||||
case v1.PodRunning:
|
||||
fmt.Printf("Pod %s running\n", o.Name)
|
||||
c.Log("Pod %s running", o.Name)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@@ -571,7 +599,7 @@ func scrubValidationError(err error) error {
|
||||
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
|
||||
// and returns said phase (PodSucceeded or PodFailed qualify).
|
||||
func (c *Client) WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) {
|
||||
client, err := c.Factory.KubernetesClientSet()
|
||||
client, err := c.getKubeClient()
|
||||
if err != nil {
|
||||
return v1.PodUnknown, err
|
||||
}
|
||||
|
||||
2
vendor/helm.sh/helm/v3/pkg/kube/interface.go
vendored
2
vendor/helm.sh/helm/v3/pkg/kube/interface.go
vendored
@@ -32,6 +32,8 @@ type Interface interface {
|
||||
|
||||
Wait(resources ResourceList, timeout time.Duration) error
|
||||
|
||||
WaitWithJobs(resources ResourceList, timeout time.Duration) error
|
||||
|
||||
// Delete destroys one or more resources.
|
||||
Delete(resources ResourceList) (*Result, []error)
|
||||
|
||||
|
||||
29
vendor/helm.sh/helm/v3/pkg/kube/wait.go
vendored
29
vendor/helm.sh/helm/v3/pkg/kube/wait.go
vendored
@@ -47,9 +47,9 @@ type waiter struct {
|
||||
log func(string, ...interface{})
|
||||
}
|
||||
|
||||
// waitForResources polls to get the current status of all pods, PVCs, and Services
|
||||
// until all are ready or a timeout is reached
|
||||
func (w *waiter) waitForResources(created ResourceList) error {
|
||||
// waitForResources polls to get the current status of all pods, PVCs, Services and
|
||||
// Jobs(optional) until all are ready or a timeout is reached
|
||||
func (w *waiter) waitForResources(created ResourceList, waitForJobsEnabled bool) error {
|
||||
w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout)
|
||||
|
||||
return wait.Poll(2*time.Second, w.timeout, func() (bool, error) {
|
||||
@@ -67,6 +67,13 @@ func (w *waiter) waitForResources(created ResourceList) error {
|
||||
if err != nil || !w.isPodReady(pod) {
|
||||
return false, err
|
||||
}
|
||||
case *batchv1.Job:
|
||||
if waitForJobsEnabled {
|
||||
job, err := w.c.BatchV1().Jobs(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil || !w.jobReady(job) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
case *appsv1.Deployment, *appsv1beta1.Deployment, *appsv1beta2.Deployment, *extensionsv1beta1.Deployment:
|
||||
currentDeployment, err := w.c.AppsV1().Deployments(v.Namespace).Get(context.Background(), v.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
@@ -182,14 +189,26 @@ func (w *waiter) isPodReady(pod *corev1.Pod) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *waiter) jobReady(job *batchv1.Job) bool {
|
||||
if job.Status.Failed >= *job.Spec.BackoffLimit {
|
||||
w.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName())
|
||||
return false
|
||||
}
|
||||
if job.Status.Succeeded < *job.Spec.Completions {
|
||||
w.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *waiter) serviceReady(s *corev1.Service) bool {
|
||||
// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
|
||||
if s.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
return true
|
||||
}
|
||||
|
||||
// Make sure the service is not explicitly set to "None" before checking the IP
|
||||
if s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "" {
|
||||
// Ensure that the service cluster IP is not empty
|
||||
if s.Spec.ClusterIP == "" {
|
||||
w.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName())
|
||||
return false
|
||||
}
|
||||
|
||||
55
vendor/helm.sh/helm/v3/pkg/plugin/plugin.go
vendored
55
vendor/helm.sh/helm/v3/pkg/plugin/plugin.go
vendored
@@ -20,9 +20,11 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
@@ -94,6 +96,12 @@ type Metadata struct {
|
||||
// Downloaders field is used if the plugin supply downloader mechanism
|
||||
// for special protocols.
|
||||
Downloaders []Downloaders `json:"downloaders"`
|
||||
|
||||
// UseTunnelDeprecated indicates that this command needs a tunnel.
|
||||
// Setting this will cause a number of side effects, such as the
|
||||
// automatic setting of HELM_HOST.
|
||||
// DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4
|
||||
UseTunnelDeprecated bool `json:"useTunnel,omitempty"`
|
||||
}
|
||||
|
||||
// Plugin represents a plugin.
|
||||
@@ -157,18 +165,51 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
|
||||
return main, baseArgs, nil
|
||||
}
|
||||
|
||||
// validPluginName is a regular expression that validates plugin names.
|
||||
//
|
||||
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
|
||||
var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$")
|
||||
|
||||
// validatePluginData validates a plugin's YAML data.
|
||||
func validatePluginData(plug *Plugin, filepath string) error {
|
||||
if !validPluginName.MatchString(plug.Metadata.Name) {
|
||||
return fmt.Errorf("invalid plugin name at %q", filepath)
|
||||
}
|
||||
// We could also validate SemVer, executable, and other fields should we so choose.
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectDuplicates(plugs []*Plugin) error {
|
||||
names := map[string]string{}
|
||||
|
||||
for _, plug := range plugs {
|
||||
if oldpath, ok := names[plug.Metadata.Name]; ok {
|
||||
return fmt.Errorf(
|
||||
"two plugins claim the name %q at %q and %q",
|
||||
plug.Metadata.Name,
|
||||
oldpath,
|
||||
plug.Dir,
|
||||
)
|
||||
}
|
||||
names[plug.Metadata.Name] = plug.Dir
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadDir loads a plugin from the given directory.
|
||||
func LoadDir(dirname string) (*Plugin, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName))
|
||||
pluginfile := filepath.Join(dirname, PluginFileName)
|
||||
data, err := ioutil.ReadFile(pluginfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile)
|
||||
}
|
||||
|
||||
plug := &Plugin{Dir: dirname}
|
||||
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
|
||||
return nil, err
|
||||
if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile)
|
||||
}
|
||||
return plug, nil
|
||||
return plug, validatePluginData(plug, pluginfile)
|
||||
}
|
||||
|
||||
// LoadAll loads all plugins found beneath the base directory.
|
||||
@@ -180,7 +221,7 @@ func LoadAll(basedir string) ([]*Plugin, error) {
|
||||
scanpath := filepath.Join(basedir, "*", PluginFileName)
|
||||
matches, err := filepath.Glob(scanpath)
|
||||
if err != nil {
|
||||
return plugins, err
|
||||
return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath)
|
||||
}
|
||||
|
||||
if matches == nil {
|
||||
@@ -195,7 +236,7 @@ func LoadAll(basedir string) ([]*Plugin, error) {
|
||||
}
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
return plugins, nil
|
||||
return plugins, detectDuplicates(plugins)
|
||||
}
|
||||
|
||||
// FindPlugins returns a list of YAML files that describe plugins.
|
||||
|
||||
3
vendor/helm.sh/helm/v3/pkg/release/mock.go
vendored
3
vendor/helm.sh/helm/v3/pkg/release/mock.go
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package release
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
@@ -53,7 +54,7 @@ func Mock(opts *MockReleaseOptions) *Release {
|
||||
|
||||
name := opts.Name
|
||||
if name == "" {
|
||||
name = "testrelease-" + string(rand.Intn(100))
|
||||
name = "testrelease-" + fmt.Sprint(rand.Intn(100))
|
||||
}
|
||||
|
||||
version := 1
|
||||
|
||||
@@ -37,6 +37,9 @@ type Release struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
// Namespace is the kubernetes namespace of the release.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Labels of the release.
|
||||
// Disabled encoding into Json cause labels are stored in storage driver metadata field.
|
||||
Labels map[string]string `json:"-"`
|
||||
}
|
||||
|
||||
// SetStatus is a helper for setting the status on a release.
|
||||
|
||||
5
vendor/helm.sh/helm/v3/pkg/release/status.go
vendored
5
vendor/helm.sh/helm/v3/pkg/release/status.go
vendored
@@ -42,3 +42,8 @@ const (
|
||||
)
|
||||
|
||||
func (x Status) String() string { return string(x) }
|
||||
|
||||
// IsPending determines if this status is a state or a transition.
|
||||
func (x Status) IsPending() bool {
|
||||
return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback
|
||||
}
|
||||
|
||||
23
vendor/helm.sh/helm/v3/pkg/repo/chartrepo.go
vendored
23
vendor/helm.sh/helm/v3/pkg/repo/chartrepo.go
vendored
@@ -205,6 +205,14 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF
|
||||
// without adding repo to repositories, like FindChartInRepoURL,
|
||||
// but it also receives credentials for the chart repository.
|
||||
func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
|
||||
return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters)
|
||||
}
|
||||
|
||||
// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL
|
||||
// without adding repo to repositories, like FindChartInRepoURL,
|
||||
// but it also receives credentials and TLS verify flag for the chart repository.
|
||||
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
|
||||
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
|
||||
|
||||
// Download and write the index file to a temporary location
|
||||
buf := make([]byte, 20)
|
||||
@@ -212,13 +220,14 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
|
||||
name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-")
|
||||
|
||||
c := Entry{
|
||||
URL: repoURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
Name: name,
|
||||
URL: repoURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
Name: name,
|
||||
InsecureSkipTLSverify: insecureSkipTLSverify,
|
||||
}
|
||||
r, err := NewChartRepository(&c, getters)
|
||||
if err != nil {
|
||||
|
||||
25
vendor/helm.sh/helm/v3/pkg/repo/index.go
vendored
25
vendor/helm.sh/helm/v3/pkg/repo/index.go
vendored
@@ -77,10 +77,16 @@ func (c ChartVersions) Less(a, b int) bool {
|
||||
|
||||
// IndexFile represents the index file in a chart repository
|
||||
type IndexFile struct {
|
||||
// This is used ONLY for validation against chartmuseum's index files and is discarded after validation.
|
||||
ServerInfo map[string]interface{} `json:"serverInfo,omitempty"`
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Generated time.Time `json:"generated"`
|
||||
Entries map[string]ChartVersions `json:"entries"`
|
||||
PublicKeys []string `json:"publicKeys,omitempty"`
|
||||
|
||||
// Annotations are additional mappings uninterpreted by Helm. They are made available for
|
||||
// other applications to add information to the index file.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// NewIndexFile initializes an index.
|
||||
@@ -228,6 +234,23 @@ type ChartVersion struct {
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Removed bool `json:"removed,omitempty"`
|
||||
Digest string `json:"digest,omitempty"`
|
||||
|
||||
// ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced
|
||||
// this with Digest. However, with a strict YAML parser enabled, a field must be
|
||||
// present on the struct for backwards compatibility.
|
||||
ChecksumDeprecated string `json:"checksum,omitempty"`
|
||||
|
||||
// EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
|
||||
// YAML parser enabled, this field must be present.
|
||||
EngineDeprecated string `json:"engine,omitempty"`
|
||||
|
||||
// TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
|
||||
// YAML parser enabled, this field must be present.
|
||||
TillerVersionDeprecated string `json:"tillerVersion,omitempty"`
|
||||
|
||||
// URLDeprecated is deprectaed in Helm 3, superseded by URLs. It is ignored. However,
|
||||
// with a strict YAML parser enabled, this must be present on the struct.
|
||||
URLDeprecated string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// IndexDirectory reads a (flat) directory and generates an index.
|
||||
@@ -281,7 +304,7 @@ 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) (*IndexFile, error) {
|
||||
i := &IndexFile{}
|
||||
if err := yaml.Unmarshal(data, i); err != nil {
|
||||
if err := yaml.UnmarshalStrict(data, i); err != nil {
|
||||
return i, err
|
||||
}
|
||||
i.SortEntries()
|
||||
|
||||
Reference in New Issue
Block a user