Merge remote-tracking branch 'helm/master'

pull/8034/head
Liu Ming 5 years ago
commit 029922202a

@ -43,9 +43,7 @@ import (
// FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work // FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI") const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
var ( var settings = cli.New()
settings = cli.New()
)
func init() { func init() {
log.SetFlags(log.Lshortfile) log.SetFlags(log.Lshortfile)
@ -72,6 +70,8 @@ func main() {
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:]) cmd := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
// run when each command's execute method is called
cobra.OnInitialize(func() {
helmDriver := os.Getenv("HELM_DRIVER") helmDriver := os.Getenv("HELM_DRIVER")
if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil { if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, debug); err != nil {
log.Fatal(err) log.Fatal(err)
@ -79,6 +79,7 @@ func main() {
if helmDriver == "memory" { if helmDriver == "memory" {
loadReleasesInMemory(actionConfig) loadReleasesInMemory(actionConfig)
} }
})
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
debug("%+v", err) debug("%+v", err)

@ -0,0 +1,51 @@
/*
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 fileutil
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"helm.sh/helm/v3/internal/third_party/dep/fs"
)
// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk.
func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename))
if err != nil {
return err
}
tempName := tempFile.Name()
if _, err := io.Copy(tempFile, reader); err != nil {
tempFile.Close() // return value is ignored as we are already on error path
return err
}
if err := tempFile.Close(); err != nil {
return err
}
if err := os.Chmod(tempName, mode); err != nil {
return err
}
return fs.RenameWithFallback(tempName, filename)
}

@ -0,0 +1,62 @@
/*
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 fileutil
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestAtomicWriteFile(t *testing.T) {
dir, err := ioutil.TempDir("", "helm-tmp")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testpath := filepath.Join(dir, "test")
stringContent := "Test content"
reader := bytes.NewReader([]byte(stringContent))
mode := os.FileMode(0644)
err = AtomicWriteFile(testpath, reader, mode)
if err != nil {
t.Errorf("AtomicWriteFile error: %s", err)
}
got, err := ioutil.ReadFile(testpath)
if err != nil {
t.Fatal(err)
}
if stringContent != string(got) {
t.Fatalf("expected: %s, got: %s", stringContent, string(got))
}
gotinfo, err := os.Stat(testpath)
if err != nil {
t.Fatal(err)
}
if mode != gotinfo.Mode() {
t.Fatalf("expected %s: to be the same mode as %s",
mode, gotinfo.Mode())
}
}

@ -26,21 +26,17 @@ import (
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"sync"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/kube"
) )
// EnvSettings describes all of the environment settings. // EnvSettings describes all of the environment settings.
type EnvSettings struct { type EnvSettings struct {
namespace string namespace string
config genericclioptions.RESTClientGetter config *genericclioptions.ConfigFlags
configOnce sync.Once
// KubeConfig is the path to the kubeconfig file // KubeConfig is the path to the kubeconfig file
KubeConfig string KubeConfig string
@ -63,8 +59,7 @@ type EnvSettings struct {
} }
func New() *EnvSettings { func New() *EnvSettings {
env := &EnvSettings{
env := EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"), namespace: os.Getenv("HELM_NAMESPACE"),
KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"), KubeToken: os.Getenv("HELM_KUBETOKEN"),
@ -75,7 +70,16 @@ func New() *EnvSettings {
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
} }
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))
return &env
// bind to kubernetes config flags
env.config = &genericclioptions.ConfigFlags{
Namespace: &env.namespace,
Context: &env.KubeContext,
BearerToken: &env.KubeToken,
APIServer: &env.KubeAPIServer,
KubeConfig: &env.KubeConfig,
}
return env
} }
// AddFlags binds flags to the given flagset. // AddFlags binds flags to the given flagset.
@ -107,25 +111,21 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_REPOSITORY_CACHE": s.RepositoryCache, "HELM_REPOSITORY_CACHE": s.RepositoryCache,
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(), "HELM_NAMESPACE": s.Namespace(),
// broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext, "HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken, "HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEAPISERVER": s.KubeAPIServer, "HELM_KUBEAPISERVER": s.KubeAPIServer,
} }
if s.KubeConfig != "" { if s.KubeConfig != "" {
envvars["KUBECONFIG"] = s.KubeConfig envvars["KUBECONFIG"] = s.KubeConfig
} }
return envvars return envvars
} }
// Namespace gets the namespace from the configuration // Namespace gets the namespace from the configuration
func (s *EnvSettings) Namespace() string { func (s *EnvSettings) Namespace() string {
if s.namespace != "" { if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil {
return s.namespace
}
if ns, _, err := s.RESTClientGetter().ToRawKubeConfigLoader().Namespace(); err == nil {
return ns return ns
} }
return "default" return "default"
@ -133,16 +133,5 @@ func (s *EnvSettings) Namespace() string {
// RESTClientGetter gets the kubeconfig from EnvSettings // RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
s.configOnce.Do(func() {
clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace)
if s.KubeToken != "" {
clientConfig.BearerToken = &s.KubeToken
}
if s.KubeAPIServer != "" {
clientConfig.APIServer = &s.KubeAPIServer
}
s.config = clientConfig
})
return s.config return s.config
} }

@ -18,7 +18,6 @@ package downloader
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -26,6 +25,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
@ -72,31 +72,6 @@ type ChartDownloader struct {
RepositoryCache string RepositoryCache string
} }
// atomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk.
func atomicWriteFile(filename string, body io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename))
if err != nil {
return err
}
tempName := tempFile.Name()
if _, err := io.Copy(tempFile, body); err != nil {
tempFile.Close() // return value is ignored as we are already on error path
return err
}
if err := tempFile.Close(); err != nil {
return err
}
if err := os.Chmod(tempName, mode); err != nil {
return err
}
return os.Rename(tempName, filename)
}
// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file.
// //
// If Verify is set to VerifyNever, the verification will be nil. // If Verify is set to VerifyNever, the verification will be nil.
@ -126,7 +101,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
name := filepath.Base(u.Path) name := filepath.Base(u.Path)
destfile := filepath.Join(dest, name) destfile := filepath.Join(dest, name)
if err := atomicWriteFile(destfile, data, 0644); err != nil { if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil {
return destfile, nil, err return destfile, nil, err
} }
@ -142,7 +117,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, nil return destfile, ver, nil
} }
provfile := destfile + ".prov" provfile := destfile + ".prov"
if err := atomicWriteFile(provfile, body, 0644); err != nil { if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil {
return destfile, nil, err return destfile, nil, err
} }

@ -53,9 +53,10 @@ func TestProviders(t *testing.T) {
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
all := All(&cli.EnvSettings{ env := cli.New()
PluginsDirectory: pluginDir, env.PluginsDirectory = pluginDir
})
all := All(env)
if len(all) != 3 { if len(all) != 3 {
t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all)) t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all))
} }
@ -66,9 +67,10 @@ func TestAll(t *testing.T) {
} }
func TestByScheme(t *testing.T) { func TestByScheme(t *testing.T) {
g := All(&cli.EnvSettings{ env := cli.New()
PluginsDirectory: pluginDir, env.PluginsDirectory = pluginDir
})
g := All(env)
if _, err := g.ByScheme("test"); err != nil { if _, err := g.ByScheme("test"); err != nil {
t.Error(err) t.Error(err)
} }

@ -122,7 +122,7 @@ func TestDownload(t *testing.T) {
})) }))
defer srv.Close() defer srv.Close()
g, err := All(new(cli.EnvSettings)).ByScheme("http") g, err := All(cli.New()).ByScheme("http")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -24,9 +24,9 @@ import (
) )
func TestCollectPlugins(t *testing.T) { func TestCollectPlugins(t *testing.T) {
env := &cli.EnvSettings{ env := cli.New()
PluginsDirectory: pluginDir, env.PluginsDirectory = pluginDir
}
p, err := collectPlugins(env) p, err := collectPlugins(env)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -54,9 +54,8 @@ func TestPluginGetter(t *testing.T) {
t.Skip("TODO: refactor this test to work on windows") t.Skip("TODO: refactor this test to work on windows")
} }
env := &cli.EnvSettings{ env := cli.New()
PluginsDirectory: pluginDir, env.PluginsDirectory = pluginDir
}
pg := NewPluginGetter("echo", env, "test", ".") pg := NewPluginGetter("echo", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {
@ -80,9 +79,9 @@ func TestPluginSubCommands(t *testing.T) {
t.Skip("TODO: refactor this test to work on windows") t.Skip("TODO: refactor this test to work on windows")
} }
env := &cli.EnvSettings{ env := cli.New()
PluginsDirectory: pluginDir, env.PluginsDirectory = pluginDir
}
pg := NewPluginGetter("echo -n", env, "test", ".") pg := NewPluginGetter("echo -n", env, "test", ".")
g, err := pg() g, err := pg()
if err != nil { if err != nil {

@ -223,6 +223,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
if err := info.Get(); err != nil { if err := info.Get(); err != nil {
c.Log("Unable to get obj %q, err: %s", info.Name, err) c.Log("Unable to get obj %q, err: %s", info.Name, err)
continue
} }
annotations, err := metadataAccessor.Annotations(info.Object) annotations, err := metadataAccessor.Annotations(info.Object)
if err != nil { if err != nil {
@ -232,16 +233,11 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy) c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy)
continue continue
} }
res.Deleted = append(res.Deleted, info)
if err := deleteResource(info); err != nil { if err := deleteResource(info); err != nil {
if apierrors.IsNotFound(err) { c.Log("Failed to delete %q, err: %s", info.ObjectName(), err)
c.Log("Attempted to delete %q, but the resource was missing", info.Name) continue
} else {
c.Log("Failed to delete %q, err: %s", info.Name, err)
return res, errors.Wrapf(err, "Failed to delete %q", info.Name)
}
} }
res.Deleted = append(res.Deleted, info)
} }
return res, nil return res, nil
} }

@ -164,9 +164,21 @@ func TestUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if _, err := c.Update(first, second, false); err != nil { result, err := c.Update(first, second, false)
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(result.Created) != 1 {
t.Errorf("expected 1 resource created, got %d", len(result.Created))
}
if len(result.Updated) != 2 {
t.Errorf("expected 2 resource updated, got %d", len(result.Updated))
}
if len(result.Deleted) != 1 {
t.Errorf("expected 1 resource deleted, got %d", len(result.Deleted))
}
// TODO: Find a way to test methods that use Client Set // TODO: Find a way to test methods that use Client Set
// Test with a wait // Test with a wait
// if err := c.Update("test", objBody(codec, &listB), objBody(codec, &listC), false, 300, true); err != nil { // if err := c.Update("test", objBody(codec, &listB), objBody(codec, &listC), false, 300, true); err != nil {
@ -190,8 +202,7 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/squid:DELETE",
} }
if len(expectedActions) != len(actions) { if len(expectedActions) != len(actions) {
t.Errorf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) t.Fatalf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions))
return
} }
for k, v := range expectedActions { for k, v := range expectedActions {
if actions[k] != v { if actions[k] != v {

@ -19,6 +19,8 @@ package kube // import "helm.sh/helm/v3/pkg/kube"
import "k8s.io/cli-runtime/pkg/genericclioptions" import "k8s.io/cli-runtime/pkg/genericclioptions"
// GetConfig returns a Kubernetes client config. // GetConfig returns a Kubernetes client config.
//
// Deprecated
func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags {
cf := genericclioptions.NewConfigFlags(true) cf := genericclioptions.NewConfigFlags(true)
cf.Namespace = &namespace cf.Namespace = &namespace

@ -305,9 +305,8 @@ func TestSetupEnv(t *testing.T) {
name := "pequod" name := "pequod"
base := filepath.Join("testdata/helmhome/helm/plugins", name) base := filepath.Join("testdata/helmhome/helm/plugins", name)
s := &cli.EnvSettings{ s := cli.New()
PluginsDirectory: "testdata/helmhome/helm/plugins", s.PluginsDirectory = "testdata/helmhome/helm/plugins"
}
SetupPluginEnv(s, name, base) SetupPluginEnv(s, name, base)
for _, tt := range []struct { for _, tt := range []struct {

@ -17,6 +17,7 @@ limitations under the License.
package repo package repo
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
@ -29,6 +30,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/fileutil"
"helm.sh/helm/v3/internal/urlutil" "helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
@ -197,7 +199,7 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(dest, b, mode) return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode)
} }
// Merge merges the given index file into this index. // Merge merges the given index file into this index.

@ -428,3 +428,23 @@ func TestIndexAdd(t *testing.T) {
t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0])
} }
} }
func TestIndexWrite(t *testing.T) {
i := NewIndexFile()
i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890")
dir, err := ioutil.TempDir("", "helm-tmp")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testpath := filepath.Join(dir, "test")
i.WriteFile(testpath, 0600)
got, err := ioutil.ReadFile(testpath)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), "clipper-0.1.0.tgz") {
t.Fatal("Index files doesn't contain expected content")
}
}

Loading…
Cancel
Save