github.com/uhthomas/helm@v3.0.0-beta.3+incompatible/pkg/action/action.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package action
    18  
    19  import (
    20  	"path"
    21  	"regexp"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	"k8s.io/client-go/discovery"
    27  	"k8s.io/client-go/kubernetes"
    28  	"k8s.io/client-go/rest"
    29  
    30  	"helm.sh/helm/internal/experimental/registry"
    31  	"helm.sh/helm/pkg/chartutil"
    32  	"helm.sh/helm/pkg/kube"
    33  	"helm.sh/helm/pkg/release"
    34  	"helm.sh/helm/pkg/storage"
    35  )
    36  
    37  // Timestamper is a function capable of producing a timestamp.Timestamper.
    38  //
    39  // By default, this is a time.Time function. This can be overridden for testing,
    40  // though, so that timestamps are predictable.
    41  var Timestamper = time.Now
    42  
    43  var (
    44  	// errMissingChart indicates that a chart was not provided.
    45  	errMissingChart = errors.New("no chart provided")
    46  	// errMissingRelease indicates that a release (name) was not provided.
    47  	errMissingRelease = errors.New("no release provided")
    48  	// errInvalidRevision indicates that an invalid release revision number was provided.
    49  	errInvalidRevision = errors.New("invalid release revision")
    50  	// errInvalidName indicates that an invalid release name was provided
    51  	errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
    52  )
    53  
    54  // ValidName is a regular expression for names.
    55  //
    56  // According to the Kubernetes help text, the regular expression it uses is:
    57  //
    58  //	(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?
    59  //
    60  // We modified that. First, we added start and end delimiters. Second, we changed
    61  // the final ? to + to require that the pattern match at least once. This modification
    62  // prevents an empty string from matching.
    63  var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$")
    64  
    65  // Configuration injects the dependencies that all actions share.
    66  type Configuration struct {
    67  	// RESTClientGetter is an interface that loads Kuberbetes clients.
    68  	RESTClientGetter RESTClientGetter
    69  
    70  	// Releases stores records of releases.
    71  	Releases *storage.Storage
    72  
    73  	// KubeClient is a Kubernetes API client.
    74  	KubeClient kube.Interface
    75  
    76  	// RegistryClient is a client for working with registries
    77  	RegistryClient *registry.Client
    78  
    79  	// Capabilities describes the capabilities of the Kubernetes cluster.
    80  	Capabilities *chartutil.Capabilities
    81  
    82  	Log func(string, ...interface{})
    83  }
    84  
    85  // capabilities builds a Capabilities from discovery information.
    86  func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
    87  	if c.Capabilities != nil {
    88  		return c.Capabilities, nil
    89  	}
    90  	dc, err := c.RESTClientGetter.ToDiscoveryClient()
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
    93  	}
    94  	kubeVersion, err := dc.ServerVersion()
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "could not get server version from Kubernetes")
    97  	}
    98  	apiVersions, err := GetVersionSet(dc)
    99  	if err != nil {
   100  		return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
   101  	}
   102  
   103  	c.Capabilities = &chartutil.Capabilities{
   104  		APIVersions: apiVersions,
   105  		KubeVersion: chartutil.KubeVersion{
   106  			Version: kubeVersion.GitVersion,
   107  			Major:   kubeVersion.Major,
   108  			Minor:   kubeVersion.Minor,
   109  		},
   110  	}
   111  	return c.Capabilities, nil
   112  }
   113  
   114  func (c *Configuration) KubernetesClientSet() (kubernetes.Interface, error) {
   115  	conf, err := c.RESTClientGetter.ToRESTConfig()
   116  	if err != nil {
   117  		return nil, errors.Wrap(err, "unable to generate config for kubernetes client")
   118  	}
   119  
   120  	return kubernetes.NewForConfig(conf)
   121  }
   122  
   123  // Now generates a timestamp
   124  //
   125  // If the configuration has a Timestamper on it, that will be used.
   126  // Otherwise, this will use time.Now().
   127  func (c *Configuration) Now() time.Time {
   128  	return Timestamper()
   129  }
   130  
   131  func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
   132  	if err := validateReleaseName(name); err != nil {
   133  		return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
   134  	}
   135  
   136  	if version <= 0 {
   137  		return c.Releases.Last(name)
   138  	}
   139  
   140  	return c.Releases.Get(name, version)
   141  }
   142  
   143  // GetVersionSet retrieves a set of available k8s API versions
   144  func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) {
   145  	groups, resources, err := client.ServerGroupsAndResources()
   146  	if err != nil {
   147  		return chartutil.DefaultVersionSet, err
   148  	}
   149  
   150  	// FIXME: The Kubernetes test fixture for cli appears to always return nil
   151  	// for calls to Discovery().ServerGroupsAndResources(). So in this case, we
   152  	// return the default API list. This is also a safe value to return in any
   153  	// other odd-ball case.
   154  	if len(groups) == 0 && len(resources) == 0 {
   155  		return chartutil.DefaultVersionSet, nil
   156  	}
   157  
   158  	versionMap := make(map[string]interface{})
   159  	versions := []string{}
   160  
   161  	// Extract the groups
   162  	for _, g := range groups {
   163  		for _, gv := range g.Versions {
   164  			versionMap[gv.GroupVersion] = struct{}{}
   165  		}
   166  	}
   167  
   168  	// Extract the resources
   169  	var id string
   170  	var ok bool
   171  	for _, r := range resources {
   172  		for _, rl := range r.APIResources {
   173  
   174  			// A Kind at a GroupVersion can show up more than once. We only want
   175  			// it displayed once in the final output.
   176  			id = path.Join(r.GroupVersion, rl.Kind)
   177  			if _, ok = versionMap[id]; !ok {
   178  				versionMap[id] = struct{}{}
   179  			}
   180  		}
   181  	}
   182  
   183  	// Convert to a form that NewVersionSet can use
   184  	for k := range versionMap {
   185  		versions = append(versions, k)
   186  	}
   187  
   188  	return chartutil.VersionSet(versions), nil
   189  }
   190  
   191  // recordRelease with an update operation in case reuse has been set.
   192  func (c *Configuration) recordRelease(r *release.Release) {
   193  	if err := c.Releases.Update(r); err != nil {
   194  		c.Log("warning: Failed to update release %s: %s", r.Name, err)
   195  	}
   196  }
   197  
   198  type RESTClientGetter interface {
   199  	ToRESTConfig() (*rest.Config, error)
   200  	ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
   201  	ToRESTMapper() (meta.RESTMapper, error)
   202  }