Use helm's merging of vaulefiles and files

This commit is contained in:
Ole Markus With 2020-12-15 16:03:08 +01:00
parent b6a07c16fe
commit 24c9d03477
31 changed files with 1721 additions and 31 deletions

View File

@ -103,6 +103,7 @@ go_library(
"//vendor/github.com/spf13/cobra/doc:go_default_library",
"//vendor/github.com/spf13/viper:go_default_library",
"//vendor/golang.org/x/crypto/ssh:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/cli/values:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/strvals:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
@ -156,6 +157,7 @@ go_test(
"//channels:channeldata", # keep
"//tests/integration/create_cluster:exported_testdata", # keep
"//tests/integration/update_cluster:exported_testdata", # keep
"test/values.yaml",
],
embed = [":go_default_library"],
shard_count = 10,

View File

@ -31,6 +31,8 @@ import (
"k8s.io/kubectl/pkg/util/templates"
"sigs.k8s.io/yaml"
helmvalues "helm.sh/helm/v3/pkg/cli/values"
"k8s.io/kops/cmd/kops/util"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/try"
@ -250,7 +252,16 @@ func newTemplateContext(files []string, values []string, stringValues []string)
return nil, fmt.Errorf("unable decode the configuration file: %s, error: %v", j, err)
}
context = mergeValues(context, ctx)
valueOpts := &helmvalues.Options{
Values: values,
ValueFiles: files,
StringValues: stringValues,
}
context, err = valueOpts.MergeValues(nil)
if err != nil {
return nil, err
}
}
}
@ -271,35 +282,6 @@ func newTemplateContext(files []string, values []string, stringValues []string)
return context, nil
}
// Merges source and destination map, preferring values from the source map
// Copied from the Helm (https://github.com/kubernetes/helm) project:
// https://github.com/kubernetes/helm/blob/282984e75fd115a0765730efe09d8257c72fa56d/cmd/helm/install.go#L302
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}
// expandFiles is responsible for resolving any references to directories
func expandFiles(path string) ([]string, error) {
// @check if the path is a directory, if not we can return straight away

View File

@ -6,7 +6,7 @@
* Added [template funtions](https://kops.sigs.k8s.io/operations/cluster_template/#template-functions) for kubernetes version based on channel data.
* The helm functions used to parse `--set` flags on `kops toolbox template` has been updated to helm3, which has a slightly different behaviour from helm3.
* kOps now use helm3 functions for merging template `--set` and `--values` arguments. This has slightly different behaviour than previous helm2-like logic.
# Breaking changes

View File

@ -0,0 +1,13 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"cfg.go",
"tls.go",
],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/internal/tlsutil",
importpath = "helm.sh/helm/v3/internal/tlsutil",
visibility = ["//vendor/helm.sh/helm/v3:__subpackages__"],
deps = ["//vendor/github.com/pkg/errors:go_default_library"],
)

View File

@ -0,0 +1,58 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tlsutil
import (
"crypto/tls"
"crypto/x509"
"os"
"github.com/pkg/errors"
)
// Options represents configurable options used to create client and server TLS configurations.
type Options struct {
CaCertFile string
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
KeyFile string
CertFile string
// Client-only options
InsecureSkipVerify bool
}
// ClientConfig returns a TLS configuration for use by a Helm client.
func ClientConfig(opts Options) (cfg *tls.Config, err error) {
var cert *tls.Certificate
var pool *x509.CertPool
if opts.CertFile != "" || opts.KeyFile != "" {
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
if os.IsNotExist(err) {
return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
}
if !opts.InsecureSkipVerify && opts.CaCertFile != "" {
if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
return nil, err
}
}
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
return cfg, nil
}

View File

