From 9763a8933577be732f2eb1787ccb80401384930a Mon Sep 17 00:00:00 2001
From: Matt Butcher <mbutcher@engineyard.com>
Date: Fri, 15 Jan 2016 17:26:00 -0700
Subject: [PATCH] feat(*): deploy dry-run now works

---
 cmd/deploy.go                     |  24 ++++--
 cmd/helm.go                       |   8 +-
 deploy/deploy.go                  |  27 ++++++-
 testdata/guestbook/README.md      | 127 ++++++++++++++++++++++++++++++
 testdata/guestbook/guestbook.yaml |  12 +++
 5 files changed, 189 insertions(+), 9 deletions(-)
 create mode 100644 testdata/guestbook/README.md
 create mode 100644 testdata/guestbook/guestbook.yaml

diff --git a/cmd/deploy.go b/cmd/deploy.go
index 8126ca4e4..80faeea6d 100644
--- a/cmd/deploy.go
+++ b/cmd/deploy.go
@@ -1,21 +1,35 @@
 package main
 
 import (
+	"encoding/json"
 	"errors"
 
 	dep "github.com/deis/helm-dm/deploy"
 	"github.com/deis/helm-dm/format"
 )
 
-func deploy(cfg *dep.Deployment, dry bool) error {
-	if dry {
-		format.Error("Not implemented: --dry-run")
-	}
+func deploy(cfg *dep.Deployment, host string, dry bool) error {
 	if cfg.Filename == "" {
 		return errors.New("A filename must be specified. For a tar archive, this is the name of the root template in the archive.")
 	}
 
-	if err := cfg.Commit(); err != nil {
+	if err := cfg.Prepare(); err != nil {
+		format.Error("Failed to prepare deployment: %s", err)
+		return err
+	}
+
+	// For a dry run, print the template and exit.
+	if dry {
+		format.Info("Template prepared for %s", cfg.Template.Name)
+		data, err := json.MarshalIndent(cfg.Template, "", "\t")
+		if err != nil {
+			return err
+		}
+		format.Msg(string(data))
+		return nil
+	}
+
+	if err := cfg.Commit(host); err != nil {
 		format.Error("Failed to commit deployment: %s", err)
 		return err
 	}
diff --git a/cmd/helm.go b/cmd/helm.go
index d0edde4bb..946b67eb7 100644
--- a/cmd/helm.go
+++ b/cmd/helm.go
@@ -89,7 +89,7 @@ func commands() []cli.Command {
 					d.Input = os.Stdin
 				}
 
-				if err := deploy(d, c.Bool("dry-run")); err != nil {
+				if err := deploy(d, c.String("host"), c.Bool("dry-run")); err != nil {
 					format.Error("%s (Try running 'helm doctor')", err)
 					os.Exit(1)
 				}
@@ -119,6 +119,12 @@ func commands() []cli.Command {
 					Usage: "The default repository",
 					Value: "kubernetes/application-dm-templates",
 				},
+				cli.StringFlag{
+					Name:   "host,u",
+					Usage:  "The URL of the DM server.",
+					EnvVar: "HELM_HOST",
+					Value:  "https://localhost:8181/FIXME_NOT_RIGHT",
+				},
 			},
 		},
 		{
diff --git a/deploy/deploy.go b/deploy/deploy.go
index 038c7eafa..09e6c375f 100644
--- a/deploy/deploy.go
+++ b/deploy/deploy.go
@@ -13,8 +13,15 @@ import (
 	"github.com/kubernetes/deployment-manager/registry"
 )
 
+// Deployer is capable of deploying an object to a back-end.
 type Deployer interface {
-	Commit() error
+	// Prepare prepares the local side of a deployment.
+	Prepare() error
+
+	// Commit pushes a deployment and checks that it is completed.
+	//
+	// This sends the data to the given host.
+	Commit(host string) error
 }
 
 // Deployment describes a deployment of a package.
@@ -31,10 +38,16 @@ type Deployment struct {
 	Input *os.File
 	// Repository is the location of the templates.
 	Repository string
+
+	// The template, typically generated by the Deployment.
+	Template *common.Template
 }
 
-// Commit prepares the Deployment and then commits it to the remote processor.
-func (d *Deployment) Commit() error {
+// Prepare loads templates and checks for client-side errors.
+//
+// This will generate the Template based on other information.
+func (d *Deployment) Prepare() error {
+
 	tpl, err := d.resolveTemplate()
 	if err != nil {
 		return err
@@ -45,6 +58,13 @@ func (d *Deployment) Commit() error {
 		tpl.Name = d.Name
 	}
 
+	d.Template = tpl
+
+	return nil
+}
+
+// Commit prepares the Deployment and then commits it to the remote processor.
+func (d *Deployment) Commit(host string) error {
 	return nil
 }
 
@@ -129,6 +149,7 @@ func buildTemplateFromType(t *registry.Type, reg string, props map[string]interf
 	}, nil
 }
 
+// getGitRegistry returns a registry object for a name.
 func getGitRegistry(reg string) (registry.Registry, error) {
 	s := strings.SplitN(reg, "/", 3)
 	if len(s) < 2 {
diff --git a/testdata/guestbook/README.md b/testdata/guestbook/README.md
new file mode 100644
index 000000000..b48dd5933
--- /dev/null
+++ b/testdata/guestbook/README.md
@@ -0,0 +1,127 @@
+T**Testing fork of the guestbook example.**
+
+# Guestbook Example
+
+Welcome to the Guestbook example. It shows you how to build and reuse
+parameterized templates.
+
+## Prerequisites
+
+First, make sure DM is installed in your Kubernetes cluster and that the
+Guestbook example is deployed by following the instructions in the top level
+[README.md](../../README.md).
+
+## Understanding the Guestbook example
+
+Let's take a closer look at the configuration used by the Guestbook example.
+
+### Replicated services
+
+The typical design pattern for microservices in Kubernetes is to create a
+replication controller and a service with the same selector, so that the service
+exposes ports from the pods managed by the replication controller.
+
+We have created a parameterized template for this kind of replicated service 
+called [Replicated Service](../../templates/replicatedservice/v1), and we use it
+three times in the Guestbook example.
+
+The template is defined by a
+[Python script](../../templates/replicatedservice/v1/replicatedservice.py). It 
+also has a [schema](../../templates/replicatedservice/v1/replicatedservice.py.schema).
+Schemas are optional. If provided, they are used to validate template invocations
+that appear in configurations.
+
+For more information about templates and schemas, see the
+[design document](../../docs/design/design.md#templates).
+
+### The Guestbook application
+The Guestbook application consists of 2 microservices: a front end and a Redis 
+cluster.
+
+#### The front end
+
+The front end is a replicated service with 3 replicas:
+
+```
+- name: frontend
+  type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
+  properties:
+    service_port: 80
+    container_port: 80
+    external_service: true
+    replicas: 3
+    image: gcr.io/google_containers/example-guestbook-php-redis:v3
+```
+
+(Note that we use the URL for a specific version of the template replicatedservice.py, 
+not just the template name.)
+
+#### The Redis cluster
+
+The Redis cluster consists of two replicated services: a master with a single replica
+and the slaves with 2 replicas. It's defined by [this template](../../templates/redis/v1/redis.jinja), 
+which is a [Jinja](http://jinja.pocoo.org/) file with a [schema](../../templates/redis/v1/redis.jinja.schema).
+
+```
+{% set REDIS_PORT = 6379 %}
+{% set WORKERS = properties['workers'] or 2 %}
+
+resources:
+- name: redis-master
+  type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
+  properties:
+    # This has to be overwritten since service names are hard coded in the code
+    service_name: redis-master
+    service_port: {{ REDIS_PORT }}
+    target_port: {{ REDIS_PORT }}
+    container_port: {{ REDIS_PORT }}
+    replicas: 1
+    container_name: master
+    image: redis
+
+- name: redis-slave
+  type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
+  properties:
+    # This has to be overwritten since service names are hard coded in the code
+    service_name: redis-slave
+    service_port: {{ REDIS_PORT }}
+    container_port: {{ REDIS_PORT }}
+    replicas: {{ WORKERS }}
+    container_name: worker
+    image: kubernetes/redis-slave:v2
+    # An example of how to specify env variables.
+    env:
+    - name: GET_HOSTS_FROM
+      value: env
+    - name: REDIS_MASTER_SERVICE_HOST
+      value: redis-master
+```
+
+### Displaying types
+
+You can see both the both primitive types and the templates you've deployed to the
+cluster using the `deployed-types` command:
+
+```
+dm deployed-types 
+
+["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py"]
+```
+
+This output shows 2 primitive types (Service and ReplicationController), and 2
+templates (redis.jinja and one imported from github named replicatedservice.py).
+
+You can also see where a specific type is being used with the `deployed-instances` command:
+
+```
+dm deployed-instances Service
+[{"name":"frontend-service","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[0].resources[0]"},{"name":"redis-master","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[1].resources[0].resources[0]"},{"name":"redis-slave","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[1].resources[1].resources[0]"}]
+```
+
+This output describes the deployment and manifest, as well as the JSON paths to
+the instances of the type within the layout.
+
+For more information about deployments, manifests and layouts, see the
+[design document](../../docs/design/design.md#api-model).
+
+
diff --git a/testdata/guestbook/guestbook.yaml b/testdata/guestbook/guestbook.yaml
new file mode 100644
index 000000000..9a31d2489
--- /dev/null
+++ b/testdata/guestbook/guestbook.yaml
@@ -0,0 +1,12 @@
+resources:
+- name: frontend
+  type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v1
+  properties:
+    service_port: 80
+    container_port: 80
+    external_service: true
+    replicas: 3
+    image: gcr.io/google_containers/example-guestbook-php-redis:v3
+- name: redis
+  type: github.com/kubernetes/application-dm-templates/storage/redis:v1
+  properties: null
-- 
GitLab