install render connected, template local only

Signed-off-by: christopher dykstra <chrdyks@gmail.com>
pull/9797/head
christopher dykstra 4 years ago
parent ee499502b4
commit aa43a434b1

@ -117,6 +117,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.RenderConnected = true
rel, err := runInstall(args, client, valueOpts, out) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return err return err

@ -74,6 +74,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
client.DryRun = true client.DryRun = true
client.RenderConnected = false
client.ReleaseName = "RELEASE-NAME" client.ReleaseName = "RELEASE-NAME"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
client.ClientOnly = !validate client.ClientOnly = !validate

@ -74,6 +74,7 @@ type Install struct {
ClientOnly bool ClientOnly bool
CreateNamespace bool CreateNamespace bool
DryRun bool DryRun bool
RenderConnected bool
DisableHooks bool DisableHooks bool
Replace bool Replace bool
Wait bool Wait bool
@ -242,10 +243,10 @@ 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
if i.DryRun { if i.RenderConnected {
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResourcesLocally(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer)
} else {
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)
} else {
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResourcesLocally(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer)
} }
// Even for errors, attach this if available // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {

@ -17,6 +17,7 @@ limitations under the License.
package action package action
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
@ -27,16 +28,23 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"helm.sh/helm/v3/internal/test" "helm.sh/helm/v3/internal/test"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"
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"
"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"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
) )
type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error)
type lookupMatch func(apiversion string, resource string, namespace string, name string) bool
type nameTemplateTestCase struct { type nameTemplateTestCase struct {
tpl string tpl string
expected string expected string
@ -52,6 +60,55 @@ func installAction(t *testing.T) *Install {
return instAction return instAction
} }
func toMap(in interface{}) map[string]interface{} {
var out map[string]interface{}
marshalled, _ := json.Marshal(in)
json.Unmarshal(marshalled, &out)
return out
}
func addLookupReturn(obj map[string]interface{}, match lookupMatch) {
originalLookupFactory := engine.NewLookupFunction
engine.NewLookupFunction = func(config *rest.Config) lookupFunc {
return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) {
if match(apiversion, resource, namespace, name) {
return obj, nil
}
return originalLookupFactory(config)(apiversion, resource, namespace, name)
}
}
}
func matchPod(pod v1.Pod) lookupMatch {
return func(apiversion string, resource string, namespace string, name string) bool {
return apiversion == "v1" && resource == pod.TypeMeta.Kind && namespace == pod.ObjectMeta.Namespace && name == pod.ObjectMeta.Name
}
}
func newPodWithStatus(name string, status v1.PodStatus, namespace string) v1.Pod {
ns := v1.NamespaceDefault
if namespace != "" {
ns = namespace
}
return v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
SelfLink: "/api/v1/namespaces/default/pods/" + name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "app:v4",
Image: "abc/app:v4",
Ports: []v1.ContainerPort{{Name: "http", ContainerPort: 80}},
}},
},
Status: status,
}
}
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
is := assert.New(t) is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
@ -241,25 +298,47 @@ 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_RenderConnected_Lookup(t *testing.T) {
func TestInstallRelease_DryRun_Lookup(t *testing.T) { it := assert.New(t)
is := assert.New(t)
instAction := installAction(t) instAction := installAction(t)
instAction.DryRun = true instAction.cfg.RESTClientGetter = cmdtesting.NewTestFactory()
vals := map[string]interface{}{} neo := newPodWithStatus("Neo", v1.PodStatus{}, "The Matricks")
instAction.RenderConnected = true
mockChart := buildChart()
addLookupReturn(toMap(neo), matchPod(neo))
mockChart.Templates = append(mockChart.Templates, &chart.File{
Name: "templates/lookup",
Data: []byte(fmt.Sprintf(`goodbye: {{ lookup "v1" "%s" "%s" "%s" }}`, neo.TypeMeta.Kind, neo.ObjectMeta.Namespace, neo.ObjectMeta.Name)),
})
mockChart := buildChart(withSampleTemplates()) res, err := instAction.Run(mockChart, map[string]interface{}{})
if err != nil {
t.Fatalf("Failed install: %s", err)
}
it.Contains(res.Manifest, fmt.Sprint(toMap(neo)))
}
func TestInstallRelease_NotRenderConnected_Lookup(t *testing.T) {
it := assert.New(t)
instAction := installAction(t)
instAction.cfg.RESTClientGetter = cmdtesting.NewTestFactory()
trinity := newPodWithStatus("Trinity", v1.PodStatus{}, "The Matricks")
instAction.RenderConnected = false
mockChart := buildChart()
addLookupReturn(toMap(trinity), matchPod(trinity))
mockChart.Templates = append(mockChart.Templates, &chart.File{ mockChart.Templates = append(mockChart.Templates, &chart.File{
Name: "templates/lookup", Name: "templates/lookup",
Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`), Data: []byte(fmt.Sprintf(`goodbye: {{ lookup "v1" "%s" "%s" "%s" }}`, trinity.TypeMeta.Kind, trinity.ObjectMeta.Namespace, trinity.ObjectMeta.Name)),
}) })
res, err := instAction.Run(mockChart, vals) res, err := instAction.Run(mockChart, map[string]interface{}{})
if err != nil { if err != nil {
t.Fatalf("Failed install: %s", err) t.Fatalf("Failed install: %s", err)
} }
is.Contains(res.Manifest, "goodbye: map[]") it.Contains(res.Manifest, "goodbye: map[]")
it.NotContains(res.Manifest, fmt.Sprint(toMap(trinity)))
} }
func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {

@ -38,7 +38,7 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam
// //
// This function is considered deprecated, and will be renamed in Helm 4. It will no // This function is considered deprecated, and will be renamed in Helm 4. It will no
// longer be a public function. // longer be a public function.
func NewLookupFunction(config *rest.Config) lookupFunc { var NewLookupFunction = func(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
c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config) c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config)

Loading…
Cancel
Save