Merge remote-tracking branch 'helm/master' into patch--upgrade-using-force

pull/8000/head
Liu Ming 5 years ago
commit 109468efa9

@ -45,19 +45,19 @@ Environment variables:
| Name | Description | | Name | Description |
|------------------------------------|-----------------------------------------------------------------------------------| |------------------------------------|-----------------------------------------------------------------------------------|
| $XDG_CACHE_HOME | set an alternative location for storing cached files. | | $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $XDG_CONFIG_HOME | set an alternative location for storing Helm configuration. | | $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $XDG_DATA_HOME | set an alternative location for storing Helm data. | | $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres | | $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | | $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | | $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
Helm stores configuration based on the XDG base directory specification, so Helm stores cache, configuration, and data based on the following configuration order:
- cached files are stored in $XDG_CACHE_HOME/helm - If a HELM_*_HOME environment variable is set, it will be used
- configuration is stored in $XDG_CONFIG_HOME/helm - Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
- data is stored in $XDG_DATA_HOME/helm - When no other location is set a default location will be used based on the operating system
By default, the default directories depend on the Operating System. The defaults are listed below: By default, the default directories depend on the Operating System. The defaults are listed below:

@ -55,6 +55,24 @@ func TestRootCmd(t *testing.T) {
envvars: map[string]string{xdg.DataHomeEnvVar: "/bar"}, envvars: map[string]string{xdg.DataHomeEnvVar: "/bar"},
dataPath: "/bar/helm", dataPath: "/bar/helm",
}, },
{
name: "with $HELM_CACHE_HOME set",
args: "env",
envvars: map[string]string{helmpath.CacheHomeEnvVar: "/foo/helm"},
cachePath: "/foo/helm",
},
{
name: "with $HELM_CONFIG_HOME set",
args: "env",
envvars: map[string]string{helmpath.ConfigHomeEnvVar: "/foo/helm"},
configPath: "/foo/helm",
},
{
name: "with $HELM_DATA_HOME set",
args: "env",
envvars: map[string]string{helmpath.DataHomeEnvVar: "/foo/helm"},
dataPath: "/foo/helm",
},
} }
for _, tt := range tests { for _, tt := range tests {

@ -1 +1 @@
version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
Version: v3.1 Version: v3.2

@ -1 +1 @@
version.BuildInfo{Version:"v3.1", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.2", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -21,6 +21,7 @@ import (
"os" "os"
"testing" "testing"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg" "helm.sh/helm/v3/pkg/helmpath/xdg"
) )
@ -31,6 +32,9 @@ func HelmHome(t *testing.T) func() {
os.Setenv(xdg.CacheHomeEnvVar, base) os.Setenv(xdg.CacheHomeEnvVar, base)
os.Setenv(xdg.ConfigHomeEnvVar, base) os.Setenv(xdg.ConfigHomeEnvVar, base)
os.Setenv(xdg.DataHomeEnvVar, base) os.Setenv(xdg.DataHomeEnvVar, base)
os.Setenv(helmpath.CacheHomeEnvVar, "")
os.Setenv(helmpath.ConfigHomeEnvVar, "")
os.Setenv(helmpath.DataHomeEnvVar, "")
return func() { return func() {
os.RemoveAll(base) os.RemoveAll(base)
} }

@ -30,7 +30,7 @@ var (
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
// Increment patch number for critical fixes to existing releases. // Increment patch number for critical fixes to existing releases.
version = "v3.1" version = "v3.2"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""

@ -62,16 +62,17 @@ var (
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") 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")
) )
// ValidName is a regular expression for names. // ValidName is a regular expression for resource names.
// //
// According to the Kubernetes help text, the regular expression it uses is: // According to the Kubernetes help text, the regular expression it uses is:
// //
// (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])? // [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
// //
// We modified that. First, we added start and end delimiters. Second, we changed // This follows the above regular expression (but requires a full string match, not partial).
// the final ? to + to require that the pattern match at least once. This modification //
// prevents an empty string from matching. // The Kubernetes documentation is here, though it is not entirely correct:
var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$") // 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])?)*$`)
// Configuration injects the dependencies that all actions share. // Configuration injects the dependencies that all actions share.
type Configuration struct { type Configuration struct {

@ -316,3 +316,40 @@ func TestGetVersionSet(t *testing.T) {
t.Error("Non-existent version is reported found.") t.Error("Non-existent version is reported found.")
} }
} }
// TestValidName is a regression test for ValidName
//
// Kubernetes has strict naming conventions for resource names. This test represents
// those conventions.
//
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
//
// NOTE: At the time of this writing, the docs above say that names cannot begin with
// digits. However, `kubectl`'s regular expression explicit allows this, and
// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits.
func TestValidName(t *testing.T) {
names := map[string]bool{
"": false,
"foo": true,
"foo.bar1234baz.seventyone": true,
"FOO": false,
"123baz": true,
"foo.BAR.baz": false,
"one-two": true,
"-two": false,
"one_two": false,
"a..b": false,
"%^&#$%*@^*@&#^": false,
"example:com": false,
"example%%com": false,
}
for input, expectPass := range names {
if ValidName.MatchString(input) != expectPass {
st := "fail"
if expectPass {
st = "succeed"
}
t.Errorf("Expected %q to %s", input, st)
}
}
}

