Merge remote-tracking branch 'helm/master'

pull/8034/head
Liu Ming 5 years ago
commit ee91c3e85f

@ -58,7 +58,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
return return
} }
found, err := findPlugins(settings.PluginsDirectory) found, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return return
@ -238,20 +238,6 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
return known, unknown return known, unknown
} }
// findPlugins returns a list of YAML files that describe plugins.
func findPlugins(plugdirs string) ([]*plugin.Plugin, error) {
found := []*plugin.Plugin{}
// Let's get all UNIXy and allow path separators
for _, p := range filepath.SplitList(plugdirs) {
matches, err := plugin.LoadAll(p)
if err != nil {
return matches, err
}
found = append(found, matches...)
}
return found, nil
}
// pluginCommand represents the optional completion.yaml file of a plugin // pluginCommand represents the optional completion.yaml file of a plugin
type pluginCommand struct { type pluginCommand struct {
Name string `json:"name"` Name string `json:"name"`

@ -22,6 +22,8 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/plugin"
) )
func newPluginListCmd(out io.Writer) *cobra.Command { func newPluginListCmd(out io.Writer) *cobra.Command {
@ -31,7 +33,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
debug("pluginDirs: %s", settings.PluginsDirectory) debug("pluginDirs: %s", settings.PluginsDirectory)
plugins, err := findPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }
@ -51,7 +53,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
// Provide dynamic auto-completion for plugin names // Provide dynamic auto-completion for plugin names
func compListPlugins(toComplete string) []string { func compListPlugins(toComplete string) []string {
var pNames []string var pNames []string
plugins, err := findPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err == nil { if err == nil {
for _, p := range plugins { for _, p := range plugins {
if strings.HasPrefix(p.Metadata.Name, toComplete) { if strings.HasPrefix(p.Metadata.Name, toComplete) {

@ -68,7 +68,7 @@ func (o *pluginUninstallOptions) complete(args []string) error {
func (o *pluginUninstallOptions) run(out io.Writer) error { func (o *pluginUninstallOptions) run(out io.Writer) error {
debug("loading installed plugins from %s", settings.PluginsDirectory) debug("loading installed plugins from %s", settings.PluginsDirectory)
plugins, err := findPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }

@ -70,7 +70,7 @@ func (o *pluginUpdateOptions) complete(args []string) error {
func (o *pluginUpdateOptions) run(out io.Writer) error { func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
debug("loading installed plugins from %s", settings.PluginsDirectory) debug("loading installed plugins from %s", settings.PluginsDirectory)
plugins, err := findPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
} }

@ -35,7 +35,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync" "sync"
@ -175,13 +174,9 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil { if currentUID == 0 {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
@ -214,13 +209,9 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil { if currentUID == 0 {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
@ -314,13 +305,9 @@ func TestCopyDirFailOpen(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
@ -483,13 +470,9 @@ func TestCopyFileFail(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil { if currentUID == 0 {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
@ -574,13 +557,9 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
func TestIsDir(t *testing.T) { func TestIsDir(t *testing.T) {
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil { if currentUID == 0 {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }
@ -631,13 +610,9 @@ func TestIsDir(t *testing.T) {
func TestIsSymlink(t *testing.T) { func TestIsSymlink(t *testing.T) {
var currentUser, err = user.Current() var currentUID = os.Getuid()
if err != nil {
t.Fatalf("Failed to get name of current user: %s", err)
}
if currentUser.Name == "root" { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
t.Skip("Skipping for root user") t.Skip("Skipping for root user")
} }

@ -17,10 +17,13 @@ limitations under the License.
package action package action
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"regexp" "regexp"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -30,9 +33,13 @@ import (
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
"helm.sh/helm/v3/pkg/time" "helm.sh/helm/v3/pkg/time"
@ -86,6 +93,132 @@ type Configuration struct {
Log func(string, ...interface{}) Log func(string, ...interface{})
} }
// renderResources renders the templates in a chart
//
// TODO: This function is badly in need of a refactor.
func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{}
b := bytes.NewBuffer(nil)
caps, err := c.getCapabilities()
if err != nil {
return hs, b, "", err
}
if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
}
}
var files map[string]string
var err2 error
// A `helm template` or `helm install --dry-run` should not talk to the remote cluster.
// It will break in interesting and exotic ways because other data (e.g. discovery)
// is mocked. It is not up to the template author to decide when the user wants to
// connect to the cluster. So when the user says to dry run, respect the user's
// wishes and do not connect to the cluster.
if !dryRun && c.RESTClientGetter != nil {
rest, err := c.RESTClientGetter.ToRESTConfig()
if err != nil {
return hs, b, "", err
}
files, err2 = engine.RenderWithClient(ch, values, rest)
} else {
files, err2 = engine.Render(ch, values)
}
if err2 != nil {
return hs, b, "", err2
}
// NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource,
// pull it out of here into a separate file so that we can actually use the output of the rendered
// text file. We have to spin through this map because the file contains path information, so we
// look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip
// it in the sortHooks.
var notesBuffer bytes.Buffer
for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) {
if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) {
// If buffer contains data, add newline before adding more
if notesBuffer.Len() > 0 {
notesBuffer.WriteString("\n")
}
notesBuffer.WriteString(v)
}
delete(files, k)
}
}
notes := notesBuffer.String()
// Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also
// removed here.
hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder)
if err != nil {
// By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes.
//
// We return the files as a big blob of data to help the user debug parser
// errors.
for name, content := range files {
if strings.TrimSpace(content) == "" {
continue
}
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
}
return hs, b, "", err
}
// Aggregate all valid manifests into one big doc.
fileWritten := make(map[string]bool)
if includeCrds {
for _, crd := range ch.CRDObjects() {
if outputDir == "" {
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:]))
} else {
err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name])
if err != nil {
return hs, b, "", err
}
fileWritten[crd.Name] = true
}
}
}
for _, m := range manifests {
if outputDir == "" {
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content)
} else {
newDir := outputDir
if useReleaseName {
newDir = filepath.Join(outputDir, releaseName)
}
// NOTE: We do not have to worry about the post-renderer because
// output dir is only used by `helm template`. In the next major
// release, we should move this logic to template only as it is not
// used by install or upgrade
err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name])
if err != nil {
return hs, b, "", err
}
fileWritten[m.Name] = true
}
}
if pr != nil {
b, err = pr.Run(b)
if err != nil {
return hs, b, notes, errors.Wrap(err, "error while running post render on files")
}
}
return hs, b, notes, nil
}
// RESTClientGetter gets the rest client // RESTClientGetter gets the rest client
type RESTClientGetter interface { type RESTClientGetter interface {
ToRESTConfig() (*rest.Config, error) ToRESTConfig() (*rest.Config, error)

@ -39,7 +39,6 @@ import (
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
@ -232,7 +231,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
rel := i.createRelease(chrt, vals) rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer) rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun)
// Even for errors, attach this if available // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()
@ -475,125 +474,6 @@ func (i *Install) replaceRelease(rel *release.Release) error {
return i.recordRelease(last) return i.recordRelease(last)
} }
// renderResources renders the templates in a chart
func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{}
b := bytes.NewBuffer(nil)
caps, err := c.getCapabilities()
if err != nil {
return hs, b, "", err
}
if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
}
}
var files map[string]string
var err2 error
if c.RESTClientGetter != nil {
rest, err := c.RESTClientGetter.ToRESTConfig()
if err != nil {
return hs, b, "", err
}
files, err2 = engine.RenderWithClient(ch, values, rest)
} else {
files, err2 = engine.Render(ch, values)
}
if err2 != nil {
return hs, b, "", err2
}
// NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource,
// pull it out of here into a separate file so that we can actually use the output of the rendered
// text file. We have to spin through this map because the file contains path information, so we
// look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip
// it in the sortHooks.
var notesBuffer bytes.Buffer
for k, v := range files {
if strings.HasSuffix(k, notesFileSuffix) {
if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) {
// If buffer contains data, add newline before adding more
if notesBuffer.Len() > 0 {
notesBuffer.WriteString("\n")
}
notesBuffer.WriteString(v)
}
delete(files, k)
}
}
notes := notesBuffer.String()
// Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also
// removed here.
hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder)
if err != nil {
// By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes.
//
// We return the files as a big blob of data to help the user debug parser
// errors.
for name, content := range files {
if strings.TrimSpace(content) == "" {
continue
}
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content)
}
return hs, b, "", err
}
// Aggregate all valid manifests into one big doc.
fileWritten := make(map[string]bool)
if includeCrds {
for _, crd := range ch.CRDObjects() {
if outputDir == "" {
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:]))
} else {
err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name])
if err != nil {
return hs, b, "", err
}
fileWritten[crd.Name] = true
}
}
}
for _, m := range manifests {
if outputDir == "" {
fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content)
} else {
newDir := outputDir
if useReleaseName {
newDir = filepath.Join(outputDir, releaseName)
}
// NOTE: We do not have to worry about the post-renderer because
// output dir is only used by `helm template`. In the next major
// release, we should move this logic to template only as it is not
// used by install or upgrade
err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name])
if err != nil {
return hs, b, "", err
}
fileWritten[m.Name] = true
}
}
if pr != nil {
b, err = pr.Run(b)
if err != nil {
return hs, b, notes, errors.Wrap(err, "error while running post render on files")
}
}
return hs, b, notes, nil
}
// write the <data> to <output-dir>/<name>. <append> controls if the file is created or content will be appended // write the <data> to <output-dir>/<name>. <append> controls if the file is created or content will be appended
func writeToFile(outputDir string, name string, data string, append bool) error { func writeToFile(outputDir string, name string, data string, append bool) error {
outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))