@ -0,0 +1,76 @@
/*
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 tlsutil
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"github.com/pkg/errors"
)
// NewClientTLS returns tls.Config appropriate for client auth.
func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
config := tls.Config{}
if certFile != "" && keyFile != "" {
cert, err := CertFromFilePair(certFile, keyFile)
if err != nil {
return nil, err
}
config.Certificates = []tls.Certificate{*cert}
}
if caFile != "" {
cp, err := CertPoolFromFile(caFile)
if err != nil {
return nil, err
}
config.RootCAs = cp
}
return &config, nil
}
// CertPoolFromFile returns an x509.CertPool containing the certificates
// in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Errorf("can't read CA file: %v", filename)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, errors.Errorf("failed to append certificates from file: %s", filename)
}
return cp, nil
}
// CertFromFilePair returns an tls.Certificate containing the
// certificates public/private key pair from a pair of given PEM-encoded files.
// Returns an error if the file could not be read, a certificate could not
// be parsed, or if the file does not contain any certificates
func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errors.Wrapf(err, "can't load key pair from cert %s and key %s", certFile, keyFile)
}
return &cert, err
}

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["urlutil.go"],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/internal/urlutil",
importpath = "helm.sh/helm/v3/internal/urlutil",
visibility = ["//vendor/helm.sh/helm/v3:__subpackages__"],
)

View File

@ -0,0 +1,73 @@
/*
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 urlutil
import (
"net/url"
"path"
"path/filepath"
)
// URLJoin joins a base URL to one or more path components.
//
// It's like filepath.Join for URLs. If the baseURL is pathish, this will still
// perform a join.
//
// If the URL is unparsable, this returns an error.
func URLJoin(baseURL string, paths ...string) (string, error) {
u, err := url.Parse(baseURL)
if err != nil {
return "", err
}
// We want path instead of filepath because path always uses /.
all := []string{u.Path}
all = append(all, paths...)
u.Path = path.Join(all...)
return u.String(), nil
}
// Equal normalizes two URLs and then compares for equality.
func Equal(a, b string) bool {
au, err := url.Parse(a)
if err != nil {
a = filepath.Clean(a)
b = filepath.Clean(b)
// If urls are paths, return true only if they are an exact match
return a == b
}
bu, err := url.Parse(b)
if err != nil {
return false
}
for _, u := range []*url.URL{au, bu} {
if u.Path == "" {
u.Path = "/"
}
u.Path = filepath.Clean(u.Path)
}
return au.String() == bu.String()
}
// ExtractHostname returns hostname from URL
func ExtractHostname(addr string) (string, error) {
u, err := url.Parse(addr)
if err != nil {
return "", err
}
return u.Hostname(), nil
}

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["version.go"],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/internal/version",
importpath = "helm.sh/helm/v3/internal/version",
visibility = ["//vendor/helm.sh/helm/v3:__subpackages__"],
)

View File

@ -0,0 +1,81 @@
/*
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 version // import "helm.sh/helm/v3/internal/version"
import (
"flag"
"runtime"
"strings"
)
var (
// version is the current version of Helm.
// Update this whenever making a new release.
// The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata]
//
// Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements.
version = "v3.4"
// metadata is extra build time data
metadata = ""
// gitCommit is the git sha1
gitCommit = ""
// gitTreeState is the state of the git tree
gitTreeState = ""
)
// BuildInfo describes the compile time information.
type BuildInfo struct {
// Version is the current semver.
Version string `json:"version,omitempty"`
// GitCommit is the git sha1.
GitCommit string `json:"git_commit,omitempty"`
// GitTreeState is the state of the git tree.
GitTreeState string `json:"git_tree_state,omitempty"`
// GoVersion is the version of the Go compiler used.
GoVersion string `json:"go_version,omitempty"`
}
// GetVersion returns the semver string of the version
func GetVersion() string {
if metadata == "" {
return version
}
return version + "+" + metadata
}
// GetUserAgent returns a user agent for user with an HTTP client
func GetUserAgent() string {
return "Helm/" + strings.TrimPrefix(GetVersion(), "v")
}
// Get returns build info
func Get() BuildInfo {
v := BuildInfo{
Version: GetVersion(),
GitCommit: gitCommit,
GitTreeState: gitTreeState,
GoVersion: runtime.Version(),
}
// HACK(bacongobbler): strip out GoVersion during a test run for consistent test output
if flag.Lookup("test.v") != nil {
v.GoVersion = ""
}
return v
}

View File

@ -0,0 +1,14 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["environment.go"],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/cli",
importpath = "helm.sh/helm/v3/pkg/cli",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/helmpath:go_default_library",
"//vendor/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
],
)

View File

@ -0,0 +1,180 @@
/*
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 cli describes the operating environment for the Helm CLI.
Helm's environment encapsulates all of the service dependencies Helm has.
These dependencies are expressed as interfaces so that alternate implementations
(mocks, etc.) can be easily generated.
*/
package cli
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"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
config *genericclioptions.ConfigFlags
// KubeConfig is the path to the kubeconfig file
KubeConfig string
// KubeContext is the name of the kubeconfig context.
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
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
// RegistryConfig is the path to the registry config file.
RegistryConfig string
// RepositoryConfig is the path to the repositories file.
RepositoryConfig string
// RepositoryCache is the path to the repository cache directory.
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"),
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")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
}
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))
// bind to kubernetes config flags
env.config = &genericclioptions.ConfigFlags{
Namespace: &env.namespace,
Context: &env.KubeContext,
BearerToken: &env.KubeToken,
APIServer: &env.KubeAPIServer,
KubeConfig: &env.KubeConfig,
Impersonate: &env.KubeAsUser,
ImpersonateGroup: &env.KubeAsGroups,
}
return env
}
// AddFlags binds flags to the given flagset.
func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request")
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.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")
fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes")
}
func envOr(name, def string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
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],
"HELM_CACHE_HOME": helmpath.CachePath(""),
"HELM_CONFIG_HOME": helmpath.ConfigPath(""),
"HELM_DATA_HOME": helmpath.DataPath(""),
"HELM_DEBUG": fmt.Sprint(s.Debug),
"HELM_PLUGINS": s.PluginsDirectory,
"HELM_REGISTRY_CONFIG": s.RegistryConfig,
"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,
}
if s.KubeConfig != "" {
envvars["KUBECONFIG"] = s.KubeConfig
}
return envvars
}
// Namespace gets the namespace from the configuration
func (s *EnvSettings) Namespace() string {
if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil {
return ns
}
return "default"
}
// RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
return s.config
}

