github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+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/rest"
    28  
    29  	"helm.sh/helm/pkg/chartutil"
    30  	"helm.sh/helm/pkg/kube"
    31  	"helm.sh/helm/pkg/registry"
    32  	"helm.sh/helm/pkg/release"
    33  	"helm.sh/helm/pkg/storage"
    34  )
    35  
    36  // Timestamper is a function capable of producing a timestamp.Timestamper.
    37  //
    38  // By default, this is a time.Time function. This can be overridden for testing,
    39  // though, so that timestamps are predictable.
    40  var Timestamper = time.Now
    41  
    42  var (
    43  	// errMissingChart indicates that a chart was not provided.
    44  	errMissingChart = errors.New("no chart provided")
    45  	// errMissingRelease indicates that a release (name) was not provided.
    46  	errMissingRelease = errors.New("no release provided")
    47  	// errInvalidRevision indicates that an invalid release revision number was provided.
    48  	errInvalidRevision = errors.New("invalid release revision")
    49  	// errInvalidName indicates that an invalid release name was provided
    50  	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")
    51  )
    52  
    53  // ValidName is a regular expression for names.
    54  //
    55  // According to the Kubernetes help text, the regular expression it uses is:
    56  //
    57  //	(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?
    58  //
    59  // We modified that. First, we added start and end delimiters. Second, we changed
    60  // the final ? to + to require that the pattern match at least once. This modification
    61  // prevents an empty string from matching.
    62  var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$")
    63  
    64  // Configuration injects the dependencies that all actions share.
    65  type Configuration struct {
    66  	// RESTClientGetter is an interface that loads Kuberbetes clients.
    67  	RESTClientGetter RESTClientGetter
    68  
    69  	// Releases stores records of releases.
    70  	Releases *storage.Storage
    71  
    72  	// KubeClient is a Kubernetes API client.
    73  	KubeClient kube.Interface
    74  
    75  	// RegistryClient is a client for working with registries
    76  	RegistryClient *registry.Client
    77  
    78  	// Capabilities describes the capabilities of the Kubernetes cluster.
    79  	Capabilities *chartutil.Capabilities
    80  
    81  	Log func(string, ...interface{})
    82  }
    83  
    84  // capabilities builds a Capabilities from discovery information.
    85  func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
    86  	if c.Capabilities != nil {
    87  		return c.Capabilities, nil
    88  	}
    89  	dc, err := c.RESTClientGetter.ToDiscoveryClient()
    90  	if err != nil {
    91  		return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
    92  	}
    93  	kubeVersion, err := dc.ServerVersion()
    94  	if err != nil {
    95  		return nil, errors.Wrap(err, "could not get server version from Kubernetes")
    96  	}
    97  	apiVersions, err := GetVersionSet(dc)
    98  	if err != nil {
    99  		return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
   100  	}
   101  
   102  	c.Capabilities = &chartutil.Capabilities{
   103  		APIVersions: apiVersions,
   104  		KubeVersion: chartutil.KubeVersion{
   105  			Version: kubeVersion.GitVersion,
   106  			Major:   kubeVersion.Major,
   107  			Minor:   kubeVersion.Minor,
   108  		},
   109  	}
   110  	return c.Capabilities, nil
   111  }
   112  
   113  // Now generates a timestamp
   114  //
   115  // If the configuration has a Timestamper on it, that will be used.
   116  // Otherwise, this will use time.Now().
   117  func (c *Configuration) Now() time.Time {
   118  	return Timestamper()
   119  }
   120  
   121  func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
   122  	if err := validateReleaseName(name); err != nil {
   123  		return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
   124  	}
   125  
   126  	if version <= 0 {
   127  		return c.Releases.Last(name)
   128  	}
   129  
   130  	return c.Releases.Get(name, version)
   131  }
   132  
   133  // GetVersionSet retrieves a set of available k8s API versions
   134  func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) {
   135  	groups, resources, err := client.ServerGroupsAndResources()
   136  	if err != nil {
   137  		return chartutil.DefaultVersionSet, err
   138  	}
   139  
   140  	// FIXME: The Kubernetes test fixture for cli appears to always return nil
   141  	// for calls to Discovery().ServerGroupsAndResources(). So in this case, we
   142  	// return the default API list. This is also a safe value to return in any
   143  	// other odd-ball case.
   144  	if len(groups) == 0 && len(resources) == 0 {
   145  		return chartutil.DefaultVersionSet, nil
   146  	}
   147  
   148  	versionMap := make(map[string]interface{})
   149  	versions := []string{}
   150  
   151  	// Extract the groups
   152  	for _, g := range groups {
   153  		for _, gv := range g.Versions {
   154  			versionMap[gv.GroupVersion] = struct{}{}
   155  		}
   156  	}
   157  
   158  	// Extract the resources
   159  	var id string
   160  	var ok bool
   161  	for _, r := range resources {
   162  		for _, rl := range r.APIResources {
   163  
   164  			// A Kind at a GroupVersion can show up more than once. We only want
   165  			// it displayed once in the final output.
   166  			id = path.Join(r.GroupVersion, rl.Kind)
   167  			if _, ok = versionMap[id]; !ok {
   168  				versionMap[id] = struct{}{}
   169  			}
   170  		}
   171  	}
   172  
   173  	// Convert to a form that NewVersionSet can use
   174  	for k := range versionMap {
   175  		versions = append(versions, k)
   176  	}
   177  
   178  	return chartutil.VersionSet(versions), nil
   179  }
   180  
   181  // recordRelease with an update operation in case reuse has been set.
   182  func (c *Configuration) recordRelease(r *release.Release) {
   183  	if err := c.Releases.Update(r); err != nil {
   184  		c.Log("warning: Failed to update release %s: %s", r.Name, err)
   185  	}
   186  }
   187  
   188  type RESTClientGetter interface {
   189  	ToRESTConfig() (*rest.Config, error)
   190  	ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
   191  	ToRESTMapper() (meta.RESTMapper, error)
   192  }