@ -34,6 +34,9 @@ import (
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// SaveDir saves a chart as files in a directory. // 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 { func SaveDir(c *chart.Chart, dest string) error {
// Create the chart directory // Create the chart directory
outdir := filepath.Join(dest, c.Name()) outdir := filepath.Join(dest, c.Name())

@ -105,6 +105,9 @@ func envOr(name, def string) string {
func (s *EnvSettings) EnvVars() map[string]string { func (s *EnvSettings) EnvVars() map[string]string {
envvars := map[string]string{ envvars := map[string]string{
"HELM_BIN": os.Args[0], "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_DEBUG": fmt.Sprint(s.Debug),
"HELM_PLUGINS": s.PluginsDirectory, "HELM_PLUGINS": s.PluginsDirectory,
"HELM_REGISTRY_CONFIG": s.RegistryConfig, "HELM_REGISTRY_CONFIG": s.RegistryConfig,

@ -20,11 +20,34 @@ import (
"helm.sh/helm/v3/pkg/helmpath/xdg" "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. // lazypath is an lazy-loaded path buffer for the XDG base directory specification.
type lazypath string type lazypath string
func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) string { func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string {
base := os.Getenv(envVar)
// 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 == "" { if base == "" {
base = defaultFn() base = defaultFn()
} }
@ -34,16 +57,16 @@ func (l lazypath) path(envVar string, defaultFn func() string, elem ...string) s
// cachePath defines the base directory relative to which user specific non-essential data files // cachePath defines the base directory relative to which user specific non-essential data files
// should be stored. // should be stored.
func (l lazypath) cachePath(elem ...string) string { func (l lazypath) cachePath(elem ...string) string {
return l.path(xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...)) return l.path(CacheHomeEnvVar, xdg.CacheHomeEnvVar, cacheHome, filepath.Join(elem...))
} }
// configPath defines the base directory relative to which user specific configuration files should // configPath defines the base directory relative to which user specific configuration files should
// be stored. // be stored.
func (l lazypath) configPath(elem ...string) string { func (l lazypath) configPath(elem ...string) string {
return l.path(xdg.ConfigHomeEnvVar, configHome, filepath.Join(elem...)) 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. // dataPath defines the base directory relative to which user specific data files should be stored.
func (l lazypath) dataPath(elem ...string) string { func (l lazypath) dataPath(elem ...string) string {
return l.path(xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...)) return l.path(DataHomeEnvVar, xdg.DataHomeEnvVar, dataHome, filepath.Join(elem...))
} }

@ -0,0 +1,64 @@
/*
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 rules // import "helm.sh/helm/v3/pkg/lint/rules"
import "fmt"
// deprecatedAPIs lists APIs that are deprecated (left) with suggested alternatives (right).
//
// An empty rvalue indicates that the API is completely deprecated.
var deprecatedAPIs = map[string]string{
"extensions/v1 Deployment": "apps/v1 Deployment",
"extensions/v1 DaemonSet": "apps/v1 DaemonSet",
"extensions/v1 ReplicaSet": "apps/v1 ReplicaSet",
"extensions/v1beta1 PodSecurityPolicy": "policy/v1beta1 PodSecurityPolicy",
"extensions/v1beta1 NetworkPolicy": "networking.k8s.io/v1beta1 NetworkPolicy",
"extensions/v1beta1 Ingress": "networking.k8s.io/v1beta1 Ingress",
"apps/v1beta1 Deployment": "apps/v1 Deployment",
"apps/v1beta1 StatefulSet": "apps/v1 StatefulSet",
"apps/v1beta1 DaemonSet": "apps/v1 DaemonSet",
"apps/v1beta1 ReplicaSet": "apps/v1 ReplicaSet",
"apps/v1beta2 Deployment": "apps/v1 Deployment",
"apps/v1beta2 StatefulSet": "apps/v1 StatefulSet",
"apps/v1beta2 DaemonSet": "apps/v1 DaemonSet",
"apps/v1beta2 ReplicaSet": "apps/v1 ReplicaSet",
}
// deprecatedAPIError indicates than an API is deprecated in Kubernetes
type deprecatedAPIError struct {
Deprecated string
Alternative string
}
func (e deprecatedAPIError) Error() string {
msg := fmt.Sprintf("the kind %q is deprecated", e.Deprecated)
if e.Alternative != "" {
msg += fmt.Sprintf(" in favor of %q", e.Alternative)
}
return msg
}
func validateNoDeprecations(resource *K8sYamlStruct) error {
gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind)
if alt, ok := deprecatedAPIs[gvk]; ok {
return deprecatedAPIError{
Deprecated: gvk,
Alternative: alt,
}
}
return nil
}

@ -0,0 +1,42 @@
/*
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 rules // import "helm.sh/helm/v3/pkg/lint/rules"
import "testing"
func TestValidateNoDeprecations(t *testing.T) {
deprecated := &K8sYamlStruct{
APIVersion: "extensions/v1",
Kind: "Deployment",
}
err := validateNoDeprecations(deprecated)
if err == nil {
t.Fatal("Expected deprecated extension to be flagged")
}
depErr := err.(deprecatedAPIError)
if depErr.Alternative != "apps/v1 Deployment" {
t.Errorf("Expected %q to be replaced by %q", depErr.Deprecated, depErr.Alternative)
}
if err := validateNoDeprecations(&K8sYamlStruct{
APIVersion: "v1",
Kind: "Pod",
}); err != nil {
t.Errorf("Expected a v1 Pod to not be deprecated")
}
}

@ -81,7 +81,6 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
return return
} }
var e engine.Engine var e engine.Engine
e.Strict = strict
e.LintMode = true e.LintMode = true
renderedContentMap, err := e.Render(chart, valuesToRender) renderedContentMap, err := e.Render(chart, valuesToRender)
@ -131,6 +130,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
// on this linter run. // on this linter run.
linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
linter.RunLinterRule(support.ErrorSev, path, validateMetadataName(&yamlStruct)) linter.RunLinterRule(support.ErrorSev, path, validateMetadataName(&yamlStruct))
linter.RunLinterRule(support.ErrorSev, path, validateNoDeprecations(&yamlStruct))
} }
} }
} }
@ -190,7 +190,9 @@ func validateNoReleaseTime(manifest []byte) error {
// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within // DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within
// the rules package. // the rules package.
type K8sYamlStruct struct { type K8sYamlStruct struct {
Metadata k8sYamlMetadata APIVersion string `json:"apiVersion"`
Kind string
Metadata k8sYamlMetadata
} }
type k8sYamlMetadata struct { type k8sYamlMetadata struct {

@ -22,6 +22,9 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/lint/support" "helm.sh/helm/v3/pkg/lint/support"
) )
@ -130,3 +133,96 @@ func TestValidateMetadataName(t *testing.T) {
} }
} }
} }
func TestDeprecatedAPIFails(t *testing.T) {
mychart := chart.Chart{
Metadata: &chart.Metadata{
APIVersion: "v2",
Name: "failapi",
Version: "0.1.0",
Icon: "satisfy-the-linting-gods.gif",
},
Templates: []*chart.File{
{
Name: "templates/baddeployment.yaml",
Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep"),
},
{
Name: "templates/goodsecret.yaml",
Data: []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: goodsecret"),
},
},
}
tmpdir := ensure.TempDir(t)
defer os.RemoveAll(tmpdir)
if err := chartutil.SaveDir(&mychart, tmpdir); err != nil {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 {
for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg)
}
t.Fatalf("Expected 1 lint error, got %d", l)
}
err := linter.Messages[0].Err.(deprecatedAPIError)
if err.Deprecated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
}
}
const manifest = `apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
myval1: {{default "val" .Values.mymap.key1 }}
myval2: {{default "val" .Values.mymap.key2 }}
`
// TestSTrictTemplatePrasingMapError is a regression test.
//
// The template engine should not produce an error when a map in values.yaml does
// not contain all possible keys.
//
// See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) {
ch := chart.Chart{
Metadata: &chart.Metadata{
Name: "regression7483",
APIVersion: "v2",
Version: "0.1.0",
},
Values: map[string]interface{}{
"mymap": map[string]string{
"key1": "val1",
},
},
Templates: []*chart.File{
{
Name: "templates/configmap.yaml",
Data: []byte(manifest),
},
},
}
dir := ensure.TempDir(t)
defer os.RemoveAll(dir)
if err := chartutil.SaveDir(&ch, dir); err != nil {
t.Fatal(err)
}
linter := &support.Linter{
ChartDir: filepath.Join(dir, ch.Metadata.Name),
}
Templates(linter, ch.Values, namespace, strict)
if len(linter.Messages) != 0 {
t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages {
t.Logf("Message %d: %q", i, msg)
}
}
}

@ -23,6 +23,8 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/pkg/plugin"
) )
// ErrMissingMetadata indicates that plugin.yaml is missing. // ErrMissingMetadata indicates that plugin.yaml is missing.
@ -100,7 +102,7 @@ func isRemoteHTTPArchive(source string) bool {
// isPlugin checks if the directory contains a plugin.yaml file. // isPlugin checks if the directory contains a plugin.yaml file.
func isPlugin(dirname string) bool { func isPlugin(dirname string) bool {
_, err := os.Stat(filepath.Join(dirname, "plugin.yaml")) _, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName))
return err == nil return err == nil
} }

@ -28,7 +28,7 @@ import (
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
) )
const pluginFileName = "plugin.yaml" const PluginFileName = "plugin.yaml"
// Downloaders represents the plugins capability if it can retrieve // Downloaders represents the plugins capability if it can retrieve
// charts from special sources // charts from special sources
@ -159,7 +159,7 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
// LoadDir loads a plugin from the given directory. // LoadDir loads a plugin from the given directory.
func LoadDir(dirname string) (*Plugin, error) { func LoadDir(dirname string) (*Plugin, error) {
data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName)) data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -177,7 +177,7 @@ func LoadDir(dirname string) (*Plugin, error) {
func LoadAll(basedir string) ([]*Plugin, error) { func LoadAll(basedir string) ([]*Plugin, error) {
plugins := []*Plugin{} plugins := []*Plugin{}
// We want basedir/*/plugin.yaml // We want basedir/*/plugin.yaml
scanpath := filepath.Join(basedir, "*", pluginFileName) scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath) matches, err := filepath.Glob(scanpath)
if err != nil { if err != nil {
return plugins, err return plugins, err

Loading…
Cancel
Save