View File

@ -0,0 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["options.go"],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/cli/values",
importpath = "helm.sh/helm/v3/pkg/cli/values",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/getter:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/strvals:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -0,0 +1,121 @@
/*
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 values
import (
"io/ioutil"
"net/url"
"os"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/strvals"
)
type Options struct {
ValueFiles []string
StringValues []string
Values []string
FileValues []string
}
// MergeValues merges values from files specified via -f/--values and directly
// via --set, --set-string, or --set-file, marshaling them to YAML
func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range opts.ValueFiles {
currentMap := map[string]interface{}{}
bytes, err := readFile(filePath, p)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return nil, errors.Wrapf(err, "failed to parse %s", filePath)
}
// Merge with the previous map
base = mergeMaps(base, currentMap)
}
// User specified a value via --set
for _, value := range opts.Values {
if err := strvals.ParseInto(value, base); err != nil {
return nil, errors.Wrap(err, "failed parsing --set data")
}
}
// User specified a value via --set-string
for _, value := range opts.StringValues {
if err := strvals.ParseIntoString(value, base); err != nil {
return nil, errors.Wrap(err, "failed parsing --set-string data")
}
}
// User specified a value via --set-file
for _, value := range opts.FileValues {
reader := func(rs []rune) (interface{}, error) {
bytes, err := readFile(string(rs), p)
return string(bytes), err
}
if err := strvals.ParseIntoFile(value, base, reader); err != nil {
return nil, errors.Wrap(err, "failed parsing --set-file data")
}
}
return base, nil
}
func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
for k, v := range a {
out[k] = v
}
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]interface{}); ok {
out[k] = mergeMaps(bv, v)
continue
}
}
}
out[k] = v
}
return out
}
// readFile load a file from stdin, the local directory, or a remote file with a url.
func readFile(filePath string, p getter.Providers) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" {
return ioutil.ReadAll(os.Stdin)
}
u, _ := url.Parse(filePath)
// FIXME: maybe someone handle other protocols like ftp.
g, err := p.ByScheme(u.Scheme)
if err != nil {
return ioutil.ReadFile(filePath)
}
data, err := g.Get(filePath, getter.WithURL(filePath))
return data.Bytes(), err
}

View File

@ -0,0 +1,22 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"getter.go",
"httpgetter.go",
"plugingetter.go",
],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/getter",
importpath = "helm.sh/helm/v3/pkg/getter",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/helm.sh/helm/v3/internal/tlsutil:go_default_library",
"//vendor/helm.sh/helm/v3/internal/urlutil:go_default_library",
"//vendor/helm.sh/helm/v3/internal/version:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/cli:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/plugin:go_default_library",
],
)

View File

@ -0,0 +1,21 @@
/*
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 provides a generalize tool for fetching data by scheme.
This provides a method by which the plugin system can load arbitrary protocol
handlers based upon a URL scheme.
*/
package getter