@ -29,6 +29,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"helm.sh/helm/v3/internal/test" "helm.sh/helm/v3/internal/test"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
@ -240,6 +241,27 @@ func TestInstallRelease_DryRun(t *testing.T) {
is.Equal(res.Info.Description, "Dry run complete") is.Equal(res.Info.Description, "Dry run complete")
} }
// Regression test for #7955: Lookup must not connect to Kubernetes on a dry-run.
func TestInstallRelease_DryRun_Lookup(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.DryRun = true
vals := map[string]interface{}{}
mockChart := buildChart(withSampleTemplates())
mockChart.Templates = append(mockChart.Templates, &chart.File{
Name: "templates/lookup",
Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`),
})
res, err := instAction.Run(mockChart, vals)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
is.Contains(res.Manifest, "goodbye: map[]")
}
func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)

@ -217,7 +217,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err return nil, nil, err
} }
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer) hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -175,7 +175,11 @@ func coalesceValues(c *chart.Chart, v map[string]interface{}) {
// //
// dest is considered authoritative. // dest is considered authoritative.
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
if dst == nil || src == nil { // When --reuse-values is set but there are no modifications yet, return new values
if src == nil {
return dst
}
if dst == nil {
return src return src
} }
// Because dest has higher precedence than src, dest values override src // Because dest has higher precedence than src, dest values override src

@ -211,4 +211,57 @@ func TestCoalesceTables(t *testing.T) {
if _, ok = dst["hole"]; ok { if _, ok = dst["hole"]; ok {
t.Error("The hole still exists.") t.Error("The hole still exists.")
} }
dst2 := map[string]interface{}{
"name": "Ishmael",
"address": map[string]interface{}{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": "US",
},
"details": map[string]interface{}{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
"hole": "black",
}
// What we expect is that anything in dst should have all values set,
// this happens when the --reuse-values flag is set but the chart has no modifications yet
CoalesceTables(dst2, nil)
if dst2["name"] != "Ishmael" {
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]interface{})
if !ok {
t.Fatal("Address went away.")
}
if addr2["street"].(string) != "123 Spouter Inn Ct." {
t.Errorf("Unexpected address: %v", addr2["street"])
}
if addr2["city"].(string) != "Nantucket" {
t.Errorf("Unexpected city: %v", addr2["city"])
}
if addr2["country"].(string) != "US" {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]interface{}); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
}
if dst2["boat"].(string) != "pequod" {
t.Errorf("Expected boat string, got %v", dst2["boat"])
}
if dst2["hole"].(string) != "black" {
t.Errorf("Expected hole string, got %v", dst2["boat"])
}
} }

@ -172,7 +172,10 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
} }
return val, nil return val, nil
} }
if e.config != nil {
// If we are not linting and have a cluster connection, provide a Kubernetes-backed
// implementation.
if !e.LintMode && e.config != nil {
funcMap["lookup"] = NewLookupFunction(e.config) funcMap["lookup"] = NewLookupFunction(e.config)
} }
@ -213,6 +216,7 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
// We want to parse the templates in a predictable order. The order favors // We want to parse the templates in a predictable order. The order favors
// higher-level (in file system) templates over deeply nested templates. // higher-level (in file system) templates over deeply nested templates.
keys := sortTemplates(tpls) keys := sortTemplates(tpls)
referenceKeys := sortTemplates(referenceTpls)
for _, filename := range keys { for _, filename := range keys {
r := tpls[filename] r := tpls[filename]
@ -223,8 +227,9 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
// Adding the reference templates to the template context // Adding the reference templates to the template context
// so they can be referenced in the tpl function // so they can be referenced in the tpl function
for filename, r := range referenceTpls { for _, filename := range referenceKeys {
if t.Lookup(filename) == nil { if t.Lookup(filename) == nil {
r := referenceTpls[filename]
if _, err := t.New(filename).Parse(r.tpl); err != nil { if _, err := t.New(filename).Parse(r.tpl); err != nil {
return map[string]string{}, cleanupParseError(filename, err) return map[string]string{}, cleanupParseError(filename, err)
} }

@ -70,7 +70,7 @@ func TestFuncMap(t *testing.T) {
} }
// Test for Engine-specific template functions. // Test for Engine-specific template functions.
expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson"} expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"}
for _, f := range expect { for _, f := range expect {
if _, ok := fns[f]; !ok { if _, ok := fns[f]; !ok {
t.Errorf("Expected add-on function %q", f) t.Errorf("Expected add-on function %q", f)
@ -126,6 +126,46 @@ func TestRender(t *testing.T) {
} }
} }
func TestRenderRefsOrdering(t *testing.T) {
parentChart := &chart.Chart{
Metadata: &chart.Metadata{
Name: "parent",
Version: "1.2.3",
},
Templates: []*chart.File{
{Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)},
{Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)},
},
}
childChart := &chart.Chart{
Metadata: &chart.Metadata{
Name: "child",
Version: "1.2.3",
},
Templates: []*chart.File{
{Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)},
},
}
parentChart.AddDependency(childChart)
expect := map[string]string{
"parent/templates/test.yaml": "parent value",
}
for i := 0; i < 100; i++ {
out, err := Render(parentChart, chartutil.Values{})
if err != nil {
t.Fatalf("Failed to render templates: %s", err)
}
for name, data := range expect {
if out[name] != data {
t.Fatalf("Expected %q, got %q (iteraction %d)", data, out[name], i+1)
}
}
}
}
func TestRenderInternals(t *testing.T) { func TestRenderInternals(t *testing.T) {
// Test the internals of the rendering tool. // Test the internals of the rendering tool.

@ -62,6 +62,11 @@ func funcMap() template.FuncMap {
"include": func(string, interface{}) string { return "not implemented" }, "include": func(string, interface{}) string { return "not implemented" },
"tpl": func(string, interface{}) interface{} { return "not implemented" }, "tpl": func(string, interface{}) interface{} { return "not implemented" },
"required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil },
// Provide a placeholder for the "lookup" function, which requires a kubernetes
// connection.
"lookup": func(string, string, string, string) (map[string]interface{}, error) {
return map[string]interface{}{}, nil
},
} }
for k, v := range extra { for k, v := range extra {

@ -94,6 +94,11 @@ func TestFuncs(t *testing.T) {
tpl: `{{ fromYamlArray . }}`, tpl: `{{ fromYamlArray . }}`,
expect: `[error unmarshaling JSON: while decoding JSON: json: cannot unmarshal object into Go value of type []interface {}]`, expect: `[error unmarshaling JSON: while decoding JSON: json: cannot unmarshal object into Go value of type []interface {}]`,
vars: `hello: world`, vars: `hello: world`,
}, {
// This should never result in a network lookup. Regression for #7955
tpl: `{{ lookup "v1" "Namespace" "" "unlikelynamespace99999999" }}`,
expect: `map[]`,
vars: `["one", "two"]`,
}} }}
for _, tt := range tests { for _, tt := range tests {

@ -32,8 +32,12 @@ import (
type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error)
// NewLookupFunction returns a function for looking up objects in the cluster. If the resource does not exist, no error // NewLookupFunction returns a function for looking up objects in the cluster.
// is raised. //
// If the resource does not exist, no error is raised.
//
// This function is considered deprecated, and will be renamed in Helm 4. It will no
// longer be a public function.
func NewLookupFunction(config *rest.Config) lookupFunc { func NewLookupFunction(config *rest.Config) lookupFunc {
return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) { return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) {
var client dynamic.ResourceInterface var client dynamic.ResourceInterface

@ -16,7 +16,6 @@ limitations under the License.
package installer // import "helm.sh/helm/v3/pkg/plugin/installer" package installer // import "helm.sh/helm/v3/pkg/plugin/installer"
import ( import (
"os"
"path/filepath" "path/filepath"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
@ -31,13 +30,7 @@ func newBase(source string) base {
return base{source} return base{source}
} }
// link creates a symlink from the plugin source to the base path. // Path is where the plugin will be installed.
func (b *base) link(from string) error {
debug("symlinking %s to %s", from, b.Path())
return os.Symlink(from, b.Path())
}
// Path is where the plugin will be symlinked to.
func (b *base) Path() string { func (b *base) Path() string {
if b.Source == "" { if b.Source == "" {
return "" return ""

@ -27,6 +27,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/third_party/dep/fs"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
@ -68,7 +69,6 @@ func NewExtractor(source string) (Extractor, error) {
// NewHTTPInstaller creates a new HttpInstaller. // NewHTTPInstaller creates a new HttpInstaller.
func NewHTTPInstaller(source string) (*HTTPInstaller, error) { func NewHTTPInstaller(source string) (*HTTPInstaller, error) {
key, err := cache.Key(source) key, err := cache.Key(source)
if err != nil { if err != nil {
return nil, err return nil, err
@ -108,18 +108,16 @@ func stripPluginName(name string) string {
} }
// Install downloads and extracts the tarball into the cache directory // Install downloads and extracts the tarball into the cache directory
// and creates a symlink to the plugin directory. // and installs into the plugin directory.
// //
// Implements Installer. // Implements Installer.
func (i *HTTPInstaller) Install() error { func (i *HTTPInstaller) Install() error {
pluginData, err := i.getter.Get(i.Source) pluginData, err := i.getter.Get(i.Source)
if err != nil { if err != nil {
return err return err
} }
err = i.extractor.Extract(pluginData, i.CacheDir) if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil {
if err != nil {
return err return err
} }
@ -132,7 +130,8 @@ func (i *HTTPInstaller) Install() error {
return err return err
} }
return i.link(src) debug("copying %s to %s", src, i.Path())
return fs.CopyDir(src, i.Path())
} }
// Update updates a local repository // Update updates a local repository
@ -141,12 +140,6 @@ func (i *HTTPInstaller) Update() error {
return errors.Errorf("method Update() not implemented for HttpInstaller") return errors.Errorf("method Update() not implemented for HttpInstaller")
} }
// Override link because we want to use HttpInstaller.Path() not base.Path()
func (i *HTTPInstaller) link(from string) error {
debug("symlinking %s to %s", from, i.Path())
return os.Symlink(from, i.Path())
}
// Path is overridden because we want to join on the plugin name not the file name // Path is overridden because we want to join on the plugin name not the file name
func (i HTTPInstaller) Path() string { func (i HTTPInstaller) Path() string {
if i.base.Source == "" { if i.base.Source == "" {
@ -164,17 +157,16 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
return err return err
} }
tarReader := tar.NewReader(uncompressedStream) if err := os.MkdirAll(targetDir, 0755); err != nil {
return err
os.MkdirAll(targetDir, 0755) }
tarReader := tar.NewReader(uncompressedStream)
for { for {
header, err := tarReader.Next() header, err := tarReader.Next()
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
return err return err
} }
@ -200,7 +192,5 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
} }
} }
return nil return nil
} }

@ -73,13 +73,13 @@ func TestHTTPInstaller(t *testing.T) {
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { if !ok {
t.Error("expected a HTTPInstaller") t.Fatal("expected a HTTPInstaller")
} }
// inject fake http client responding with minimal plugin tarball // inject fake http client responding with minimal plugin tarball
@ -94,17 +94,17 @@ func TestHTTPInstaller(t *testing.T) {
// install the plugin // install the plugin
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Fatal(err)
} }
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }
// Install again to test plugin exists error // Install again to test plugin exists error
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Error("expected error for plugin exists, got none") t.Fatal("expected error for plugin exists, got none")
} else if err.Error() != "plugin already exists" { } else if err.Error() != "plugin already exists" {
t.Errorf("expected error for plugin exists, got (%v)", err) t.Fatalf("expected error for plugin exists, got (%v)", err)
} }
} }
@ -119,13 +119,13 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
i, err := NewForSource(source, "0.0.2") i, err := NewForSource(source, "0.0.2")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { if !ok {
t.Error("expected a HTTPInstaller") t.Fatal("expected a HTTPInstaller")
} }
// inject fake http client responding with error // inject fake http client responding with error
@ -135,7 +135,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
// attempt to install the plugin // attempt to install the plugin
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Error("expected error from http client") t.Fatal("expected error from http client")
} }
} }
@ -150,13 +150,13 @@ func TestHTTPInstallerUpdate(t *testing.T) {
i, err := NewForSource(source, "0.0.1") i, err := NewForSource(source, "0.0.1")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
// ensure a HTTPInstaller was returned // ensure a HTTPInstaller was returned
httpInstaller, ok := i.(*HTTPInstaller) httpInstaller, ok := i.(*HTTPInstaller)
if !ok { if !ok {
t.Error("expected a HTTPInstaller") t.Fatal("expected a HTTPInstaller")
} }
// inject fake http client responding with minimal plugin tarball // inject fake http client responding with minimal plugin tarball
@ -171,15 +171,15 @@ func TestHTTPInstallerUpdate(t *testing.T) {
// install the plugin before updating // install the plugin before updating
if err := Install(i); err != nil { if err := Install(i); err != nil {
t.Error(err) t.Fatal(err)
} }
if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
} }
// Update plugin, should fail because it is not implemented // Update plugin, should fail because it is not implemented
if err := Update(i); err == nil { if err := Update(i); err == nil {
t.Error("update method not implemented for http installer") t.Fatal("update method not implemented for http installer")
} }
} }
@ -240,29 +240,27 @@ func TestExtract(t *testing.T) {
} }
if err = extractor.Extract(&buf, tempDir); err != nil { if err = extractor.Extract(&buf, tempDir); err != nil {
t.Errorf("Did not expect error but got error: %v", err) t.Fatalf("Did not expect error but got error: %v", err)
} }
pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml")
if info, err := os.Stat(pluginYAMLFullPath); err != nil { if info, err := os.Stat(pluginYAMLFullPath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
t.Errorf("Expected %s to exist but doesn't", pluginYAMLFullPath) t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath)
} else {
t.Error(err)
} }
t.Fatal(err)
} else if info.Mode().Perm() != 0600 { } else if info.Mode().Perm() != 0600 {
t.Errorf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm())
} }
readmeFullPath := filepath.Join(tempDir, "README.md") readmeFullPath := filepath.Join(tempDir, "README.md")
if info, err := os.Stat(readmeFullPath); err != nil { if info, err := os.Stat(readmeFullPath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
t.Errorf("Expected %s to exist but doesn't", readmeFullPath) t.Fatalf("Expected %s to exist but doesn't", readmeFullPath)
} else {
t.Error(err)
} }
t.Fatal(err)
} else if info.Mode().Perm() != 0777 { } else if info.Mode().Perm() != 0777 {
t.Errorf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm())
} }
} }

@ -17,6 +17,7 @@ package installer
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -103,9 +104,10 @@ func isPlugin(dirname string) bool {
return err == nil return err == nil
} }
var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile)
func debug(format string, args ...interface{}) { func debug(format string, args ...interface{}) {
if Debug { if Debug {
format = fmt.Sprintf("[debug] %s\n", format) logger.Output(2, fmt.Sprintf(format, args...))
fmt.Printf(format, args...)
} }
} }

@ -16,6 +16,7 @@ limitations under the License.
package installer // import "helm.sh/helm/v3/pkg/plugin/installer" package installer // import "helm.sh/helm/v3/pkg/plugin/installer"
import ( import (
"os"
"path/filepath" "path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -45,7 +46,8 @@ func (i *LocalInstaller) Install() error {
if !isPlugin(i.Source) { if !isPlugin(i.Source) {
return ErrMissingMetadata return ErrMissingMetadata
} }
return i.link(i.Source) debug("symlinking %s to %s", i.Source, i.Path())
return os.Symlink(i.Source, i.Path())
} }
// Update updates a local repository // Update updates a local repository

@ -40,7 +40,7 @@ func TestLocalInstaller(t *testing.T) {
source := "../testdata/plugdir/echo" source := "../testdata/plugdir/echo"
i, err := NewForSource(source, "") i, err := NewForSource(source, "")
if err != nil { if err != nil {
t.Errorf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
if err := Install(i); err != nil { if err := Install(i); err != nil {
@ -48,6 +48,6 @@ func TestLocalInstaller(t *testing.T) {
} }
if i.Path() != helmpath.DataPath("plugins", "echo") { if i.Path() != helmpath.DataPath("plugins", "echo") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
} }

@ -23,6 +23,7 @@ import (
"github.com/Masterminds/vcs" "github.com/Masterminds/vcs"
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/third_party/dep/fs"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/plugin/cache" "helm.sh/helm/v3/pkg/plugin/cache"
) )
@ -43,7 +44,7 @@ func existingVCSRepo(location string) (Installer, error) {
Repo: repo, Repo: repo,
base: newBase(repo.Remote()), base: newBase(repo.Remote()),
} }
return i, err return i, nil
} }
// NewVCSInstaller creates a new VCSInstaller. // NewVCSInstaller creates a new VCSInstaller.
@ -65,7 +66,7 @@ func NewVCSInstaller(source, version string) (*VCSInstaller, error) {
return i, err return i, err
} }
// Install clones a remote repository and creates a symlink to the plugin directory. // Install clones a remote repository and installs into the plugin directory.
// //
// Implements Installer. // Implements Installer.
func (i *VCSInstaller) Install() error { func (i *VCSInstaller) Install() error {
@ -87,7 +88,8 @@ func (i *VCSInstaller) Install() error {
return ErrMissingMetadata return ErrMissingMetadata
} }
return i.link(i.Repo.LocalPath()) debug("copying %s to %s", i.Repo.LocalPath(), i.Path())
return fs.CopyDir(i.Repo.LocalPath(), i.Path())
} }
// Update updates a remote repository // Update updates a remote repository

@ -80,24 +80,24 @@ func TestVCSInstaller(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if repo.current != "0.1.1" { if repo.current != "0.1.1" {
t.Errorf("expected version '0.1.1', got %q", repo.current) t.Fatalf("expected version '0.1.1', got %q", repo.current)
} }
if i.Path() != helmpath.DataPath("plugins", "helm-env") { if i.Path() != helmpath.DataPath("plugins", "helm-env") {
t.Errorf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path())
} }
// Install again to test plugin exists error // Install again to test plugin exists error
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Error("expected error for plugin exists, got none") t.Fatalf("expected error for plugin exists, got none")
} else if err.Error() != "plugin already exists" { } else if err.Error() != "plugin already exists" {
t.Errorf("expected error for plugin exists, got (%v)", err) t.Fatalf("expected error for plugin exists, got (%v)", err)
} }
// Testing FindSource method, expect error because plugin code is not a cloned repository // Testing FindSource method, expect error because plugin code is not a cloned repository
if _, err := FindSource(i.Path()); err == nil { if _, err := FindSource(i.Path()); err == nil {
t.Error("expected error for inability to find plugin source, got none") t.Fatalf("expected error for inability to find plugin source, got none")
} else if err.Error() != "cannot get information about plugin source" { } else if err.Error() != "cannot get information about plugin source" {
t.Errorf("expected error for inability to find plugin source, got (%v)", err) t.Fatalf("expected error for inability to find plugin source, got (%v)", err)
} }
} }
@ -113,15 +113,14 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
} }
// ensure a VCSInstaller was returned // ensure a VCSInstaller was returned
_, ok := i.(*VCSInstaller) if _, ok := i.(*VCSInstaller); !ok {
if !ok {
t.Fatal("expected a VCSInstaller") t.Fatal("expected a VCSInstaller")
} }
if err := Install(i); err == nil { if err := Install(i); err == nil {
t.Error("expected error for version does not exists, got none") t.Fatalf("expected error for version does not exists, got none")
} else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) {
t.Errorf("expected error for version does not exists, got (%v)", err) t.Fatalf("expected error for version does not exists, got (%v)", err)
} }
} }
func TestVCSInstallerUpdate(t *testing.T) { func TestVCSInstallerUpdate(t *testing.T) {
@ -135,8 +134,7 @@ func TestVCSInstallerUpdate(t *testing.T) {
} }
// ensure a VCSInstaller was returned // ensure a VCSInstaller was returned
_, ok := i.(*VCSInstaller) if _, ok := i.(*VCSInstaller); !ok {
if !ok {
t.Fatal("expected a VCSInstaller") t.Fatal("expected a VCSInstaller")
} }
@ -157,7 +155,9 @@ func TestVCSInstallerUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote() vcsInstaller := pluginInfo.(*VCSInstaller)
repoRemote := vcsInstaller.Repo.Remote()
if repoRemote != source { if repoRemote != source {
t.Fatalf("invalid source found, expected %q got %q", source, repoRemote) t.Fatalf("invalid source found, expected %q got %q", source, repoRemote)
} }
@ -168,12 +168,14 @@ func TestVCSInstallerUpdate(t *testing.T) {
} }
// Test update failure // Test update failure
os.Remove(filepath.Join(i.Path(), "plugin.yaml")) if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil {
t.Fatal(err)
}
// Testing update for error // Testing update for error
if err := Update(i); err == nil { if err := Update(vcsInstaller); err == nil {
t.Error("expected error for plugin modified, got none") t.Fatalf("expected error for plugin modified, got none")
} else if err.Error() != "plugin repo was modified" { } else if err.Error() != "plugin repo was modified" {
t.Errorf("expected error for plugin modified, got (%v)", err) t.Fatalf("expected error for plugin modified, got (%v)", err)
} }
} }

@ -28,14 +28,14 @@ import (
func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) {
cmd, args, err := p.PrepareCommand(extraArgs) cmd, args, err := p.PrepareCommand(extraArgs)
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Fatal(err)
} }
if cmd != "echo" { if cmd != "echo" {
t.Errorf("Expected echo, got %q", cmd) t.Fatalf("Expected echo, got %q", cmd)
} }
if l := len(args); l != 5 { if l := len(args); l != 5 {
t.Errorf("expected 5 args, got %d", l) t.Fatalf("expected 5 args, got %d", l)
} }
expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"}
@ -49,13 +49,13 @@ func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T)
p.Metadata.IgnoreFlags = true p.Metadata.IgnoreFlags = true
cmd, args, err = p.PrepareCommand(extraArgs) cmd, args, err = p.PrepareCommand(extraArgs)
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Fatal(err)
} }
if cmd != "echo" { if cmd != "echo" {
t.Errorf("Expected echo, got %q", cmd) t.Fatalf("Expected echo, got %q", cmd)
} }
if l := len(args); l != 2 { if l := len(args); l != 2 {
t.Errorf("expected 2 args, got %d", l) t.Fatalf("expected 2 args, got %d", l)
} }
expect = []string{"-n", osStrCmp} expect = []string{"-n", osStrCmp}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
@ -155,7 +155,7 @@ func TestNoPrepareCommand(t *testing.T) {
_, _, err := p.PrepareCommand(argv) _, _, err := p.PrepareCommand(argv)
if err == nil { if err == nil {
t.Errorf("Expected error to be returned") t.Fatalf("Expected error to be returned")
} }
} }
@ -172,7 +172,7 @@ func TestNoMatchPrepareCommand(t *testing.T) {
argv := []string{"--debug", "--foo", "bar"} argv := []string{"--debug", "--foo", "bar"}
if _, _, err := p.PrepareCommand(argv); err == nil { if _, _, err := p.PrepareCommand(argv); err == nil {
t.Errorf("Expected error to be returned") t.Fatalf("Expected error to be returned")
} }
} }
@ -184,7 +184,7 @@ func TestLoadDir(t *testing.T) {
} }
if plug.Dir != dirname { if plug.Dir != dirname {
t.Errorf("Expected dir %q, got %q", dirname, plug.Dir) t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir)
} }
expect := &Metadata{ expect := &Metadata{
@ -200,7 +200,7 @@ func TestLoadDir(t *testing.T) {
} }
if !reflect.DeepEqual(expect, plug.Metadata) { if !reflect.DeepEqual(expect, plug.Metadata) {
t.Errorf("Expected plugin metadata %v, got %v", expect, plug.Metadata) t.Fatalf("Expected plugin metadata %v, got %v", expect, plug.Metadata)
} }
} }
@ -212,7 +212,7 @@ func TestDownloader(t *testing.T) {
} }
if plug.Dir != dirname { if plug.Dir != dirname {
t.Errorf("Expected dir %q, got %q", dirname, plug.Dir) t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir)
} }
expect := &Metadata{ expect := &Metadata{
@ -230,7 +230,7 @@ func TestDownloader(t *testing.T) {
} }
if !reflect.DeepEqual(expect, plug.Metadata) { if !reflect.DeepEqual(expect, plug.Metadata) {
t.Errorf("Expected metadata %v, got %v", expect, plug.Metadata) t.Fatalf("Expected metadata %v, got %v", expect, plug.Metadata)
} }
} }

Loading…
Cancel
Save