diff --git a/cmd/helm/init.go b/cmd/helm/init.go index d6c432060..138fa14d7 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -181,95 +181,64 @@ func (i *initCmd) run() error { i.opts.MaxHistory = i.maxHistory i.opts.Replicas = i.replicas - writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { + writeYAMLManifests := func(manifests []string) error { w := i.out - if !first { - // YAML starting document boundary marker + for _, manifest := range manifests { if _, err := fmt.Fprintln(w, "---"); err != nil { return err } + + if _, err := fmt.Fprintln(w, manifest); err != nil { + return err + } } - if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil { - return err - } - if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { - return err - } - if _, err := fmt.Fprint(w, body); err != nil { - return err - } - if !last { - return nil - } + // YAML ending document boundary marker _, err := fmt.Fprintln(w, "...") return err } if len(i.opts.Output) > 0 { - var body string + var manifests []string var err error - const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` - if body, err = installer.DeploymentManifest(&i.opts); err != nil { + if manifests, err = installer.TillerManifests(&i.opts); err != nil { return err } switch i.opts.Output.String() { case "json": - var out bytes.Buffer - jsonb, err := yaml.ToJSON([]byte(body)) - if err != nil { - return err - } - buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) - buf.WriteString(tm) - // Drop the opening object delimiter ('{'). - buf.Write(jsonb[1:]) - if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { - return err - } - if _, err = i.out.Write(out.Bytes()); err != nil { - return err + for _, manifest := range manifests { + var out bytes.Buffer + jsonb, err := yaml.ToJSON([]byte(manifest)) + if err != nil { + return err + } + buf := bytes.NewBuffer(jsonb) + if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { + return err + } + if _, err = i.out.Write(out.Bytes()); err != nil { + return err + } + fmt.Fprint(i.out, "\n") } - return nil case "yaml": - if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { - return err - } - return nil + return writeYAMLManifests(manifests) default: return fmt.Errorf("unknown output format: %q", i.opts.Output) } } if settings.Debug { - - var body string + var manifests []string var err error - // write Deployment manifest - if body, err = installer.DeploymentManifest(&i.opts); err != nil { - return err - } - if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { + // write Tiller manifests + if manifests, err = installer.TillerManifests(&i.opts); err != nil { return err } - // write Service manifest - if body, err = installer.ServiceManifest(i.namespace); err != nil { - return err - } - if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil { + if err = writeYAMLManifests(manifests); err != nil { return err } - - // write Secret manifest - if i.opts.EnableTLS { - if body, err = installer.SecretManifest(&i.opts); err != nil { - return err - } - if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil { - return err - } - } } if i.dryRun { diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 74d6a0424..fd6ef97c4 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "io" "io/ioutil" "os" "path/filepath" @@ -32,11 +33,10 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes/fake" testcore "k8s.io/client-go/testing" - "encoding/json" - "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/helm/helmpath" ) @@ -306,7 +306,7 @@ func TestInitCmd_tlsOptions(t *testing.T) { } } -// TestInitCmd_output tests that init -o formats are unmarshal-able +// TestInitCmd_output tests that init -o can be decoded func TestInitCmd_output(t *testing.T) { // This is purely defensive in this case. home, err := ioutil.TempDir("", "helm_home") @@ -320,26 +320,14 @@ func TestInitCmd_output(t *testing.T) { settings.Debug = dbg }() fc := fake.NewSimpleClientset() - tests := []struct { - expectF func([]byte, interface{}) error - expectName string - }{ - { - json.Unmarshal, - "json", - }, - { - yaml.Unmarshal, - "yaml", - }, - } + tests := []string{"json", "yaml"} for _, s := range tests { var buf bytes.Buffer cmd := &initCmd{ out: &buf, home: helmpath.Home(home), kubeClient: fc, - opts: installer.Options{Output: installer.OutputFormat(s.expectName)}, + opts: installer.Options{Output: installer.OutputFormat(s)}, namespace: v1.NamespaceDefault, } if err := cmd.run(); err != nil { @@ -348,10 +336,17 @@ func TestInitCmd_output(t *testing.T) { if got := len(fc.Actions()); got != 0 { t.Errorf("expected no server calls, got %d", got) } - d := &v1beta1.Deployment{} - if err = s.expectF(buf.Bytes(), &d); err != nil { - t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String()) + + var obj interface{} + decoder := yamlutil.NewYAMLOrJSONDecoder(&buf, 4096) + for { + err := decoder.Decode(&obj) + if err != nil { + if err == io.EOF { + break + } + t.Errorf("error decoding init %s output %s %s", s, err, buf.String()) + } } } - } diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index 0108d1892..055601440 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -28,6 +28,7 @@ import ( "k8s.io/api/extensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -104,7 +105,7 @@ func semverCompare(image string) int { // createDeployment creates the Tiller Deployment resource. func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { - obj, err := deployment(opts) + obj, err := generateDeployment(opts) if err != nil { return err } @@ -113,40 +114,68 @@ func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) } -// deployment gets the deployment object that installs Tiller. -func deployment(opts *Options) (*v1beta1.Deployment, error) { - return generateDeployment(opts) +// Deployment gets a deployment object that can be used to generate a manifest +// as a string. This object should not be submitted directly to the Kubernetes +// api +func Deployment(opts *Options) (*v1beta1.Deployment, error) { + dep, err := generateDeployment(opts) + if err != nil { + return nil, err + } + dep.TypeMeta = metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + } + return dep, nil } // createService creates the Tiller service resource func createService(client corev1.ServicesGetter, namespace string) error { - obj := service(namespace) + obj := generateService(namespace) _, err := client.Services(obj.Namespace).Create(obj) return err } -// service gets the service object that installs Tiller. -func service(namespace string) *v1.Service { - return generateService(namespace) +// Service gets a service object that can be used to generate a manifest as a +// string. This object should not be submitted directly to the Kubernetes api +func Service(namespace string) *v1.Service { + svc := generateService(namespace) + svc.TypeMeta = metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + } + return svc } -// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment -// resource. -func DeploymentManifest(opts *Options) (string, error) { - obj, err := deployment(opts) +// TillerManifests gets the Deployment, Service, and Secret (if tls-enabled) manifests +func TillerManifests(opts *Options) ([]string, error) { + dep, err := Deployment(opts) if err != nil { - return "", err + return []string{}, err + } + + svc := Service(opts.Namespace) + + objs := []runtime.Object{dep, svc} + + if opts.EnableTLS { + secret, err := Secret(opts) + if err != nil { + return []string{}, err + } + objs = append(objs, secret) + } + + manifests := make([]string, len(objs)) + for i, obj := range objs { + o, err := yaml.Marshal(obj) + if err != nil { + return []string{}, err + } + manifests[i] = string(o) } - buf, err := yaml.Marshal(obj) - return string(buf), err -} -// ServiceManifest gets the manifest (as a string) that describes the Tiller Service -// resource. -func ServiceManifest(namespace string) (string, error) { - obj := service(namespace) - buf, err := yaml.Marshal(obj) - return string(buf), err + return manifests, err } func generateLabels(labels map[string]string) map[string]string { @@ -321,14 +350,20 @@ func generateService(namespace string) *v1.Service { return s } -// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource. -func SecretManifest(opts *Options) (string, error) { - o, err := generateSecret(opts) +// Secret gets a secret object that can be used to generate a manifest as a +// string. This object should not be submitted directly to the Kubernetes api +func Secret(opts *Options) (*v1.Secret, error) { + secret, err := generateSecret(opts) if err != nil { - return "", err + return nil, err + } + + secret.TypeMeta = metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", } - buf, err := yaml.Marshal(o) - return string(buf), err + + return secret, nil } // createSecret creates the Tiller secret resource. diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index b67bb0ad8..d5f3dfec0 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -34,7 +34,7 @@ import ( "k8s.io/helm/pkg/version" ) -func TestDeploymentManifest(t *testing.T) { +func TestDeployment(t *testing.T) { tests := []struct { name string image string @@ -48,14 +48,10 @@ func TestDeploymentManifest(t *testing.T) { } for _, tt := range tests { - o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary}) + dep, err := Deployment(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary}) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } - var dep v1beta1.Deployment - if err := yaml.Unmarshal([]byte(o), &dep); err != nil { - t.Fatalf("%s: error %q", tt.name, err) - } if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect { t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got) @@ -71,7 +67,7 @@ func TestDeploymentManifest(t *testing.T) { } } -func TestDeploymentManifestForServiceAccount(t *testing.T) { +func TestDeploymentForServiceAccount(t *testing.T) { tests := []struct { name string image string @@ -84,22 +80,18 @@ func TestDeploymentManifestForServiceAccount(t *testing.T) { {"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""}, } for _, tt := range tests { - o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount}) + d, err := Deployment(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount}) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } - var d v1beta1.Deployment - if err := yaml.Unmarshal([]byte(o), &d); err != nil { - t.Fatalf("%s: error %q", tt.name, err) - } if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount { t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got) } } } -func TestDeploymentManifest_WithTLS(t *testing.T) { +func TestDeployment_WithTLS(t *testing.T) { tests := []struct { opts Options name string @@ -126,15 +118,11 @@ func TestDeploymentManifest_WithTLS(t *testing.T) { }, } for _, tt := range tests { - o, err := DeploymentManifest(&tt.opts) + d, err := Deployment(&tt.opts) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } - var d v1beta1.Deployment - if err := yaml.Unmarshal([]byte(o), &d); err != nil { - t.Fatalf("%s: error %q", tt.name, err) - } // verify environment variable in deployment reflect the use of tls being enabled. if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify { t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got) @@ -146,14 +134,7 @@ func TestDeploymentManifest_WithTLS(t *testing.T) { } func TestServiceManifest(t *testing.T) { - o, err := ServiceManifest(v1.NamespaceDefault) - if err != nil { - t.Fatalf("error %q", err) - } - var svc v1.Service - if err := yaml.Unmarshal([]byte(o), &svc); err != nil { - t.Fatalf("error %q", err) - } + svc := Service(v1.NamespaceDefault) if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault { t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) @@ -161,7 +142,7 @@ func TestServiceManifest(t *testing.T) { } func TestSecretManifest(t *testing.T) { - o, err := SecretManifest(&Options{ + obj, err := Secret(&Options{ VerifyTLS: true, EnableTLS: true, Namespace: v1.NamespaceDefault, @@ -174,11 +155,6 @@ func TestSecretManifest(t *testing.T) { t.Fatalf("error %q", err) } - var obj v1.Secret - if err := yaml.Unmarshal([]byte(o), &obj); err != nil { - t.Fatalf("error %q", err) - } - if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault { t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) } @@ -362,13 +338,13 @@ func TestInstall_canary(t *testing.T) { func TestUpgrade(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" serviceAccount := "newServiceAccount" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace:v1.0.0", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) - existingService := service(v1.NamespaceDefault) + existingService := generateService(v1.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -403,7 +379,7 @@ func TestUpgrade(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false, @@ -446,13 +422,13 @@ func TestUpgrade_serviceNotFound(t *testing.T) { func TestUgrade_newerVersion(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" serviceAccount := "newServiceAccount" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace:v100.5.0", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) - existingService := service(v1.NamespaceDefault) + existingService := generateService(v1.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -506,13 +482,13 @@ func TestUgrade_newerVersion(t *testing.T) { func TestUpgrade_identical(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" serviceAccount := "newServiceAccount" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace:v2.0.0", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) - existingService := service(v1.NamespaceDefault) + existingService := generateService(v1.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -547,13 +523,13 @@ func TestUpgrade_identical(t *testing.T) { func TestUpgrade_canaryClient(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:canary" serviceAccount := "newServiceAccount" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace:v1.0.0", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) - existingService := service(v1.NamespaceDefault) + existingService := generateService(v1.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -588,13 +564,13 @@ func TestUpgrade_canaryClient(t *testing.T) { func TestUpgrade_canaryServer(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" serviceAccount := "newServiceAccount" - existingDeployment, _ := deployment(&Options{ + existingDeployment, _ := generateDeployment(&Options{ Namespace: v1.NamespaceDefault, ImageSpec: "imageToReplace:canary", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) - existingService := service(v1.NamespaceDefault) + existingService := generateService(v1.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -634,7 +610,8 @@ func tlsTestFile(t *testing.T, path string) string { } return path } -func TestDeploymentManifest_WithNodeSelectors(t *testing.T) { + +func TestDeployment_WithNodeSelectors(t *testing.T) { tests := []struct { opts Options name string @@ -658,15 +635,11 @@ func TestDeploymentManifest_WithNodeSelectors(t *testing.T) { }, } for _, tt := range tests { - o, err := DeploymentManifest(&tt.opts) + d, err := Deployment(&tt.opts) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } - var d v1beta1.Deployment - if err := yaml.Unmarshal([]byte(o), &d); err != nil { - t.Fatalf("%s: error %q", tt.name, err) - } // Verify that environment variables in Deployment reflect the use of TLS being enabled. got := d.Spec.Template.Spec.NodeSelector for k, v := range tt.expect { @@ -676,7 +649,8 @@ func TestDeploymentManifest_WithNodeSelectors(t *testing.T) { } } } -func TestDeploymentManifest_WithSetValues(t *testing.T) { + +func TestDeployment_WithSetValues(t *testing.T) { tests := []struct { opts Options name string @@ -703,11 +677,17 @@ func TestDeploymentManifest_WithSetValues(t *testing.T) { }, } for _, tt := range tests { - o, err := DeploymentManifest(&tt.opts) + d, err := Deployment(&tt.opts) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } - values, err := chartutil.ReadValues([]byte(o)) + + o, err := yaml.Marshal(d) + if err != nil { + t.Errorf("Error marshaling Deployment: %s", err) + } + + values, err := chartutil.ReadValues(o) if err != nil { t.Errorf("Error converting Deployment manifest to Values: %s", err) }