View File

@ -0,0 +1,150 @@
/*
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"
"time"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/cli"
)
// options are generic parameters to be provided to the getter during instantiation.
//
// Getters may or may not ignore these parameters as they are passed in.
type options struct {
url string
certFile string
keyFile string
caFile string
insecureSkipVerifyTLS bool
username string
password string
userAgent string
timeout time.Duration
}
// Option allows specifying various settings configurable by the user for overriding the defaults
// used when performing Get operations with the Getter.
type Option func(*options)
// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with
// WithTLSClientConfig to set the TLSClientConfig's server name.
func WithURL(url string) Option {
return func(opts *options) {
opts.url = url
}
}
// WithBasicAuth sets the request's Authorization header to use the provided credentials
func WithBasicAuth(username, password string) Option {
return func(opts *options) {
opts.username = username
opts.password = password
}
}
// WithUserAgent sets the request's User-Agent header to use the provided agent name.
func WithUserAgent(userAgent string) Option {
return func(opts *options) {
opts.userAgent = userAgent
}
}
// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked
func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option {
return func(opts *options) {
opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS
}
}
// WithTLSClientConfig sets the client auth with the provided credentials.
func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
return func(opts *options) {
opts.certFile = certFile
opts.keyFile = keyFile
opts.caFile = caFile
}
}
// WithTimeout sets the timeout for requests
func WithTimeout(timeout time.Duration) Option {
return func(opts *options) {
opts.timeout = timeout
}
}
// Getter is an interface to support GET to the specified URL.
type Getter interface {
// Get file content by url string
Get(url string, options ...Option) (*bytes.Buffer, error)
}
// Constructor is the function for every getter which creates a specific instance
// according to the configuration
type Constructor func(options ...Option) (Getter, error)
// Provider represents any getter and the schemes that it supports.
//
// For example, an HTTP provider may provide one getter that handles both
// 'http' and 'https' schemes.
type Provider struct {
Schemes []string
New Constructor
}
// Provides returns true if the given scheme is supported by this Provider.
func (p Provider) Provides(scheme string) bool {
for _, i := range p.Schemes {
if i == scheme {
return true
}
}
return false
}
// Providers is a collection of Provider objects.
type Providers []Provider
// ByScheme returns a Provider that handles the given scheme.
//
// If no provider handles this scheme, this will return an error.
func (p Providers) ByScheme(scheme string) (Getter, error) {
for _, pp := range p {
if pp.Provides(scheme) {
return pp.New()
}
}
return nil, errors.Errorf("scheme %q not supported", scheme)
}
var httpProvider = Provider{
Schemes: []string{"http", "https"},
New: NewHTTPGetter,
}
// 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}
pluginDownloaders, _ := collectPlugins(settings)
result = append(result, pluginDownloaders...)
return result
}

View File

@ -0,0 +1,126 @@
/*
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"
"crypto/tls"
"io"
"net/http"
"github.com/pkg/errors"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/internal/version"
)
// HTTPGetter is the default HTTP(/S) backend handler
type HTTPGetter struct {
opts options
}
//Get performs a Get from repo.Getter and returns the body.
func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&g.opts)
}
return g.get(href)
}
func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
// Set a helm specific user agent so that a repo server and metrics can
// separate helm calls from other tools interacting with repos.
req, err := http.NewRequest("GET", href, nil)
if err != nil {
return buf, err
}
req.Header.Set("User-Agent", version.GetUserAgent())
if g.opts.userAgent != "" {
req.Header.Set("User-Agent", g.opts.userAgent)
}
if g.opts.username != "" && g.opts.password != "" {
req.SetBasicAuth(g.opts.username, g.opts.password)
}
client, err := g.httpClient()
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return buf, err
}
if resp.StatusCode != 200 {
return buf, errors.Errorf("failed to fetch %s : %s", href, resp.Status)
}
_, err = io.Copy(buf, resp.Body)
resp.Body.Close()
return buf, err
}
// NewHTTPGetter constructs a valid http/https client as a Getter
func NewHTTPGetter(options ...Option) (Getter, error) {
var client HTTPGetter
for _, opt := range options {
opt(&client.opts)
}
return &client, nil
}
func (g *HTTPGetter) httpClient() (*http.Client, error) {
transport := &http.Transport{
DisableCompression: true,
Proxy: http.ProxyFromEnvironment,
}
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
if err != nil {
return nil, errors.Wrap(err, "can't create TLS config for client")
}
tlsConf.BuildNameToCertificate()
sni, err := urlutil.ExtractHostname(g.opts.url)
if err != nil {
return nil, err
}
tlsConf.ServerName = sni
transport.TLSClientConfig = tlsConf
}
if g.opts.insecureSkipVerifyTLS {
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
client := &http.Client{
Transport: transport,
Timeout: g.opts.timeout,
}
return client, nil
}

View File

@ -0,0 +1,102 @@
/*
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"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/plugin"
)
// collectPlugins scans for getter plugins.
// This will load plugins according to the cli.
func collectPlugins(settings *cli.EnvSettings) (Providers, error) {
plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
return nil, err
}
var result Providers
for _, plugin := range plugins {
for _, downloader := range plugin.Metadata.Downloaders {
result = append(result, Provider{
Schemes: downloader.Protocols,
New: NewPluginGetter(
downloader.Command,
settings,
plugin.Metadata.Name,
plugin.Dir,
),
})
}
}
return result, nil
}
// pluginGetter is a generic type to invoke custom downloaders,
// implemented in plugins.
type pluginGetter struct {
command string
settings *cli.EnvSettings
name string
base string
opts options
}
// Get runs downloader plugin command
func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
for _, opt := range options {
opt(&p.opts)
}
commands := strings.Split(p.command, " ")
argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href)
prog := exec.Command(filepath.Join(p.base, commands[0]), argv...)
plugin.SetupPluginEnv(p.settings, p.name, p.base)
prog.Env = os.Environ()
buf := bytes.NewBuffer(nil)
prog.Stdout = buf
prog.Stderr = os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
return nil, errors.Errorf("plugin %q exited with error", p.command)
}
return nil, err
}
return buf, nil
}
// NewPluginGetter constructs a valid plugin getter
func NewPluginGetter(command string, settings *cli.EnvSettings, name, base string) Constructor {
return func(options ...Option) (Getter, error) {
result := &pluginGetter{
command: command,
settings: settings,
name: name,
base: base,
}
for _, opt := range options {
opt(&result.opts)
}
return result, nil
}
}

View File

@ -0,0 +1,53 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"home.go",
"lazypath.go",
"lazypath_darwin.go",
"lazypath_unix.go",
"lazypath_windows.go",
],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/helmpath",
importpath = "helm.sh/helm/v3/pkg/helmpath",
visibility = ["//visibility:public"],
deps = [
"//vendor/helm.sh/helm/v3/pkg/helmpath/xdg:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:ios": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:nacl": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:plan9": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"@io_bazel_rules_go//go/platform:solaris": [
"//vendor/k8s.io/client-go/util/homedir:go_default_library",
],
"//conditions:default": [],
}),
)

View 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 helmpath calculates filesystem paths to Helm's configuration, cache and data.
package helmpath
// This helper builds paths to Helm's configuration, cache and data paths.
const lp = lazypath("helm")
// ConfigPath returns the path where Helm stores configuration.
func ConfigPath(elem ...string) string { return lp.configPath(elem...) }
// CachePath returns the path where Helm stores cached objects.
func CachePath(elem ...string) string { return lp.cachePath(elem...) }
// DataPath returns the path where Helm stores data.
func DataPath(elem ...string) string { return lp.dataPath(elem...) }
// CacheIndexFile returns the path to an index for the given named repository.
func CacheIndexFile(name string) string {
if name != "" {
name += "-"
}
return name + "index.yaml"
}
// CacheChartsFile returns the path to a text file listing all the charts
// within the given named repository.
func CacheChartsFile(name string) string {
if name != "" {
name += "-"
}
return name + "charts.txt"
}

View File

@ -0,0 +1,72 @@
// 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 helmpath
import (
"os"
"path/filepath"
"helm.sh/helm/v3/pkg/helmpath/xdg"
)
const (
// CacheHomeEnvVar is the environment variable used by Helm
// for the cache directory. When no value is set a default is used.
CacheHomeEnvVar = "HELM_CACHE_HOME"
// ConfigHomeEnvVar is the environment variable used by Helm
// for the config directory. When no value is set a default is used.
ConfigHomeEnvVar = "HELM_CONFIG_HOME"
// DataHomeEnvVar is the environment variable used by Helm
// for the data directory. When no value is set a default is used.
DataHomeEnvVar = "HELM_DATA_HOME"
)
// lazypath is an lazy-loaded path buffer for the XDG base directory specification.
type lazypath string
func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
// There is an order to checking for a path.
// 1. See if a Helm specific environment variable has been set.
// 2. Check if an XDG environment variable is set
// 3. Fall back to a default
base := os.Getenv(helmEnvVar)
if base != "" {
return filepath.Join(base, filepath.Join(elem...))
}
base = os.Getenv(xdgEnvVar)
if base == "" {
base = defaultFn()
}
return filepath.Join(base, string(l), filepath.Join(elem...))
}
// cachePath defines the base directory relative to which user specific non-essential data files
// should be stored.
func (l lazypath) cachePath(elem ...string) string {
return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
}
// configPath defines the base directory relative to which user specific configuration files should
// be stored.
func (l lazypath) configPath(elem ...string) string {
return l.path(ConfigHomeEnvVar, xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...))
}
// dataPath defines the base directory relative to which user specific data files should be stored.
func (l lazypath) dataPath(elem ...string) string {
return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
}

View 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.
// +build darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
func dataHome() string {
return filepath.Join(homedir.HomeDir(), "Library")
}
func configHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Preferences")
}
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), "Library", "Caches")
}

View File

@ -0,0 +1,45 @@
// 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.
// +build !windows,!darwin
package helmpath
import (
"path/filepath"
"k8s.io/client-go/util/homedir"
)
// dataHome defines the base directory relative to which user specific data files should be stored.
//
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share is used.
func dataHome() string {
return filepath.Join(homedir.HomeDir(), ".local", "share")
}
// configHome defines the base directory relative to which user specific configuration files should
// be stored.
//
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config is used.
func configHome() string {
return filepath.Join(homedir.HomeDir(), ".config")
}
// cacheHome defines the base directory relative to which user specific non-essential data files
// should be stored.
//
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache is used.
func cacheHome() string {
return filepath.Join(homedir.HomeDir(), ".cache")
}

View File

@ -0,0 +1,24 @@
// 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.
// +build windows
package helmpath
import "os"
func dataHome() string { return configHome() }
func configHome() string { return os.Getenv("APPDATA") }
func cacheHome() string { return os.Getenv("TEMP") }

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["xdg.go"],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/helmpath/xdg",
importpath = "helm.sh/helm/v3/pkg/helmpath/xdg",
visibility = ["//visibility:public"],
)

View 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 xdg holds constants pertaining to XDG Base Directory Specification.
//
// The XDG Base Directory Specification https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// specifies the environment variables that define user-specific base directories for various categories of files.
package xdg
const (
// CacheHomeEnvVar is the environment variable used by the
// XDG base directory specification for the cache directory.
CacheHomeEnvVar = "XDG_CACHE_HOME"
// ConfigHomeEnvVar is the environment variable used by the
// XDG base directory specification for the config directory.
ConfigHomeEnvVar = "XDG_CONFIG_HOME"
// DataHomeEnvVar is the environment variable used by the
// XDG base directory specification for the data directory.
DataHomeEnvVar = "XDG_DATA_HOME"
)

View File

@ -0,0 +1,17 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"hooks.go",
"plugin.go",
],
importmap = "k8s.io/kops/vendor/helm.sh/helm/v3/pkg/plugin",
importpath = "helm.sh/helm/v3/pkg/plugin",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/helm.sh/helm/v3/pkg/cli:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -0,0 +1,29 @@
/*
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 plugin // import "helm.sh/helm/v3/pkg/plugin"
// Types of hooks
const (
// Install is executed after the plugin is added.
Install = "install"
// Delete is executed after the plugin is removed.
Delete = "delete"
// Update is executed after the plugin is updated.
Update = "update"
)
// Hooks is a map of events to commands.
type Hooks map[string]string

View File

@ -0,0 +1,266 @@
/*
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 plugin // import "helm.sh/helm/v3/pkg/plugin"
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/cli"
)
const PluginFileName = "plugin.yaml"
// Downloaders represents the plugins capability if it can retrieve
// charts from special sources
type Downloaders struct {
// Protocols are the list of schemes from the charts URL.
Protocols []string `json:"protocols"`
// Command is the executable path with which the plugin performs
// the actual download for the corresponding Protocols
Command string `json:"command"`
}
// PlatformCommand represents a command for a particular operating system and architecture
type PlatformCommand struct {
OperatingSystem string `json:"os"`
Architecture string `json:"arch"`
Command string `json:"command"`
}
// Metadata describes a plugin.
//
// This is the plugin equivalent of a chart.Metadata.
type Metadata struct {
// Name is the name of the plugin
Name string `json:"name"`
// Version is a SemVer 2 version of the plugin.
Version string `json:"version"`
// Usage is the single-line usage text shown in help
Usage string `json:"usage"`
// Description is a long description shown in places like `helm help`
Description string `json:"description"`
// Command is the command, as a single string.
//
// The command will be passed through environment expansion, so env vars can
// be present in this command. Unless IgnoreFlags is set, this will
// also merge the flags passed from Helm.
//
// Note that command is not executed in a shell. To do so, we suggest
// pointing the command to a shell script.
//
// The following rules will apply to processing commands:
// - If platformCommand is present, it will be searched first
// - If both OS and Arch match the current platform, search will stop and the command will be executed
// - If OS matches and there is no more specific match, the command will be executed
// - If no OS/Arch match is found, the default command will be executed
// - If no command is present and no matches are found in platformCommand, Helm will exit with an error
PlatformCommand []PlatformCommand `json:"platformCommand"`
Command string `json:"command"`
// IgnoreFlags ignores any flags passed in from Helm
//
// For example, if the plugin is invoked as `helm --debug myplugin`, if this
// is false, `--debug` will be appended to `--command`. If this is true,
// the `--debug` flag will be discarded.
IgnoreFlags bool `json:"ignoreFlags"`
// Hooks are commands that will run on events.
Hooks Hooks
// 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.
type Plugin struct {
// Metadata is a parsed representation of a plugin.yaml
Metadata *Metadata
// Dir is the string path to the directory that holds the plugin.
Dir string
}
// The following rules will apply to processing the Plugin.PlatformCommand.Command:
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
// - If OS matches and there is no more specific match, the command will be prepared for execution
// - If no OS/Arch match is found, return nil
func getPlatformCommand(cmds []PlatformCommand) []string {
var command []string
eq := strings.EqualFold
for _, c := range cmds {
if eq(c.OperatingSystem, runtime.GOOS) {
command = strings.Split(os.ExpandEnv(c.Command), " ")
}
if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) {
return strings.Split(os.ExpandEnv(c.Command), " ")
}
}
return command
}
// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing:
// - If platformCommand is present, it will be searched first
// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution
// - If OS matches and there is no more specific match, the command will be prepared for execution
// - If no OS/Arch match is found, the default command will be prepared for execution
// - If no command is present and no matches are found in platformCommand, will exit with an error
//
// It merges extraArgs into any arguments supplied in the plugin. It
// returns the name of the command and an args array.
//
// The result is suitable to pass to exec.Command.
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
var parts []string
platCmdLen := len(p.Metadata.PlatformCommand)
if platCmdLen > 0 {
parts = getPlatformCommand(p.Metadata.PlatformCommand)
}
if platCmdLen == 0 || parts == nil {
parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
}
if len(parts) == 0 || parts[0] == "" {
return "", nil, fmt.Errorf("No plugin command is applicable")
}
main := parts[0]
baseArgs := []string{}
if len(parts) > 1 {
baseArgs = parts[1:]
}
if !p.Metadata.IgnoreFlags {
baseArgs = append(baseArgs, extraArgs...)
}
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) {
pluginfile := filepath.Join(dirname, PluginFileName)
data, err := ioutil.ReadFile(pluginfile)
if err != nil {
return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile)
}
plug := &Plugin{Dir: dirname}
if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil {
return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile)
}
return plug, validatePluginData(plug, pluginfile)
}
// LoadAll loads all plugins found beneath the base directory.
//
// This scans only one directory level.
func LoadAll(basedir string) ([]*Plugin, error) {
plugins := []*Plugin{}
// We want basedir/*/plugin.yaml
scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath)
if err != nil {
return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath)
}
if matches == nil {
return plugins, nil
}
for _, yaml := range matches {
dir := filepath.Dir(yaml)
p, err := LoadDir(dir)
if err != nil {
return plugins, err
}
plugins = append(plugins, p)
}
return plugins, detectDuplicates(plugins)
}
// FindPlugins returns a list of YAML files that describe plugins.
func FindPlugins(plugdirs string) ([]*Plugin, error) {
found := []*Plugin{}
// Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) {
matches, err := LoadAll(p)
if err != nil {
return matches, err
}
found = append(found, matches...)
}
return found, nil
}
// SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because
// the plugin subsystem itself needs access to the environment variables
// created here.
func SetupPluginEnv(settings *cli.EnvSettings, name, base string) {
env := settings.EnvVars()
env["HELM_PLUGIN_NAME"] = name
env["HELM_PLUGIN_DIR"] = base
for key, val := range env {
os.Setenv(key, val)
}
}

9
vendor/modules.txt vendored
View File

@ -816,6 +816,15 @@ gopkg.in/yaml.v2
gopkg.in/yaml.v3
# helm.sh/helm/v3 v3.4.2
## explicit
helm.sh/helm/v3/internal/tlsutil
helm.sh/helm/v3/internal/urlutil
helm.sh/helm/v3/internal/version
helm.sh/helm/v3/pkg/cli
helm.sh/helm/v3/pkg/cli/values
helm.sh/helm/v3/pkg/getter
helm.sh/helm/v3/pkg/helmpath
helm.sh/helm/v3/pkg/helmpath/xdg
helm.sh/helm/v3/pkg/plugin
helm.sh/helm/v3/pkg/strvals
# honnef.co/go/tools v0.0.1-2020.1.3
honnef.co/go/tools/arg