Browse code

Deployments POC

Paul Morie authored on 2014/09/17 00:55:46
Showing 28 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,276 @@
0
+{
1
+   "id":"guestbook-deployment",
2
+   "kind":"Config",
3
+   "name":"guestbook-d",
4
+   "description":"A simple guestbook application configuration",
5
+   "items":[
6
+      {
7
+         "id":"frontend",
8
+         "kind":"Service",
9
+         "apiVersion":"v1beta1",
10
+         "port":5432,
11
+         "selector":{
12
+            "name":"frontend"
13
+         }
14
+      },
15
+      {
16
+         "id":"redismaster",
17
+         "kind":"Service",
18
+         "apiVersion":"v1beta1",
19
+         "port":10000,
20
+         "selector":{
21
+            "name":"redis-master"
22
+         }
23
+      },
24
+      {
25
+         "id":"redisslave",
26
+         "kind":"Service",
27
+         "apiVersion":"v1beta1",
28
+         "port":10001,
29
+         "labels":{
30
+            "name":"redisslave"
31
+         },
32
+         "selector":{
33
+            "name":"redisslave"
34
+         }
35
+      },
36
+      {
37
+         "id":"redis-master-2",
38
+         "kind":"Pod",
39
+         "apiVersion":"v1beta1",
40
+         "desiredState":{
41
+            "manifest":{
42
+               "version":"v1beta1",
43
+               "id":"redis-master-2",
44
+               "containers":[
45
+                  {
46
+                     "name":"master",
47
+                     "image":"dockerfile/redis",
48
+                     "env":[
49
+                        {
50
+                           "name":"REDIS_PASSWORD",
51
+                           "value":"secret"
52
+                        }
53
+                     ],
54
+                     "ports":[
55
+                        {
56
+                           "containerPort":6379
57
+                        }
58
+                     ]
59
+                  }
60
+               ]
61
+            }
62
+         }
63
+      },
64
+      {
65
+         "id":"frontend-config",
66
+         "kind":"DeploymentConfig",
67
+         "apiVersion":"v1beta1",
68
+         "triggerPolicy":"manual",
69
+         "template":{
70
+            "strategy":{
71
+               "type":"customPod",
72
+               "customPod":{
73
+                  "image":"127.0.0.1:5000/openshift/kube-deploy"
74
+               }
75
+            },
76
+            "controllerTemplate":{
77
+               "replicas":1,
78
+               "replicaSelector":{
79
+                  "name":"frontend"
80
+               },
81
+               "podTemplate":{
82
+                  "desiredState":{
83
+                     "manifest":{
84
+                        "version":"v1beta1",
85
+                        "id":"frontendController",
86
+                        "containers":[
87
+                           {
88
+                              "name":"php-redis",
89
+                              "image":"brendanburns/php-redis",
90
+                              "env":[
91
+                                 {
92
+                                    "name":"ADMIN_USERNAME",
93
+                                    "value":"admin"
94
+                                 },
95
+                                 {
96
+                                    "name":"ADMIN_PASSWORD",
97
+                                    "value":"secret"
98
+                                 },
99
+                                 {
100
+                                    "name":"REDIS_PASSWORD",
101
+                                    "value":"secret"
102
+                                 }
103
+                              ],
104
+                              "ports":[
105
+                                 {
106
+                                    "containerPort":80,
107
+                                    "hostPort":8000
108
+                                 }
109
+                              ]
110
+                           }
111
+                        ]
112
+                     }
113
+                  },
114
+                  "labels":{
115
+                     "name":"frontend"
116
+                  }
117
+               }
118
+            }
119
+         }
120
+      },
121
+      {
122
+         "id":"redisslave-config",
123
+         "kind":"DeploymentConfig",
124
+         "apiVersion":"v1beta1",
125
+         "triggerPolicy":"manual",
126
+         "template":{
127
+            "strategy":{
128
+               "type":"customPod",
129
+               "customPod":{
130
+                  "image":"127.0.0.1:5000/openshift/kube-deploy"
131
+               }
132
+            },
133
+            "controllerTemplate":{
134
+               "replicas":2,
135
+               "replicaSelector":{
136
+                  "name":"redisslave"
137
+               },
138
+               "podTemplate":{
139
+                  "desiredState":{
140
+                     "manifest":{
141
+                        "version":"v1beta1",
142
+                        "id":"redisSlaveController",
143
+                        "containers":[
144
+                           {
145
+                              "name":"slave",
146
+                              "image":"brendanburns/redis-slave",
147
+                              "env":[
148
+                                 {
149
+                                    "name":"REDIS_PASSWORD",
150
+                                    "value":"secret"
151
+                                 }
152
+                              ],
153
+                              "ports":[
154
+                                 {
155
+                                    "containerPort":6379,
156
+                                    "hostPort":6380
157
+                                 }
158
+                              ]
159
+                           }
160
+                        ]
161
+                     }
162
+                  },
163
+                  "labels":{
164
+                     "name":"redisslave"
165
+                  }
166
+               }
167
+            }
168
+         }
169
+      },
170
+      {
171
+         "id":"frontend-deploy",
172
+         "kind":"Deployment",
173
+         "apiVersion":"v1beta1",
174
+         "triggerPolicy":"manual",
175
+         "configId":"frontend-config",
176
+         "strategy":{
177
+            "type":"customPod",
178
+            "customPod":{
179
+               "image":"127.0.0.1:5000/openshift/kube-deploy"
180
+            }
181
+         },
182
+         "controllerTemplate":{
183
+            "replicas":1,
184
+            "replicaSelector":{
185
+               "name":"frontend"
186
+            },
187
+            "podTemplate":{
188
+               "desiredState":{
189
+                  "manifest":{
190
+                     "version":"v1beta1",
191
+                     "id":"frontendController",
192
+                     "containers":[
193
+                        {
194
+                           "name":"php-redis",
195
+                           "image":"brendanburns/php-redis",
196
+                           "env":[
197
+                              {
198
+                                 "name":"ADMIN_USERNAME",
199
+                                 "value":"admin"
200
+                              },
201
+                              {
202
+                                 "name":"ADMIN_PASSWORD",
203
+                                 "value":"secret"
204
+                              },
205
+                              {
206
+                                 "name":"REDIS_PASSWORD",
207
+                                 "value":"secret"
208
+                              }
209
+                           ],
210
+                           "ports":[
211
+                              {
212
+                                 "containerPort":80,
213
+                                 "hostPort":8000
214
+                              }
215
+                           ]
216
+                        }
217
+                     ]
218
+                  }
219
+               },
220
+               "labels":{
221
+                  "name":"frontend"
222
+               }
223
+            }
224
+         }
225
+      },
226
+      {
227
+         "id":"redisslave-deploy",
228
+         "kind":"Deployment",
229
+         "apiVersion":"v1beta1",
230
+         "triggerPolicy":"manual",
231
+         "configId":"redisslave-config",
232
+         "strategy":{
233
+            "type":"customPod",
234
+            "customPod":{
235
+               "image":"127.0.0.1:5000/openshift/kube-deploy"
236
+            }
237
+         },
238
+         "controllerTemplate":{
239
+            "replicas":2,
240
+            "replicaSelector":{
241
+               "name":"redisslave"
242
+            },
243
+            "podTemplate":{
244
+               "desiredState":{
245
+                  "manifest":{
246
+                     "version":"v1beta1",
247
+                     "id":"redisSlaveController",
248
+                     "containers":[
249
+                        {
250
+                           "name":"slave",
251
+                           "image":"brendanburns/redis-slave",
252
+                           "env":[
253
+                              {
254
+                                 "name":"REDIS_PASSWORD",
255
+                                 "value":"secret"
256
+                              }
257
+                           ],
258
+                           "ports":[
259
+                              {
260
+                                 "containerPort":6379,
261
+                                 "hostPort":6380
262
+                              }
263
+                           ]
264
+                        }
265
+                     ]
266
+                  }
267
+               },
268
+               "labels":{
269
+                  "name":"redisslave"
270
+               }
271
+            }
272
+         }
273
+      }
274
+   ]
275
+}
0 276
\ No newline at end of file
1 277
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+FROM scratch
1
+COPY kube-deploy /kube-deploy
2
+CMD ["/kube-deploy"]
0 3
new file mode 100755
... ...
@@ -0,0 +1,6 @@
0
+#!/bin/sh
1
+KUBE_DEPLOY_DIR=$(dirname $(readlink -f 0))
2
+cd $KUBE_DEPLOY_DIR
3
+DOCKERUSER=${DOCKERUSER:-openshift}
4
+sudo docker build -t $DOCKERUSER/kube-deploy .
5
+rm kube-deploy
0 6
new file mode 100755
... ...
@@ -0,0 +1,5 @@
0
+#!/bin/sh
1
+KUBE_DEPLOY_DIR=$(dirname $(readlink -f 0))
2
+cd $KUBE_DEPLOY_DIR
3
+source ../../../hack/config-go.sh
4
+CGO_ENABLED=0 go build -a -ldflags '-s' cmd/kube-deploy.go
0 5
new file mode 100644
... ...
@@ -0,0 +1,112 @@
0
+package main
1
+
2
+import (
3
+	"net/url"
4
+	"os"
5
+
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	kubeclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
10
+	"github.com/golang/glog"
11
+	osclient "github.com/openshift/origin/pkg/client"
12
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
13
+	"gopkg.in/v1/yaml"
14
+)
15
+
16
+func main() {
17
+	util.InitLogs()
18
+	defer util.FlushLogs()
19
+
20
+	var masterServer string
21
+	if len(os.Getenv("KUBERNETES_MASTER")) > 0 {
22
+		masterServer = os.Getenv("KUBERNETES_MASTER")
23
+	} else {
24
+		masterServer = "http://localhost:8080"
25
+	}
26
+	_, err := url.Parse(masterServer)
27
+	if err != nil {
28
+		glog.Fatalf("Unable to parse %v as a URL\n", err)
29
+	}
30
+
31
+	client, err := kubeclient.New(masterServer, nil)
32
+	if err != nil {
33
+		glog.Errorf("Unable to connect to kubernetes master: %v", err)
34
+		os.Exit(1)
35
+	}
36
+
37
+	osClient, err := osclient.New(masterServer, nil)
38
+	if err != nil {
39
+		glog.Errorf("Unable to connect to openshift master: %v", err)
40
+		os.Exit(1)
41
+	}
42
+
43
+	deployTarget(client, osClient)
44
+}
45
+
46
+func deployTarget(client *kubeclient.Client, osClient osclient.Interface) {
47
+	deploymentID := os.Getenv("KUBERNETES_DEPLOYMENT_ID")
48
+	if len(deploymentID) == 0 {
49
+		glog.Fatal("No deployment id was specified. Expected KUBERNETES_DEPLOYMENT_ID variable.")
50
+		return
51
+	}
52
+	glog.Infof("Retrieving deployment id: %v", deploymentID)
53
+
54
+	var deployment deployapi.Deployment
55
+	var err error
56
+	if deployment, err = osClient.GetDeployment(deploymentID); err != nil {
57
+		glog.Fatalf("An error occurred retrieving the deployment object: %v", err)
58
+		return
59
+	}
60
+
61
+	selector, _ := labels.ParseSelector("deployment=" + deployment.ConfigID)
62
+	replicationControllers, err := client.ListReplicationControllers(selector)
63
+	if err != nil {
64
+		glog.Fatalf("Unable to get list of replication controllers %v\n", err)
65
+		return
66
+	}
67
+
68
+	controller := api.ReplicationController{
69
+		DesiredState: deployment.ControllerTemplate,
70
+		Labels:       map[string]string{"deployment": deployment.ConfigID},
71
+	}
72
+	if controller.DesiredState.PodTemplate.Labels == nil {
73
+		controller.DesiredState.PodTemplate.Labels = make(map[string]string)
74
+	}
75
+	controller.DesiredState.PodTemplate.Labels["deployment"] = deployment.ConfigID
76
+
77
+	glog.Info("Creating replication controller: ")
78
+	obj, _ := yaml.Marshal(controller)
79
+	glog.Info(string(obj))
80
+
81
+	if _, err := client.CreateReplicationController(controller); err != nil {
82
+		glog.Fatalf("An error occurred creating the replication controller: %v", err)
83
+		return
84
+	}
85
+
86
+	glog.Info("Create replication controller")
87
+
88
+	// For this simple deploy, remove previous replication controllers
89
+	for _, rc := range replicationControllers.Items {
90
+		glog.Info("Stopping replication controller: ")
91
+		obj, _ := yaml.Marshal(rc)
92
+		glog.Info(string(obj))
93
+		rcObj, err1 := client.GetReplicationController(rc.ID)
94
+		if err1 != nil {
95
+			glog.Fatalf("Unable to get replication controller %s - error: %#v\n", rc.ID, err1)
96
+		}
97
+		rcObj.DesiredState.Replicas = 0
98
+		_, err := client.UpdateReplicationController(rcObj)
99
+		if err != nil {
100
+			glog.Fatalf("Unable to stop replication controller %s - error: %#v\n", rc.ID, err)
101
+		}
102
+	}
103
+
104
+	for _, rc := range replicationControllers.Items {
105
+		glog.Infof("Deleting replication controller %s", rc.ID)
106
+		err := client.DeleteReplicationController(rc.ID)
107
+		if err != nil {
108
+			glog.Fatalf("Unable to remove replication controller %s - error: %#v\n", rc.ID, err)
109
+		}
110
+	}
111
+}
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
7 7
 	buildapi "github.com/openshift/origin/pkg/build/api"
8 8
 	_ "github.com/openshift/origin/pkg/build/api/v1beta1"
9
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
+	_ "github.com/openshift/origin/pkg/deploy/api/v1beta1"
9 11
 	imageapi "github.com/openshift/origin/pkg/image/api"
10 12
 	_ "github.com/openshift/origin/pkg/image/api/v1beta1"
11 13
 )
... ...
@@ -17,6 +19,8 @@ type Interface interface {
17 17
 	ImageInterface
18 18
 	ImageRepositoryInterface
19 19
 	ImageRepositoryMappingInterface
20
+	DeploymentInterface
21
+	DeploymentConfigInterface
20 22
 }
21 23
 
22 24
 // BuildInterface exposes methods on Build resources.
... ...
@@ -57,6 +61,24 @@ type ImageRepositoryMappingInterface interface {
57 57
 	CreateImageRepositoryMapping(*imageapi.ImageRepositoryMapping) error
58 58
 }
59 59
 
60
+// DeploymentConfigInterface contains methods for working with DeploymentConfigs
61
+type DeploymentConfigInterface interface {
62
+	ListDeploymentConfigs(selector labels.Selector) (*deployapi.DeploymentConfigList, error)
63
+	GetDeploymentConfig(id string) (*deployapi.DeploymentConfig, error)
64
+	CreateDeploymentConfig(*deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
65
+	UpdateDeploymentConfig(*deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
66
+	DeleteDeploymentConfig(string) error
67
+}
68
+
69
+// DeploymentInterface contains methods for working with Deployments
70
+type DeploymentInterface interface {
71
+	ListDeployments(selector labels.Selector) (*deployapi.DeploymentList, error)
72
+	GetDeployment(id string) (*deployapi.Deployment, error)
73
+	CreateDeployment(*deployapi.Deployment) (*deployapi.Deployment, error)
74
+	UpdateDeployment(*deployapi.Deployment) (*deployapi.Deployment, error)
75
+	DeleteDeployment(string) error
76
+}
77
+
60 78
 // Client is an OpenShift client object
61 79
 type Client struct {
62 80
 	*kubeclient.RESTClient
... ...
@@ -195,3 +217,69 @@ func (c *Client) UpdateImageRepository(repo *imageapi.ImageRepository) (result *
195 195
 func (c *Client) CreateImageRepositoryMapping(mapping *imageapi.ImageRepositoryMapping) error {
196 196
 	return c.Post().Path("imageRepositoryMappings").Body(mapping).Do().Error()
197 197
 }
198
+
199
+// ListDeploymentConfigs takes a selector, and returns the list of deploymentConfigs that match that selector
200
+func (c *Client) ListDeploymentConfigs(selector labels.Selector) (result *deployapi.DeploymentConfigList, err error) {
201
+	result = &deployapi.DeploymentConfigList{}
202
+	err = c.Get().Path("deploymentConfigs").SelectorParam("labels", selector).Do().Into(result)
203
+	return
204
+}
205
+
206
+// GetDeploymentConfig returns information about a particular deploymentConfig
207
+func (c *Client) GetDeploymentConfig(id string) (result *deployapi.DeploymentConfig, err error) {
208
+	result = &deployapi.DeploymentConfig{}
209
+	err = c.Get().Path("deploymentConfigs").Path(id).Do().Into(result)
210
+	return
211
+}
212
+
213
+// CreateDeploymentConfig creates a new deploymentConfig
214
+func (c *Client) CreateDeploymentConfig(deploymentConfig *deployapi.DeploymentConfig) (result *deployapi.DeploymentConfig, err error) {
215
+	result = &deployapi.DeploymentConfig{}
216
+	err = c.Post().Path("deploymentConfigs").Body(deploymentConfig).Do().Into(result)
217
+	return
218
+}
219
+
220
+// UpdateDeploymentConfig updates an existing deploymentConfig
221
+func (c *Client) UpdateDeploymentConfig(deploymentConfig *deployapi.DeploymentConfig) (result *deployapi.DeploymentConfig, err error) {
222
+	result = &deployapi.DeploymentConfig{}
223
+	err = c.Put().Path("deploymentConfigs").Path(deploymentConfig.ID).Body(deploymentConfig).Do().Into(result)
224
+	return
225
+}
226
+
227
+// DeleteDeploymentConfig deletes an existing deploymentConfig.
228
+func (c *Client) DeleteDeploymentConfig(id string) error {
229
+	return c.Delete().Path("deploymentConfigs").Path(id).Do().Error()
230
+}
231
+
232
+// ListDeployments takes a selector, and returns the list of deployments that match that selector
233
+func (c *Client) ListDeployments(selector labels.Selector) (result *deployapi.DeploymentList, err error) {
234
+	result = &deployapi.DeploymentList{}
235
+	err = c.Get().Path("deployments").SelectorParam("labels", selector).Do().Into(result)
236
+	return
237
+}
238
+
239
+// GetDeployment returns information about a particular deployment
240
+func (c *Client) GetDeployment(id string) (result *deployapi.Deployment, err error) {
241
+	result = &deployapi.Deployment{}
242
+	err = c.Get().Path("deployments").Path(id).Do().Into(result)
243
+	return
244
+}
245
+
246
+// CreateDeployment creates a new deployment
247
+func (c *Client) CreateDeployment(deployment *deployapi.Deployment) (result *deployapi.Deployment, err error) {
248
+	result = &deployapi.Deployment{}
249
+	err = c.Post().Path("deployments").Body(deployment).Do().Into(result)
250
+	return
251
+}
252
+
253
+// UpdateDeployment updates an existing deployment
254
+func (c *Client) UpdateDeployment(deployment *deployapi.Deployment) (result *deployapi.Deployment, err error) {
255
+	result = &deployapi.Deployment{}
256
+	err = c.Put().Path("deployments").Path(deployment.ID).Body(deployment).Do().Into(result)
257
+	return
258
+}
259
+
260
+// DeleteDeployment deletes an existing replication deployment.
261
+func (c *Client) DeleteDeployment(id string) error {
262
+	return c.Delete().Path("deployments").Path(id).Do().Error()
263
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
5 5
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
6 6
 	buildapi "github.com/openshift/origin/pkg/build/api"
7
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
7 8
 	imageapi "github.com/openshift/origin/pkg/image/api"
8 9
 )
9 10
 
... ...
@@ -108,3 +109,53 @@ func (c *Fake) CreateImageRepositoryMapping(mapping *imageapi.ImageRepositoryMap
108 108
 	c.Actions = append(c.Actions, FakeAction{Action: "create-imagerepository-mapping"})
109 109
 	return nil
110 110
 }
111
+
112
+func (c *Fake) ListDeploymentConfigs(selector labels.Selector) (*deployapi.DeploymentConfigList, error) {
113
+	c.Actions = append(c.Actions, FakeAction{Action: "list-deploymentconfig"})
114
+	return &deployapi.DeploymentConfigList{}, nil
115
+}
116
+
117
+func (c *Fake) GetDeploymentConfig(id string) (*deployapi.DeploymentConfig, error) {
118
+	c.Actions = append(c.Actions, FakeAction{Action: "get-deploymentconfig"})
119
+	return &deployapi.DeploymentConfig{}, nil
120
+}
121
+
122
+func (c *Fake) CreateDeploymentConfig(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
123
+	c.Actions = append(c.Actions, FakeAction{Action: "create-deploymentconfig"})
124
+	return &deployapi.DeploymentConfig{}, nil
125
+}
126
+
127
+func (c *Fake) UpdateDeploymentConfig(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
128
+	c.Actions = append(c.Actions, FakeAction{Action: "update-deploymentconfig"})
129
+	return &deployapi.DeploymentConfig{}, nil
130
+}
131
+
132
+func (c *Fake) DeleteDeploymentConfig(id string) error {
133
+	c.Actions = append(c.Actions, FakeAction{Action: "delete-deploymentconfig"})
134
+	return nil
135
+}
136
+
137
+func (c *Fake) ListDeployments(selector labels.Selector) (*deployapi.DeploymentList, error) {
138
+	c.Actions = append(c.Actions, FakeAction{Action: "list-deployment"})
139
+	return &deployapi.DeploymentList{}, nil
140
+}
141
+
142
+func (c *Fake) GetDeployment(id string) (*deployapi.Deployment, error) {
143
+	c.Actions = append(c.Actions, FakeAction{Action: "get-deployment"})
144
+	return &deployapi.Deployment{}, nil
145
+}
146
+
147
+func (c *Fake) CreateDeployment(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
148
+	c.Actions = append(c.Actions, FakeAction{Action: "create-deployment"})
149
+	return &deployapi.Deployment{}, nil
150
+}
151
+
152
+func (c *Fake) UpdateDeployment(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
153
+	c.Actions = append(c.Actions, FakeAction{Action: "update-deployment"})
154
+	return &deployapi.Deployment{}, nil
155
+}
156
+
157
+func (c *Fake) DeleteDeployment(id string) error {
158
+	c.Actions = append(c.Actions, FakeAction{Action: "delete-deployment"})
159
+	return nil
160
+}
... ...
@@ -44,6 +44,8 @@ import (
44 44
 	"github.com/openshift/origin/pkg/config"
45 45
 	configapi "github.com/openshift/origin/pkg/config/api"
46 46
 	_ "github.com/openshift/origin/pkg/config/api/v1beta1"
47
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
48
+	deployclient "github.com/openshift/origin/pkg/deploy/client"
47 49
 	imageapi "github.com/openshift/origin/pkg/image/api"
48 50
 )
49 51
 
... ...
@@ -101,6 +103,8 @@ var parser = kubecfg.NewParser(map[string]interface{}{
101 101
 	"imageRepositories":       imageapi.ImageRepository{},
102 102
 	"imageRepositoryMappings": imageapi.ImageRepositoryMapping{},
103 103
 	"config":                  configapi.Config{},
104
+	"deployments":             deployapi.Deployment{},
105
+	"deploymentConfigs":       deployapi.DeploymentConfig{},
104 106
 })
105 107
 
106 108
 func prettyWireStorage() string {
... ...
@@ -198,6 +202,7 @@ func (c *KubeConfig) Run() {
198 198
 		fmt.Printf("Server Version: %#v\n", got)
199 199
 		os.Exit(0)
200 200
 	}
201
+
201 202
 	if c.PreventSkew {
202 203
 		got, err := kubeClient.ServerVersion()
203 204
 		if err != nil {
... ...
@@ -227,6 +232,8 @@ func (c *KubeConfig) Run() {
227 227
 		"images":                  {"Image", client.RESTClient},
228 228
 		"imageRepositories":       {"ImageRepository", client.RESTClient},
229 229
 		"imageRepositoryMappings": {"ImageRepositoryMapping", client.RESTClient},
230
+		"deployments":             {"Deployment", client.RESTClient},
231
+		"deploymentConfigs":       {"DeploymentConfig", client.RESTClient},
230 232
 	}
231 233
 
232 234
 	matchFound := c.executeConfigRequest(method, clients) || c.executeControllerRequest(method, kubeClient) || c.executeAPIRequest(method, clients)
... ...
@@ -445,6 +452,7 @@ func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
445 445
 	// Add Handler calls here to support additional types
446 446
 	build.RegisterPrintHandlers(printer)
447 447
 	image.RegisterPrintHandlers(printer)
448
+	deployclient.RegisterPrintHandlers(printer)
448 449
 
449 450
 	return printer
450 451
 }
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"path"
7 7
 	"time"
8 8
 
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
10 11
 	kubeclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
11 12
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
... ...
@@ -26,6 +27,8 @@ import (
26 26
 	"github.com/google/cadvisor/client"
27 27
 	"github.com/spf13/cobra"
28 28
 
29
+	_ "github.com/openshift/origin/pkg/api"
30
+	_ "github.com/openshift/origin/pkg/api/v1beta1"
29 31
 	"github.com/openshift/origin/pkg/build"
30 32
 	buildapi "github.com/openshift/origin/pkg/build/api"
31 33
 	buildregistry "github.com/openshift/origin/pkg/build/registry/build"
... ...
@@ -35,6 +38,10 @@ import (
35 35
 	"github.com/openshift/origin/pkg/build/webhook/github"
36 36
 	osclient "github.com/openshift/origin/pkg/client"
37 37
 	"github.com/openshift/origin/pkg/cmd/util/docker"
38
+	"github.com/openshift/origin/pkg/deploy"
39
+	deployregistry "github.com/openshift/origin/pkg/deploy/registry/deploy"
40
+	deployconfigregistry "github.com/openshift/origin/pkg/deploy/registry/deployconfig"
41
+	deployetcd "github.com/openshift/origin/pkg/deploy/registry/etcd"
38 42
 	imageetcd "github.com/openshift/origin/pkg/image/registry/etcd"
39 43
 	"github.com/openshift/origin/pkg/image/registry/image"
40 44
 	"github.com/openshift/origin/pkg/image/registry/imagerepository"
... ...
@@ -134,6 +141,7 @@ func (c *config) startAllInOne() {
134 134
 	c.runScheduler()
135 135
 	c.runReplicationController()
136 136
 	c.runBuildController()
137
+	c.runDeploymentController()
137 138
 
138 139
 	select {}
139 140
 }
... ...
@@ -144,6 +152,7 @@ func (c *config) startMaster() {
144 144
 	c.runScheduler()
145 145
 	c.runReplicationController()
146 146
 	c.runBuildController()
147
+	c.runDeploymentController()
147 148
 
148 149
 	select {}
149 150
 }
... ...
@@ -167,6 +176,7 @@ func (c *config) runApiserver() {
167 167
 	etcdClient, etcdServers := c.getEtcdClient()
168 168
 
169 169
 	imageRegistry := imageetcd.NewEtcd(etcdClient)
170
+	deployEtcd := deployetcd.NewEtcd(etcdClient)
170 171
 
171 172
 	// initialize OpenShift API
172 173
 	storage := map[string]apiserver.RESTStorage{
... ...
@@ -176,6 +186,8 @@ func (c *config) runApiserver() {
176 176
 		"imageRepositories":       imagerepository.NewREST(imageRegistry),
177 177
 		"imageRepositoryMappings": imagerepositorymapping.NewREST(imageRegistry, imageRegistry),
178 178
 		"templateConfigs":         template.NewStorage(),
179
+		"deployments":             deployregistry.NewREST(deployEtcd),
180
+		"deploymentConfigs":       deployconfigregistry.NewREST(deployEtcd),
179 181
 	}
180 182
 
181 183
 	osMux := http.NewServeMux()
... ...
@@ -327,6 +339,17 @@ func (c *config) runBuildController() {
327 327
 	buildController.Run(10 * time.Second)
328 328
 }
329 329
 
330
+func (c *config) runDeploymentController() {
331
+	env := []api.EnvVar{
332
+		api.EnvVar{Name: "KUBERNETES_MASTER", Value: "http://" + c.ListenAddr},
333
+	}
334
+	kubeClient := c.getKubeClient()
335
+	osClient := c.getOsClient()
336
+
337
+	deployController := deploy.NewDeploymentController(kubeClient, osClient, env)
338
+	deployController.Run(10 * time.Second)
339
+}
340
+
330 341
 func env(key string, defaultValue string) string {
331 342
 	val := os.Getenv(key)
332 343
 	if len(val) == 0 {
333 344
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package api
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
4
+)
5
+
6
+func init() {
7
+	runtime.AddKnownTypes("",
8
+		DeploymentList{},
9
+		Deployment{},
10
+		DeploymentConfigList{},
11
+		DeploymentConfig{},
12
+	)
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+package api
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+)
5
+
6
+// CustomPodDeploymentStrategy describes a deployment carried out by a custom pod.
7
+type CustomPodDeploymentStrategy struct {
8
+	Image       string       `json:"image,omitempty" yaml:"image,omitempty"`
9
+	Environment []api.EnvVar `json:"environment,omitempty" yaml:"environment,omitempty"`
10
+}
11
+
12
+// DeploymentStrategy describes how to perform a deployment.
13
+type DeploymentStrategy struct {
14
+	Type      string                       `json:"type,omitempty" yaml:"type,omitempty"`
15
+	CustomPod *CustomPodDeploymentStrategy `json:"customPod,omitempty" yaml:"customPod,omitempty"`
16
+}
17
+
18
+// DeploymentTemplate contains all the necessary information to create a Deployment from a
19
+// DeploymentStrategy.
20
+type DeploymentTemplate struct {
21
+	Strategy           DeploymentStrategy             `json:"strategy,omitempty" yaml:"strategy,omitempty"`
22
+	ControllerTemplate api.ReplicationControllerState `json:"controllerTemplate,omitempty" yaml:"controllerTemplate,omitempty"`
23
+}
24
+
25
+// DeploymentState decribes the possible states a Deployment can be in.
26
+type DeploymentState string
27
+
28
+const (
29
+	DeploymentNew      DeploymentState = "new"
30
+	DeploymentPending  DeploymentState = "pending"
31
+	DeploymentRunning  DeploymentState = "running"
32
+	DeploymentComplete DeploymentState = "complete"
33
+	DeploymentFailed   DeploymentState = "failed"
34
+)
35
+
36
+// A Deployment represents a single unique realization of a DeploymentConfig.
37
+type Deployment struct {
38
+	api.JSONBase       `json:",inline" yaml:",inline"`
39
+	Labels             map[string]string              `json:"labels,omitempty" yaml:"labels,omitempty"`
40
+	Strategy           DeploymentStrategy             `json:"strategy,omitempty" yaml:"strategy,omitempty"`
41
+	ControllerTemplate api.ReplicationControllerState `json:"controllerTemplate,omitempty" yaml:"controllerTemplate,omitempty"`
42
+	State              DeploymentState                `json:"state,omitempty" yaml:"state,omitempty"`
43
+	ConfigID           string                         `json:"configId,omitempty" yaml:"configId,omitempty"`
44
+}
45
+
46
+// DeploymentTriggerPolicy describes the possible triggers that result in a new Deployment.
47
+type DeploymentTriggerPolicy struct {
48
+	Type DeploymentTriggerType `json:"type,omitempty" yaml:"type,omitempty"`
49
+}
50
+
51
+type DeploymentTriggerType string
52
+
53
+const (
54
+	DeploymentTriggerOnImageChange  DeploymentTriggerType = "image-change"
55
+	DeploymentTriggerOnConfigChange DeploymentTriggerType = "config-change"
56
+	DeploymentTriggerManual         DeploymentTriggerType = "manual"
57
+)
58
+
59
+// DeploymentConfig represents a configuration for a single deployment of a replication controller:
60
+// what the template for the deployment, how new deployments are triggered, what the current
61
+// deployed state is.
62
+type DeploymentConfig struct {
63
+	api.JSONBase  `json:",inline" yaml:",inline"`
64
+	Labels        map[string]string              `json:"labels,omitempty" yaml:"labels,omitempty"`
65
+	TriggerPolicy DeploymentTriggerPolicy        `json:"triggerPolicy,omitempty" yaml:"triggerPolicy,omitempty"`
66
+	Template      DeploymentTemplate             `json:"template,omitempty" yaml:"template,omitempty"`
67
+	CurrentState  api.ReplicationControllerState `json:"currentState" yaml:"currentState,omitempty"`
68
+}
69
+
70
+// A DeploymentConfigList is a collection of deployment configs
71
+type DeploymentConfigList struct {
72
+	api.JSONBase `json:",inline" yaml:",inline"`
73
+	Items        []DeploymentConfig `json:"items,omitempty" yaml:"items,omitempty"`
74
+}
75
+
76
+// A DeploymentList is a collection of deployments.
77
+type DeploymentList struct {
78
+	api.JSONBase `json:",inline" yaml:",inline"`
79
+	Items        []Deployment `json:"items,omitempty" yaml:"items,omitempty"`
80
+}
0 81
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package v1beta1
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
4
+)
5
+
6
+func init() {
7
+	runtime.AddKnownTypes("v1beta1",
8
+		DeploymentList{},
9
+		Deployment{},
10
+		DeploymentConfigList{},
11
+		DeploymentConfig{},
12
+	)
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+package v1beta1
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+)
5
+
6
+// CustomPodDeploymentStrategy describes a deployment carried out by a custom pod.
7
+type CustomPodDeploymentStrategy struct {
8
+	Image       string       `json:"image,omitempty" yaml:"image,omitempty"`
9
+	Environment []api.EnvVar `json:"environment,omitempty" yaml:"environment,omitempty"`
10
+}
11
+
12
+// DeploymentStrategy describes how to perform a deployment.
13
+type DeploymentStrategy struct {
14
+	Type      string                       `json:"type,omitempty" yaml:"type,omitempty"`
15
+	CustomPod *CustomPodDeploymentStrategy `json:"customPod,omitempty" yaml:"customPod,omitempty"`
16
+}
17
+
18
+// DeploymentTemplate contains all the necessary information to create a Deployment from a
19
+// DeploymentStrategy.
20
+type DeploymentTemplate struct {
21
+	Strategy           DeploymentStrategy             `json:"strategy,omitempty" yaml:"strategy,omitempty"`
22
+	ControllerTemplate api.ReplicationControllerState `json:"controllerTemplate,omitempty" yaml:"controllerTemplate,omitempty"`
23
+}
24
+
25
+// DeploymentState decribes the possible states a Deployment can be in.
26
+type DeploymentState string
27
+
28
+const (
29
+	DeploymentNew      DeploymentState = "new"
30
+	DeploymentPending  DeploymentState = "pending"
31
+	DeploymentRunning  DeploymentState = "running"
32
+	DeploymentComplete DeploymentState = "complete"
33
+	DeploymentFailed   DeploymentState = "failed"
34
+)
35
+
36
+// A Deployment represents a single unique realization of a DeploymentConfig.
37
+type Deployment struct {
38
+	api.JSONBase       `json:",inline" yaml:",inline"`
39
+	Labels             map[string]string              `json:"labels,omitempty" yaml:"labels,omitempty"`
40
+	Strategy           DeploymentStrategy             `json:"strategy,omitempty" yaml:"strategy,omitempty"`
41
+	ControllerTemplate api.ReplicationControllerState `json:"controllerTemplate,omitempty" yaml:"controllerTemplate,omitempty"`
42
+	State              DeploymentState                `json:"state,omitempty" yaml:"state,omitempty"`
43
+	ConfigID           string                         `json:"configId,omitempty" yaml:"configId,omitempty"`
44
+}
45
+
46
+// DeploymentTriggerPolicy describes the possible triggers that result in a new Deployment.
47
+type DeploymentTriggerPolicy struct {
48
+	Type DeploymentTriggerType `json:"type,omitempty" yaml:"type,omitempty"`
49
+}
50
+
51
+type DeploymentTriggerType string
52
+
53
+const (
54
+	DeploymentTriggerOnImageChange  DeploymentTriggerType = "image-change"
55
+	DeploymentTriggerOnConfigChange DeploymentTriggerType = "config-change"
56
+	DeploymentTriggerManual         DeploymentTriggerType = "manual"
57
+)
58
+
59
+// DeploymentConfig represents a configuration for a single deployment of a replication controller:
60
+// what the template for the deployment, how new deployments are triggered, what the current
61
+// deployed state is.
62
+type DeploymentConfig struct {
63
+	api.JSONBase  `json:",inline" yaml:",inline"`
64
+	Labels        map[string]string              `json:"labels,omitempty" yaml:"labels,omitempty"`
65
+	TriggerPolicy DeploymentTriggerPolicy        `json:"triggerPolicy,omitempty" yaml:"triggerPolicy,omitempty"`
66
+	Template      DeploymentTemplate             `json:"template,omitempty" yaml:"template,omitempty"`
67
+	CurrentState  api.ReplicationControllerState `json:"currentState" yaml:"currentState,omitempty"`
68
+}
69
+
70
+// A DeploymentConfigList is a collection of deployment configs
71
+type DeploymentConfigList struct {
72
+	api.JSONBase `json:",inline" yaml:",inline"`
73
+	Items        []DeploymentConfig `json:"items,omitempty" yaml:"items,omitempty"`
74
+}
75
+
76
+// A DeploymentList is a collection of deployments.
77
+type DeploymentList struct {
78
+	api.JSONBase `json:",inline" yaml:",inline"`
79
+	Items        []Deployment `json:"items,omitempty" yaml:"items,omitempty"`
80
+}
0 81
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package validation
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
4
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
5
+)
6
+
7
+// TODO: These tests validate the ReplicationControllerState in a Deployment or DeploymentConfig.
8
+//       The upstream validation API isn't factored currently to allow this; we'll make a PR to
9
+//       upstream and fix when it goes in.
10
+
11
+func ValidateDeployment(deployment *deployapi.Deployment) errors.ErrorList {
12
+	result := validateDeploymentStrategy(&deployment.Strategy).Prefix("Strategy")
13
+
14
+	// TODO: validate ReplicationControllerState
15
+
16
+	return result
17
+}
18
+
19
+func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy) errors.ErrorList {
20
+	result := errors.ErrorList{}
21
+
22
+	if len(strategy.Type) == 0 {
23
+		result = append(result, errors.NewFieldRequired("Type", ""))
24
+	}
25
+
26
+	if strategy.CustomPod == nil {
27
+		result = append(result, errors.NewFieldRequired("CustomPod", nil))
28
+	} else {
29
+		if len(strategy.CustomPod.Image) == 0 {
30
+			result = append(result, errors.NewFieldRequired("CustomPod.Image", ""))
31
+		}
32
+	}
33
+
34
+	return result
35
+}
36
+
37
+func validateTriggerPolicy(policy *deployapi.DeploymentTriggerPolicy) errors.ErrorList {
38
+	result := errors.ErrorList{}
39
+
40
+	if len(policy.Type) == 0 {
41
+		result = append(result, errors.NewFieldRequired("Type", ""))
42
+	}
43
+
44
+	return result
45
+}
46
+
47
+func ValidateDeploymentConfig(config *deployapi.DeploymentConfig) errors.ErrorList {
48
+	result := errors.ErrorList{}
49
+	result = append(result, validateTriggerPolicy(&config.TriggerPolicy).Prefix("TriggerPolicy")...)
50
+	result = append(result, validateDeploymentStrategy(&config.Template.Strategy).Prefix("Template.Strategy")...)
51
+
52
+	// TODO: validate ReplicationControllerState
53
+
54
+	return result
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,175 @@
0
+package validation
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
6
+	"github.com/openshift/origin/pkg/deploy/api"
7
+)
8
+
9
+// Convenience methods
10
+
11
+func manualTrigger() api.DeploymentTriggerPolicy {
12
+	return api.DeploymentTriggerPolicy{
13
+		Type: api.DeploymentTriggerManual,
14
+	}
15
+}
16
+
17
+func okTemplate() api.DeploymentTemplate {
18
+	return api.DeploymentTemplate{
19
+		Strategy: okStrategy(),
20
+	}
21
+}
22
+
23
+func okStrategy() api.DeploymentStrategy {
24
+	return api.DeploymentStrategy{
25
+		Type:      "customPod",
26
+		CustomPod: okCustomPod(),
27
+	}
28
+}
29
+
30
+func okCustomPod() *api.CustomPodDeploymentStrategy {
31
+	return &api.CustomPodDeploymentStrategy{
32
+		Image: "openshift/kube-deploy",
33
+	}
34
+}
35
+
36
+// TODO: test validation errors for ReplicationControllerTemplates
37
+
38
+func TestValidateDeploymentOK(t *testing.T) {
39
+	errs := ValidateDeployment(&api.Deployment{
40
+		Strategy: okStrategy(),
41
+	})
42
+	if len(errs) > 0 {
43
+		t.Errorf("Unxpected non-empty error list: %#v", errs)
44
+	}
45
+}
46
+
47
+func TestValidateDeploymentMissingFields(t *testing.T) {
48
+	errorCases := map[string]struct {
49
+		D api.Deployment
50
+		T errors.ValidationErrorType
51
+		F string
52
+	}{
53
+		"missing Strategy.Type": {
54
+			api.Deployment{
55
+				Strategy: api.DeploymentStrategy{
56
+					CustomPod: okCustomPod(),
57
+				},
58
+			},
59
+			errors.ValidationErrorTypeRequired,
60
+			"Strategy.Type",
61
+		},
62
+		"missing Strategy.CustomPod": {
63
+			api.Deployment{
64
+				Strategy: api.DeploymentStrategy{
65
+					Type: "customPod",
66
+				},
67
+			},
68
+			errors.ValidationErrorTypeRequired,
69
+			"Strategy.CustomPod",
70
+		},
71
+		"missing Strategy.CustomPod.Image": {
72
+			api.Deployment{
73
+				Strategy: api.DeploymentStrategy{
74
+					Type:      "customPod",
75
+					CustomPod: &api.CustomPodDeploymentStrategy{},
76
+				},
77
+			},
78
+			errors.ValidationErrorTypeRequired,
79
+			"Strategy.CustomPod.Image",
80
+		},
81
+	}
82
+
83
+	for k, v := range errorCases {
84
+		errs := ValidateDeployment(&v.D)
85
+		if len(errs) == 0 {
86
+			t.Errorf("Expected failure for scenario %s", k)
87
+		}
88
+		for i := range errs {
89
+			if errs[i].(errors.ValidationError).Type != v.T {
90
+				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
91
+			}
92
+			if errs[i].(errors.ValidationError).Field != v.F {
93
+				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
94
+			}
95
+		}
96
+	}
97
+}
98
+
99
+func TestValidateDeploymentConfigOK(t *testing.T) {
100
+	errs := ValidateDeploymentConfig(&api.DeploymentConfig{
101
+		TriggerPolicy: manualTrigger(),
102
+		Template:      okTemplate(),
103
+	})
104
+
105
+	if len(errs) > 0 {
106
+		t.Errorf("Unxpected non-empty error list: %#v", errs)
107
+	}
108
+}
109
+
110
+func TestValidateDeploymentConfigMissingFields(t *testing.T) {
111
+	errorCases := map[string]struct {
112
+		D api.DeploymentConfig
113
+		T errors.ValidationErrorType
114
+		F string
115
+	}{
116
+		"missing TriggerPolicy.Type": {
117
+			api.DeploymentConfig{Template: okTemplate()},
118
+			errors.ValidationErrorTypeRequired,
119
+			"TriggerPolicy.Type",
120
+		},
121
+		"missing Strategy.Type": {
122
+			api.DeploymentConfig{
123
+				TriggerPolicy: manualTrigger(),
124
+				Template: api.DeploymentTemplate{
125
+					Strategy: api.DeploymentStrategy{
126
+						CustomPod: okCustomPod(),
127
+					},
128
+				},
129
+			},
130
+			errors.ValidationErrorTypeRequired,
131
+			"Template.Strategy.Type",
132
+		},
133
+		"missing Strategy.CustomPod": {
134
+			api.DeploymentConfig{
135
+				TriggerPolicy: manualTrigger(),
136
+				Template: api.DeploymentTemplate{
137
+					Strategy: api.DeploymentStrategy{
138
+						Type: "customPod",
139
+					},
140
+				},
141
+			},
142
+			errors.ValidationErrorTypeRequired,
143
+			"Template.Strategy.CustomPod",
144
+		},
145
+		"missing Template.Strategy.CustomPod.Image": {
146
+			api.DeploymentConfig{
147
+				TriggerPolicy: manualTrigger(),
148
+				Template: api.DeploymentTemplate{
149
+					Strategy: api.DeploymentStrategy{
150
+						Type:      "customPod",
151
+						CustomPod: &api.CustomPodDeploymentStrategy{},
152
+					},
153
+				},
154
+			},
155
+			errors.ValidationErrorTypeRequired,
156
+			"Template.Strategy.CustomPod.Image",
157
+		},
158
+	}
159
+
160
+	for k, v := range errorCases {
161
+		errs := ValidateDeploymentConfig(&v.D)
162
+		if len(errs) == 0 {
163
+			t.Errorf("Expected failure for scenario %s", k)
164
+		}
165
+		for i := range errs {
166
+			if errs[i].(errors.ValidationError).Type != v.T {
167
+				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
168
+			}
169
+			if errs[i].(errors.ValidationError).Field != v.F {
170
+				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
171
+			}
172
+		}
173
+	}
174
+}
0 175
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+package client
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
7
+	"github.com/openshift/origin/pkg/deploy/api"
8
+)
9
+
10
+var deploymentColumns = []string{"ID", "State"}
11
+var deploymentConfigColumns = []string{"ID", "Trigger Policy"}
12
+
13
+// RegisterPrintHandlers registers human-readable printers for deploy types.
14
+func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
15
+	printer.Handler(deploymentColumns, printDeployment)
16
+	printer.Handler(deploymentColumns, printDeploymentList)
17
+	printer.Handler(deploymentConfigColumns, printDeploymentConfig)
18
+	printer.Handler(deploymentConfigColumns, printDeploymentConfigList)
19
+}
20
+
21
+func printDeployment(d *api.Deployment, w io.Writer) error {
22
+	_, err := fmt.Fprintf(w, "%s\t%s\n", d.ID, d.State)
23
+	return err
24
+}
25
+
26
+func printDeploymentList(list *api.DeploymentList, w io.Writer) error {
27
+	for _, d := range list.Items {
28
+		if err := printDeployment(&d, w); err != nil {
29
+			return err
30
+		}
31
+	}
32
+
33
+	return nil
34
+}
35
+
36
+func printDeploymentConfig(dc *api.DeploymentConfig, w io.Writer) error {
37
+	_, err := fmt.Fprintf(w, "%s\t%s\n", dc.ID, dc.TriggerPolicy)
38
+	return err
39
+}
40
+
41
+func printDeploymentConfigList(list *api.DeploymentConfigList, w io.Writer) error {
42
+	for _, dc := range list.Items {
43
+		if err := printDeploymentConfig(&dc, w); err != nil {
44
+			return err
45
+		}
46
+	}
47
+
48
+	return nil
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,214 @@
0
+package deploy
1
+
2
+import (
3
+	"time"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	kubeclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
+	"github.com/golang/glog"
10
+	osclient "github.com/openshift/origin/pkg/client"
11
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+)
13
+
14
+// A DeploymentController is responsible for executing Deployment objects stored in etcd
15
+type DeploymentController struct {
16
+	osClient     osclient.Interface
17
+	kubeClient   kubeclient.Interface
18
+	syncTicker   <-chan time.Time
19
+	stateHandler DeploymentStateHandler
20
+}
21
+
22
+// DeploymentStateHandler holds methods that handle the possible deployment states.
23
+type DeploymentStateHandler interface {
24
+	HandleNew(*deployapi.Deployment) error
25
+	HandlePending(*deployapi.Deployment) error
26
+	HandleRunning(*deployapi.Deployment) error
27
+}
28
+
29
+// DefaultDeploymentRunner is the default implementation of DeploymentRunner interface.
30
+type DefaultDeploymentHandler struct {
31
+	osClient    osclient.Interface
32
+	kubeClient  kubeclient.Interface
33
+	environment []api.EnvVar
34
+}
35
+
36
+// NewDeploymentController creates a new DeploymentController.
37
+func NewDeploymentController(kubeClient kubeclient.Interface, osClient osclient.Interface, initialEnvironment []api.EnvVar) *DeploymentController {
38
+	dc := &DeploymentController{
39
+		kubeClient: kubeClient,
40
+		osClient:   osClient,
41
+		stateHandler: &DefaultDeploymentHandler{
42
+			osClient:    osClient,
43
+			kubeClient:  kubeClient,
44
+			environment: initialEnvironment,
45
+		},
46
+	}
47
+	return dc
48
+}
49
+
50
+// Run begins watching and synchronizing deployment states.
51
+func (dc *DeploymentController) Run(period time.Duration) {
52
+	dc.syncTicker = time.Tick(period)
53
+	go util.Forever(func() { dc.synchronize() }, period)
54
+}
55
+
56
+// The main synchronization loop.  Iterates through all deployments and handles the current state
57
+// for each.
58
+func (dc *DeploymentController) synchronize() {
59
+	deployments, err := dc.osClient.ListDeployments(labels.Everything())
60
+	if err != nil {
61
+		glog.Errorf("Synchronization error: %v (%#v)", err, err)
62
+		return
63
+	}
64
+
65
+	for ix := range deployments.Items {
66
+		id := deployments.Items[ix].ID
67
+		deployment, err := dc.osClient.GetDeployment(id)
68
+		if err != nil {
69
+			glog.Errorf("Got error retrieving deployment with id %s -- %v", id, err)
70
+			continue
71
+		}
72
+		err = dc.syncDeployment(deployment)
73
+		if err != nil {
74
+			glog.Errorf("Error synchronizing: %#v", err)
75
+		}
76
+	}
77
+}
78
+
79
+// Invokes the appropriate handler for the current state of the given deployment.
80
+func (dc *DeploymentController) syncDeployment(deployment *deployapi.Deployment) error {
81
+	glog.Infof("Synchronizing deployment id: %v state: %v resourceVersion: %v", deployment.ID, deployment.State, deployment.ResourceVersion)
82
+	var err error = nil
83
+	switch deployment.State {
84
+	case deployapi.DeploymentNew:
85
+		err = dc.stateHandler.HandleNew(deployment)
86
+	case deployapi.DeploymentPending:
87
+		err = dc.stateHandler.HandlePending(deployment)
88
+	case deployapi.DeploymentRunning:
89
+		err = dc.stateHandler.HandleRunning(deployment)
90
+	}
91
+	return err
92
+}
93
+
94
+func (dh *DefaultDeploymentHandler) saveDeployment(deployment *deployapi.Deployment) error {
95
+	glog.Infof("Saving deployment %v state: %v", deployment.ID, deployment.State)
96
+	_, err := dh.osClient.UpdateDeployment(deployment)
97
+	if err != nil {
98
+		glog.Errorf("Received error while saving deployment %v: %v", deployment.ID, err)
99
+	}
100
+	return err
101
+}
102
+
103
+func (dh *DefaultDeploymentHandler) makeDeploymentPod(deployment *deployapi.Deployment) api.Pod {
104
+	podID := deploymentPodID(deployment)
105
+
106
+	envVars := deployment.Strategy.CustomPod.Environment
107
+	envVars = append(envVars, api.EnvVar{Name: "KUBERNETES_DEPLOYMENT_ID", Value: deployment.ID})
108
+	for _, env := range dh.environment {
109
+		envVars = append(envVars, env)
110
+	}
111
+
112
+	return api.Pod{
113
+		JSONBase: api.JSONBase{
114
+			ID: podID,
115
+		},
116
+		DesiredState: api.PodState{
117
+			Manifest: api.ContainerManifest{
118
+				Version: "v1beta1",
119
+				Containers: []api.Container{
120
+					{
121
+						Name:          "deployment",
122
+						Image:         deployment.Strategy.CustomPod.Image,
123
+						Env:           envVars,
124
+						RestartPolicy: "runOnce",
125
+					},
126
+				},
127
+			},
128
+			RestartPolicy: api.RestartPolicy{
129
+				Type: api.RestartNever,
130
+			},
131
+		},
132
+	}
133
+}
134
+
135
+func deploymentPodID(deployment *deployapi.Deployment) string {
136
+	return "deploy-" + deployment.ID
137
+}
138
+
139
+// Handler for a deployment in the 'new' state.
140
+func (dh *DefaultDeploymentHandler) HandleNew(deployment *deployapi.Deployment) error {
141
+	deploymentPod := dh.makeDeploymentPod(deployment)
142
+	glog.Infof("Attempting to create deployment pod: %+v", deploymentPod)
143
+	if pod, err := dh.kubeClient.CreatePod(deploymentPod); err != nil {
144
+		glog.Warningf("Received error creating pod: %v", err)
145
+		deployment.State = deployapi.DeploymentFailed
146
+	} else {
147
+		glog.Infof("Successfully created pod %+v", pod)
148
+		deployment.State = deployapi.DeploymentPending
149
+	}
150
+
151
+	return dh.saveDeployment(deployment)
152
+}
153
+
154
+// Handler for a deployment in the 'pending' state
155
+func (dh *DefaultDeploymentHandler) HandlePending(deployment *deployapi.Deployment) error {
156
+	podID := deploymentPodID(deployment)
157
+	glog.Infof("Retrieving deployment pod id %s", podID)
158
+	pod, err := dh.kubeClient.GetPod(podID)
159
+	if err != nil {
160
+		glog.Errorf("Error retrieving pod for deployment ID %v: %#v", deployment.ID, err)
161
+		deployment.State = deployapi.DeploymentFailed
162
+	} else {
163
+		glog.Infof("Deployment pod is %+v", pod)
164
+
165
+		switch pod.CurrentState.Status {
166
+		case api.PodRunning:
167
+			deployment.State = deployapi.DeploymentRunning
168
+		case api.PodTerminated:
169
+			dh.checkForTerminatedDeploymentPod(deployment, pod)
170
+		}
171
+	}
172
+
173
+	return dh.saveDeployment(deployment)
174
+}
175
+
176
+// Handler for a deployment in the 'running' state
177
+func (dh *DefaultDeploymentHandler) HandleRunning(deployment *deployapi.Deployment) error {
178
+	podID := deploymentPodID(deployment)
179
+	glog.Infof("Retrieving deployment pod id %s", podID)
180
+	pod, err := dh.kubeClient.GetPod(podID)
181
+	if err != nil {
182
+		glog.Errorf("Error retrieving pod for deployment ID %v: %#v", deployment.ID, err)
183
+		deployment.State = deployapi.DeploymentFailed
184
+	} else {
185
+		glog.Infof("Deployment pod is %+v", pod)
186
+		dh.checkForTerminatedDeploymentPod(deployment, pod)
187
+	}
188
+
189
+	return dh.saveDeployment(deployment)
190
+}
191
+
192
+func (dh *DefaultDeploymentHandler) checkForTerminatedDeploymentPod(deployment *deployapi.Deployment, pod api.Pod) {
193
+	if pod.CurrentState.Status != api.PodTerminated {
194
+		glog.Infof("The deployment has not yet finished. Pod status is %s. Continuing", pod.CurrentState.Status)
195
+		return
196
+	}
197
+
198
+	deployment.State = deployapi.DeploymentComplete
199
+	for _, info := range pod.CurrentState.Info {
200
+		if info.State.ExitCode != 0 {
201
+			deployment.State = deployapi.DeploymentFailed
202
+		}
203
+	}
204
+
205
+	if deployment.State == deployapi.DeploymentComplete {
206
+		podID := deploymentPodID(deployment)
207
+		glog.Infof("Removing deployment pod for ID %v", podID)
208
+		dh.kubeClient.DeletePod(podID)
209
+	}
210
+
211
+	glog.Infof("The deployment pod has finished. Setting deployment state to %s", deployment.State)
212
+	return
213
+}
0 214
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+// Describes deployment resource and associated controller
1
+package deploy
0 2
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package deploy
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
4
+	api "github.com/openshift/origin/pkg/deploy/api"
5
+)
6
+
7
+// Registry is an interface for things that know how to store Deployments
8
+type Registry interface {
9
+	ListDeployments(selector labels.Selector) (*api.DeploymentList, error)
10
+	GetDeployment(id string) (*api.Deployment, error)
11
+	CreateDeployment(deployment *api.Deployment) error
12
+	UpdateDeployment(deployment *api.Deployment) error
13
+	DeleteDeployment(id string) error
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,101 @@
0
+package deploy
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"code.google.com/p/go-uuid/uuid"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	kubeerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10
+	"github.com/golang/glog"
11
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	"github.com/openshift/origin/pkg/deploy/api/validation"
13
+)
14
+
15
+// REST is an implementation of RESTStorage for the api server.
16
+type REST struct {
17
+	registry Registry
18
+}
19
+
20
+func NewREST(registry Registry) apiserver.RESTStorage {
21
+	return &REST{
22
+		registry: registry,
23
+	}
24
+}
25
+
26
+// List obtains a list of Deployments that match selector.
27
+func (s *REST) List(selector labels.Selector) (interface{}, error) {
28
+	deployments, err := s.registry.ListDeployments(selector)
29
+	if err != nil {
30
+		return nil, err
31
+	}
32
+
33
+	return deployments, nil
34
+}
35
+
36
+// New creates a new Deployment for use with Create and Update
37
+func (s *REST) New() interface{} {
38
+	return &deployapi.Deployment{}
39
+}
40
+
41
+// Get obtains the Deployment specified by its id.
42
+func (s *REST) Get(id string) (interface{}, error) {
43
+	deployment, err := s.registry.GetDeployment(id)
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	return deployment, err
48
+}
49
+
50
+// Delete asynchronously deletes the Deployment specified by its id.
51
+func (s *REST) Delete(id string) (<-chan interface{}, error) {
52
+	return apiserver.MakeAsync(func() (interface{}, error) {
53
+		return api.Status{Status: api.StatusSuccess}, s.registry.DeleteDeployment(id)
54
+	}), nil
55
+}
56
+
57
+// Create registers a given new Deployment instance to s.registry.
58
+func (s *REST) Create(obj interface{}) (<-chan interface{}, error) {
59
+	deployment, ok := obj.(*deployapi.Deployment)
60
+	if !ok {
61
+		return nil, fmt.Errorf("not a deployment: %#v", obj)
62
+	}
63
+
64
+	glog.Infof("Creating deployment with ID: %v", deployment.ID)
65
+
66
+	if len(deployment.ID) == 0 {
67
+		deployment.ID = uuid.NewUUID().String()
68
+	}
69
+	deployment.State = deployapi.DeploymentNew
70
+
71
+	if errs := validation.ValidateDeployment(deployment); len(errs) > 0 {
72
+		return nil, kubeerrors.NewInvalid("deployment", deployment.ID, errs)
73
+	}
74
+
75
+	return apiserver.MakeAsync(func() (interface{}, error) {
76
+		err := s.registry.CreateDeployment(deployment)
77
+		if err != nil {
78
+			return nil, err
79
+		}
80
+		return *deployment, nil
81
+	}), nil
82
+}
83
+
84
+// Update replaces a given Deployment instance with an existing instance in s.registry.
85
+func (s *REST) Update(obj interface{}) (<-chan interface{}, error) {
86
+	deployment, ok := obj.(*deployapi.Deployment)
87
+	if !ok {
88
+		return nil, fmt.Errorf("not a deployment: %#v", obj)
89
+	}
90
+	if len(deployment.ID) == 0 {
91
+		return nil, fmt.Errorf("id is unspecified: %#v", deployment)
92
+	}
93
+	return apiserver.MakeAsync(func() (interface{}, error) {
94
+		err := s.registry.UpdateDeployment(deployment)
95
+		if err != nil {
96
+			return nil, err
97
+		}
98
+		return deployment, nil
99
+	}), nil
100
+}
0 101
new file mode 100644
... ...
@@ -0,0 +1,294 @@
0
+package deploy
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+	"testing"
6
+	"time"
7
+
8
+	kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10
+	"github.com/openshift/origin/pkg/deploy/api"
11
+	"github.com/openshift/origin/pkg/deploy/registry/test"
12
+)
13
+
14
+func TestListDeploymentsError(t *testing.T) {
15
+	mockRegistry := test.NewDeploymentRegistry()
16
+	mockRegistry.Err = fmt.Errorf("test error")
17
+
18
+	storage := REST{
19
+		registry: mockRegistry,
20
+	}
21
+
22
+	deployments, err := storage.List(nil)
23
+	if err != mockRegistry.Err {
24
+		t.Errorf("Expected %#v, Got %#v", mockRegistry.Err, err)
25
+	}
26
+
27
+	if deployments != nil {
28
+		t.Errorf("Unexpected non-nil deployments list: %#v", deployments)
29
+	}
30
+}
31
+
32
+func TestListDeploymentsEmptyList(t *testing.T) {
33
+	mockRegistry := test.NewDeploymentRegistry()
34
+	mockRegistry.Deployments = &api.DeploymentList{
35
+		Items: []api.Deployment{},
36
+	}
37
+
38
+	storage := REST{
39
+		registry: mockRegistry,
40
+	}
41
+
42
+	deployments, err := storage.List(labels.Everything())
43
+	if err != nil {
44
+		t.Errorf("Unexpected non-nil error: %#v", err)
45
+	}
46
+
47
+	if len(deployments.(*api.DeploymentList).Items) != 0 {
48
+		t.Errorf("Unexpected non-zero deployments list: %#v", deployments)
49
+	}
50
+}
51
+
52
+func TestListDeploymentsPopulatedList(t *testing.T) {
53
+	mockRegistry := test.NewDeploymentRegistry()
54
+	mockRegistry.Deployments = &api.DeploymentList{
55
+		Items: []api.Deployment{
56
+			{
57
+				JSONBase: kubeapi.JSONBase{
58
+					ID: "foo",
59
+				},
60
+			},
61
+			{
62
+				JSONBase: kubeapi.JSONBase{
63
+					ID: "bar",
64
+				},
65
+			},
66
+		},
67
+	}
68
+
69
+	storage := REST{
70
+		registry: mockRegistry,
71
+	}
72
+
73
+	list, err := storage.List(labels.Everything())
74
+	if err != nil {
75
+		t.Errorf("Unexpected non-nil error: %#v", err)
76
+	}
77
+
78
+	deployments := list.(*api.DeploymentList)
79
+
80
+	if e, a := 2, len(deployments.Items); e != a {
81
+		t.Errorf("Expected %v, got %v", e, a)
82
+	}
83
+}
84
+
85
+func TestCreateDeploymentBadObject(t *testing.T) {
86
+	storage := REST{}
87
+
88
+	channel, err := storage.Create("hello")
89
+	if channel != nil {
90
+		t.Errorf("Expected nil, got %v", channel)
91
+	}
92
+	if strings.Index(err.Error(), "not a deployment") == -1 {
93
+		t.Errorf("Expected 'not a deployment' error, got '%v'", err.Error())
94
+	}
95
+}
96
+
97
+func okStrategy() api.DeploymentStrategy {
98
+	return api.DeploymentStrategy{
99
+		Type:      "customPod",
100
+		CustomPod: okCustomPod(),
101
+	}
102
+}
103
+
104
+func okCustomPod() *api.CustomPodDeploymentStrategy {
105
+	return &api.CustomPodDeploymentStrategy{
106
+		Image: "openshift/kube-deploy",
107
+	}
108
+}
109
+
110
+func TestCreateRegistrySaveError(t *testing.T) {
111
+	mockRegistry := test.NewDeploymentRegistry()
112
+	mockRegistry.Err = fmt.Errorf("test error")
113
+	storage := REST{registry: mockRegistry}
114
+
115
+	channel, err := storage.Create(&api.Deployment{
116
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
117
+		Strategy: okStrategy(),
118
+	})
119
+	if channel == nil {
120
+		t.Errorf("Expected nil channel, got %v", channel)
121
+	}
122
+	if err != nil {
123
+		t.Errorf("Unexpected non-nil error: %#v", err)
124
+	}
125
+
126
+	select {
127
+	case result := <-channel:
128
+		status, ok := result.(*kubeapi.Status)
129
+		if !ok {
130
+			t.Errorf("Expected status type, got: %#v", result)
131
+		}
132
+		if status.Status != "failure" || status.Message != "foo" {
133
+			t.Errorf("Expected failure status, got %#V", status)
134
+		}
135
+	case <-time.After(50 * time.Millisecond):
136
+		t.Errorf("Timed out waiting for result")
137
+	default:
138
+	}
139
+}
140
+
141
+func TestCreateDeploymentOK(t *testing.T) {
142
+	mockRegistry := test.NewDeploymentRegistry()
143
+	storage := REST{registry: mockRegistry}
144
+
145
+	channel, err := storage.Create(&api.Deployment{
146
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
147
+		Strategy: okStrategy(),
148
+	})
149
+	if channel == nil {
150
+		t.Errorf("Expected nil channel, got %v", channel)
151
+	}
152
+	if err != nil {
153
+		t.Errorf("Unexpected non-nil error: %#v", err)
154
+	}
155
+
156
+	select {
157
+	case result := <-channel:
158
+		deployment, ok := result.(*api.Deployment)
159
+		if !ok {
160
+			t.Errorf("Expected deployment type, got: %#v", result)
161
+		}
162
+		if deployment.ID != "foo" {
163
+			t.Errorf("Unexpected deployment: %#v", deployment)
164
+		}
165
+	case <-time.After(50 * time.Millisecond):
166
+		t.Errorf("Timed out waiting for result")
167
+	default:
168
+	}
169
+}
170
+
171
+func TestGetDeploymentError(t *testing.T) {
172
+	mockRegistry := test.NewDeploymentRegistry()
173
+	mockRegistry.Err = fmt.Errorf("bad")
174
+	storage := REST{registry: mockRegistry}
175
+
176
+	deployment, err := storage.Get("foo")
177
+	if deployment != nil {
178
+		t.Errorf("Unexpected non-nil deployment: %#v", deployment)
179
+	}
180
+	if err != mockRegistry.Err {
181
+		t.Errorf("Expected %#v, got %#v", mockRegistry.Err, err)
182
+	}
183
+}
184
+
185
+func TestGetDeploymentOK(t *testing.T) {
186
+	mockRegistry := test.NewDeploymentRegistry()
187
+	mockRegistry.Deployment = &api.Deployment{
188
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
189
+	}
190
+	storage := REST{registry: mockRegistry}
191
+
192
+	deployment, err := storage.Get("foo")
193
+	if deployment == nil {
194
+		t.Error("Unexpected nil deployment")
195
+	}
196
+	if err != nil {
197
+		t.Errorf("Unexpected non-nil error", err)
198
+	}
199
+	if deployment.(*api.Deployment).ID != "foo" {
200
+		t.Errorf("Unexpected deployment: %#v", deployment)
201
+	}
202
+}
203
+
204
+func TestUpdateDeploymentBadObject(t *testing.T) {
205
+	storage := REST{}
206
+
207
+	channel, err := storage.Update("hello")
208
+	if channel != nil {
209
+		t.Errorf("Expected nil, got %v", channel)
210
+	}
211
+	if strings.Index(err.Error(), "not a deployment:") == -1 {
212
+		t.Errorf("Expected 'not a deployment' error, got %v", err)
213
+	}
214
+}
215
+
216
+func TestUpdateDeploymentMissingID(t *testing.T) {
217
+	storage := REST{}
218
+
219
+	channel, err := storage.Update(&api.Deployment{})
220
+	if channel != nil {
221
+		t.Errorf("Expected nil, got %v", channel)
222
+	}
223
+	if strings.Index(err.Error(), "id is unspecified:") == -1 {
224
+		t.Errorf("Expected 'id is unspecified' error, got %v", err)
225
+	}
226
+}
227
+
228
+func TestUpdateRegistryErrorSaving(t *testing.T) {
229
+	mockRepositoryRegistry := test.NewDeploymentRegistry()
230
+	mockRepositoryRegistry.Err = fmt.Errorf("foo")
231
+	storage := REST{registry: mockRepositoryRegistry}
232
+
233
+	channel, err := storage.Update(&api.Deployment{
234
+		JSONBase: kubeapi.JSONBase{ID: "bar"},
235
+	})
236
+	if err != nil {
237
+		t.Errorf("Unexpected non-nil error: %#v", err)
238
+	}
239
+	result := <-channel
240
+	status, ok := result.(*kubeapi.Status)
241
+	if !ok {
242
+		t.Errorf("Expected status, got %#v", result)
243
+	}
244
+	if status.Status != "failure" || status.Message != "foo" {
245
+		t.Errorf("Expected status=failure, message=foo, got %#v", status)
246
+	}
247
+}
248
+
249
+func TestUpdateDeploymentOK(t *testing.T) {
250
+	mockRepositoryRegistry := test.NewDeploymentRegistry()
251
+	storage := REST{registry: mockRepositoryRegistry}
252
+
253
+	channel, err := storage.Update(&api.Deployment{
254
+		JSONBase: kubeapi.JSONBase{ID: "bar"},
255
+	})
256
+	if err != nil {
257
+		t.Errorf("Unexpected non-nil error: %#v", err)
258
+	}
259
+	result := <-channel
260
+	repo, ok := result.(*api.Deployment)
261
+	if !ok {
262
+		t.Errorf("Expected Deployment, got %#v", result)
263
+	}
264
+	if repo.ID != "bar" {
265
+		t.Errorf("Unexpected repo returned: %#v", repo)
266
+	}
267
+}
268
+
269
+func TestDeleteDeployment(t *testing.T) {
270
+	mockRegistry := test.NewDeploymentRegistry()
271
+	storage := REST{registry: mockRegistry}
272
+	channel, err := storage.Delete("foo")
273
+	if channel == nil {
274
+		t.Error("Unexpected nil channel")
275
+	}
276
+	if err != nil {
277
+		t.Errorf("Unexpected non-nil error: %#v", err)
278
+	}
279
+
280
+	select {
281
+	case result := <-channel:
282
+		status, ok := result.(*kubeapi.Status)
283
+		if !ok {
284
+			t.Errorf("Expected status type, got: %#v", result)
285
+		}
286
+		if status.Status != "success" {
287
+			t.Errorf("Expected status=success, got: %#v", status)
288
+		}
289
+	case <-time.After(50 * time.Millisecond):
290
+		t.Errorf("Timed out waiting for result")
291
+	default:
292
+	}
293
+}
0 294
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package deployconfig
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
4
+	api "github.com/openshift/origin/pkg/deploy/api"
5
+)
6
+
7
+// Registry is an interface for things that know how to store DeploymentConfigs
8
+type Registry interface {
9
+	ListDeploymentConfigs(selector labels.Selector) (*api.DeploymentConfigList, error)
10
+	GetDeploymentConfig(id string) (*api.DeploymentConfig, error)
11
+	CreateDeploymentConfig(deploymentConfig *api.DeploymentConfig) error
12
+	UpdateDeploymentConfig(deploymentConfig *api.DeploymentConfig) error
13
+	DeleteDeploymentConfig(id string) error
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,92 @@
0
+package deployconfig
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"code.google.com/p/go-uuid/uuid"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
+)
11
+
12
+// REST is an implementation of RESTStorage for the api server.
13
+type REST struct {
14
+	registry Registry
15
+}
16
+
17
+func NewREST(registry Registry) apiserver.RESTStorage {
18
+	return &REST{
19
+		registry: registry,
20
+	}
21
+}
22
+
23
+// List obtains a list of DeploymentConfigs that match selector.
24
+func (s *REST) List(selector labels.Selector) (interface{}, error) {
25
+	deploymentConfigs, err := s.registry.ListDeploymentConfigs(selector)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+
30
+	return deploymentConfigs, nil
31
+}
32
+
33
+// Get obtains the DeploymentConfig specified by its id.
34
+func (s *REST) Get(id string) (interface{}, error) {
35
+	deploymentConfig, err := s.registry.GetDeploymentConfig(id)
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+	return deploymentConfig, err
40
+}
41
+
42
+// Delete asynchronously deletes the DeploymentConfig specified by its id.
43
+func (s *REST) Delete(id string) (<-chan interface{}, error) {
44
+	return apiserver.MakeAsync(func() (interface{}, error) {
45
+		return api.Status{Status: api.StatusSuccess}, s.registry.DeleteDeploymentConfig(id)
46
+	}), nil
47
+}
48
+
49
+// New creates a new DeploymentConfig for use with Create and Update
50
+func (s *REST) New() interface{} {
51
+	return &deployapi.DeploymentConfig{}
52
+}
53
+
54
+// Create registers a given new DeploymentConfig instance to s.registry.
55
+func (s *REST) Create(obj interface{}) (<-chan interface{}, error) {
56
+	deploymentConfig, ok := obj.(*deployapi.DeploymentConfig)
57
+	if !ok {
58
+		return nil, fmt.Errorf("not a deploymentConfig: %#v", obj)
59
+	}
60
+	if len(deploymentConfig.ID) == 0 {
61
+		deploymentConfig.ID = uuid.NewUUID().String()
62
+	}
63
+
64
+	//TODO: Add validation
65
+
66
+	return apiserver.MakeAsync(func() (interface{}, error) {
67
+		err := s.registry.CreateDeploymentConfig(deploymentConfig)
68
+		if err != nil {
69
+			return nil, err
70
+		}
71
+		return deploymentConfig, nil
72
+	}), nil
73
+}
74
+
75
+// Update replaces a given DeploymentConfig instance with an existing instance in s.registry.
76
+func (s *REST) Update(obj interface{}) (<-chan interface{}, error) {
77
+	deploymentConfig, ok := obj.(*deployapi.DeploymentConfig)
78
+	if !ok {
79
+		return nil, fmt.Errorf("not a deploymentConfig: %#v", obj)
80
+	}
81
+	if len(deploymentConfig.ID) == 0 {
82
+		return nil, fmt.Errorf("id is unspecified: %#v", deploymentConfig)
83
+	}
84
+	return apiserver.MakeAsync(func() (interface{}, error) {
85
+		err := s.registry.UpdateDeploymentConfig(deploymentConfig)
86
+		if err != nil {
87
+			return nil, err
88
+		}
89
+		return deploymentConfig, nil
90
+	}), nil
91
+}
0 92
new file mode 100644
... ...
@@ -0,0 +1,279 @@
0
+package deployconfig
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+	"testing"
6
+	"time"
7
+
8
+	kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10
+	"github.com/openshift/origin/pkg/deploy/api"
11
+	"github.com/openshift/origin/pkg/deploy/registry/test"
12
+)
13
+
14
+func TestListDeploymentConfigsError(t *testing.T) {
15
+	mockRegistry := test.NewDeploymentConfigRegistry()
16
+	mockRegistry.Err = fmt.Errorf("test error")
17
+
18
+	storage := REST{
19
+		registry: mockRegistry,
20
+	}
21
+
22
+	deploymentConfigs, err := storage.List(nil)
23
+	if err != mockRegistry.Err {
24
+		t.Errorf("Expected %#v, Got %#v", mockRegistry.Err, err)
25
+	}
26
+
27
+	if deploymentConfigs != nil {
28
+		t.Errorf("Unexpected non-nil deploymentConfigs list: %#v", deploymentConfigs)
29
+	}
30
+}
31
+
32
+func TestListDeploymentConfigsEmptyList(t *testing.T) {
33
+	mockRegistry := test.NewDeploymentConfigRegistry()
34
+	mockRegistry.DeploymentConfigs = &api.DeploymentConfigList{
35
+		Items: []api.DeploymentConfig{},
36
+	}
37
+
38
+	storage := REST{
39
+		registry: mockRegistry,
40
+	}
41
+
42
+	deploymentConfigs, err := storage.List(labels.Everything())
43
+	if err != nil {
44
+		t.Errorf("Unexpected non-nil error: %#v", err)
45
+	}
46
+
47
+	if len(deploymentConfigs.(*api.DeploymentConfigList).Items) != 0 {
48
+		t.Errorf("Unexpected non-zero deploymentConfigs list: %#v", deploymentConfigs)
49
+	}
50
+}
51
+
52
+func TestListDeploymentConfigsPopulatedList(t *testing.T) {
53
+	mockRegistry := test.NewDeploymentConfigRegistry()
54
+	mockRegistry.DeploymentConfigs = &api.DeploymentConfigList{
55
+		Items: []api.DeploymentConfig{
56
+			{
57
+				JSONBase: kubeapi.JSONBase{
58
+					ID: "foo",
59
+				},
60
+			},
61
+			{
62
+				JSONBase: kubeapi.JSONBase{
63
+					ID: "bar",
64
+				},
65
+			},
66
+		},
67
+	}
68
+
69
+	storage := REST{
70
+		registry: mockRegistry,
71
+	}
72
+
73
+	list, err := storage.List(labels.Everything())
74
+	if err != nil {
75
+		t.Errorf("Unexpected non-nil error: %#v", err)
76
+	}
77
+
78
+	deploymentConfigs := list.(*api.DeploymentConfigList)
79
+
80
+	if e, a := 2, len(deploymentConfigs.Items); e != a {
81
+		t.Errorf("Expected %v, got %v", e, a)
82
+	}
83
+}
84
+
85
+func TestCreateDeploymentConfigBadObject(t *testing.T) {
86
+	storage := REST{}
87
+
88
+	channel, err := storage.Create("hello")
89
+	if channel != nil {
90
+		t.Errorf("Expected nil, got %v", channel)
91
+	}
92
+	if strings.Index(err.Error(), "not a deploymentConfig") == -1 {
93
+		t.Errorf("Expected 'not a deploymentConfig' error, got '%v'", err.Error())
94
+	}
95
+}
96
+
97
+func TestCreateRegistrySaveError(t *testing.T) {
98
+	mockRegistry := test.NewDeploymentConfigRegistry()
99
+	mockRegistry.Err = fmt.Errorf("test error")
100
+	storage := REST{registry: mockRegistry}
101
+
102
+	channel, err := storage.Create(&api.DeploymentConfig{
103
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
104
+	})
105
+	if channel == nil {
106
+		t.Errorf("Expected nil channel, got %v", channel)
107
+	}
108
+	if err != nil {
109
+		t.Errorf("Unexpected non-nil error: %#v", err)
110
+	}
111
+
112
+	select {
113
+	case result := <-channel:
114
+		status, ok := result.(*kubeapi.Status)
115
+		if !ok {
116
+			t.Errorf("Expected status type, got: %#v", result)
117
+		}
118
+		if status.Status != "failure" || status.Message != "foo" {
119
+			t.Errorf("Expected failure status, got %#V", status)
120
+		}
121
+	case <-time.After(50 * time.Millisecond):
122
+		t.Errorf("Timed out waiting for result")
123
+	default:
124
+	}
125
+}
126
+
127
+func TestCreateDeploymentConfigOK(t *testing.T) {
128
+	mockRegistry := test.NewDeploymentConfigRegistry()
129
+	storage := REST{registry: mockRegistry}
130
+
131
+	channel, err := storage.Create(&api.DeploymentConfig{
132
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
133
+	})
134
+	if channel == nil {
135
+		t.Errorf("Expected nil channel, got %v", channel)
136
+	}
137
+	if err != nil {
138
+		t.Errorf("Unexpected non-nil error: %#v", err)
139
+	}
140
+
141
+	select {
142
+	case result := <-channel:
143
+		deploymentConfig, ok := result.(*api.DeploymentConfig)
144
+		if !ok {
145
+			t.Errorf("Expected deploymentConfig type, got: %#v", result)
146
+		}
147
+		if deploymentConfig.ID != "foo" {
148
+			t.Errorf("Unexpected deploymentConfig: %#v", deploymentConfig)
149
+		}
150
+	case <-time.After(50 * time.Millisecond):
151
+		t.Errorf("Timed out waiting for result")
152
+	default:
153
+	}
154
+}
155
+
156
+func TestGetDeploymentConfigError(t *testing.T) {
157
+	mockRegistry := test.NewDeploymentConfigRegistry()
158
+	mockRegistry.Err = fmt.Errorf("bad")
159
+	storage := REST{registry: mockRegistry}
160
+
161
+	deploymentConfig, err := storage.Get("foo")
162
+	if deploymentConfig != nil {
163
+		t.Errorf("Unexpected non-nil deploymentConfig: %#v", deploymentConfig)
164
+	}
165
+	if err != mockRegistry.Err {
166
+		t.Errorf("Expected %#v, got %#v", mockRegistry.Err, err)
167
+	}
168
+}
169
+
170
+func TestGetDeploymentConfigOK(t *testing.T) {
171
+	mockRegistry := test.NewDeploymentConfigRegistry()
172
+	mockRegistry.DeploymentConfig = &api.DeploymentConfig{
173
+		JSONBase: kubeapi.JSONBase{ID: "foo"},
174
+	}
175
+	storage := REST{registry: mockRegistry}
176
+
177
+	deploymentConfig, err := storage.Get("foo")
178
+	if deploymentConfig == nil {
179
+		t.Error("Unexpected nil deploymentConfig")
180
+	}
181
+	if err != nil {
182
+		t.Errorf("Unexpected non-nil error", err)
183
+	}
184
+	if deploymentConfig.(*api.DeploymentConfig).ID != "foo" {
185
+		t.Errorf("Unexpected deploymentConfig: %#v", deploymentConfig)
186
+	}
187
+}
188
+
189
+func TestUpdateDeploymentConfigBadObject(t *testing.T) {
190
+	storage := REST{}
191
+
192
+	channel, err := storage.Update("hello")
193
+	if channel != nil {
194
+		t.Errorf("Expected nil, got %v", channel)
195
+	}
196
+	if strings.Index(err.Error(), "not a deploymentConfig:") == -1 {
197
+		t.Errorf("Expected 'not a deploymentConfig' error, got %v", err)
198
+	}
199
+}
200
+
201
+func TestUpdateDeploymentConfigMissingID(t *testing.T) {
202
+	storage := REST{}
203
+
204
+	channel, err := storage.Update(&api.DeploymentConfig{})
205
+	if channel != nil {
206
+		t.Errorf("Expected nil, got %v", channel)
207
+	}
208
+	if strings.Index(err.Error(), "id is unspecified:") == -1 {
209
+		t.Errorf("Expected 'id is unspecified' error, got %v", err)
210
+	}
211
+}
212
+
213
+func TestUpdateRegistryErrorSaving(t *testing.T) {
214
+	mockRepositoryRegistry := test.NewDeploymentConfigRegistry()
215
+	mockRepositoryRegistry.Err = fmt.Errorf("foo")
216
+	storage := REST{registry: mockRepositoryRegistry}
217
+
218
+	channel, err := storage.Update(&api.DeploymentConfig{
219
+		JSONBase: kubeapi.JSONBase{ID: "bar"},
220
+	})
221
+	if err != nil {
222
+		t.Errorf("Unexpected non-nil error: %#v", err)
223
+	}
224
+	result := <-channel
225
+	status, ok := result.(*kubeapi.Status)
226
+	if !ok {
227
+		t.Errorf("Expected status, got %#v", result)
228
+	}
229
+	if status.Status != "failure" || status.Message != "foo" {
230
+		t.Errorf("Expected status=failure, message=foo, got %#v", status)
231
+	}
232
+}
233
+
234
+func TestUpdateDeploymentConfigOK(t *testing.T) {
235
+	mockRepositoryRegistry := test.NewDeploymentConfigRegistry()
236
+	storage := REST{registry: mockRepositoryRegistry}
237
+
238
+	channel, err := storage.Update(&api.DeploymentConfig{
239
+		JSONBase: kubeapi.JSONBase{ID: "bar"},
240
+	})
241
+	if err != nil {
242
+		t.Errorf("Unexpected non-nil error: %#v", err)
243
+	}
244
+	result := <-channel
245
+	repo, ok := result.(*api.DeploymentConfig)
246
+	if !ok {
247
+		t.Errorf("Expected DeploymentConfig, got %#v", result)
248
+	}
249
+	if repo.ID != "bar" {
250
+		t.Errorf("Unexpected repo returned: %#v", repo)
251
+	}
252
+}
253
+
254
+func TestDeleteDeploymentConfig(t *testing.T) {
255
+	mockRegistry := test.NewDeploymentConfigRegistry()
256
+	storage := REST{registry: mockRegistry}
257
+	channel, err := storage.Delete("foo")
258
+	if channel == nil {
259
+		t.Error("Unexpected nil channel")
260
+	}
261
+	if err != nil {
262
+		t.Errorf("Unexpected non-nil error: %#v", err)
263
+	}
264
+
265
+	select {
266
+	case result := <-channel:
267
+		status, ok := result.(*kubeapi.Status)
268
+		if !ok {
269
+			t.Errorf("Expected status type, got: %#v", result)
270
+		}
271
+		if status.Status != "success" {
272
+			t.Errorf("Expected status=success, got: %#v", status)
273
+		}
274
+	case <-time.After(50 * time.Millisecond):
275
+		t.Errorf("Timed out waiting for result")
276
+	default:
277
+	}
278
+}
0 279
new file mode 100644
... ...
@@ -0,0 +1,142 @@
0
+package etcd
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
4
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
7
+	"github.com/openshift/origin/pkg/deploy/api"
8
+)
9
+
10
+// Etcd implements deployment.Registry and deploymentconfig.Registry interfaces.
11
+type Etcd struct {
12
+	tools.EtcdHelper
13
+}
14
+
15
+// NewEtcd creates an etcd registry using the given etcd client.
16
+func NewEtcd(client tools.EtcdClient) *Etcd {
17
+	registry := &Etcd{
18
+		tools.EtcdHelper{client, runtime.Codec, runtime.ResourceVersioner},
19
+	}
20
+	return registry
21
+}
22
+
23
+// ListDeployments obtains a list of Deployments.
24
+func (r *Etcd) ListDeployments(selector labels.Selector) (*api.DeploymentList, error) {
25
+	deployments := api.DeploymentList{}
26
+	err := r.ExtractList("/deployments", &deployments.Items, &deployments.ResourceVersion)
27
+	if err != nil {
28
+		return nil, err
29
+	}
30
+	filtered := []api.Deployment{}
31
+	for _, item := range deployments.Items {
32
+		if selector.Matches(labels.Set(item.Labels)) {
33
+			filtered = append(filtered, item)
34
+		}
35
+	}
36
+
37
+	deployments.Items = filtered
38
+	return &deployments, err
39
+}
40
+
41
+func makeDeploymentKey(id string) string {
42
+	return "/deployments/" + id
43
+}
44
+
45
+// GetDeployment gets a specific Deployment specified by its ID.
46
+func (r *Etcd) GetDeployment(id string) (*api.Deployment, error) {
47
+	var deployment api.Deployment
48
+	key := makeDeploymentKey(id)
49
+	err := r.ExtractObj(key, &deployment, false)
50
+	if tools.IsEtcdNotFound(err) {
51
+		return nil, errors.NewNotFound("deployment", id)
52
+	}
53
+	if err != nil {
54
+		return nil, err
55
+	}
56
+	return &deployment, nil
57
+}
58
+
59
+// CreateDeployment creates a new Deployment.
60
+func (r *Etcd) CreateDeployment(deployment *api.Deployment) error {
61
+	err := r.CreateObj(makeDeploymentKey(deployment.ID), deployment)
62
+	if tools.IsEtcdNodeExist(err) {
63
+		return errors.NewAlreadyExists("deployment", deployment.ID)
64
+	}
65
+	return err
66
+}
67
+
68
+// UpdateDeployment replaces an existing Deployment.
69
+func (r *Etcd) UpdateDeployment(deployment *api.Deployment) error {
70
+	return r.SetObj(makeDeploymentKey(deployment.ID), deployment)
71
+}
72
+
73
+// DeleteDeployment deletes a Deployment specified by its ID.
74
+func (r *Etcd) DeleteDeployment(id string) error {
75
+	key := makeDeploymentKey(id)
76
+	err := r.Delete(key, false)
77
+	if tools.IsEtcdNotFound(err) {
78
+		return errors.NewNotFound("deployment", id)
79
+	}
80
+	return err
81
+}
82
+
83
+// ListDeploymentConfigs obtains a list of DeploymentConfigs.
84
+func (r *Etcd) ListDeploymentConfigs(selector labels.Selector) (*api.DeploymentConfigList, error) {
85
+	deploymentConfigs := api.DeploymentConfigList{}
86
+	err := r.ExtractList("/deploymentConfigs", &deploymentConfigs.Items, &deploymentConfigs.ResourceVersion)
87
+	if err != nil {
88
+		return nil, err
89
+	}
90
+	filtered := []api.DeploymentConfig{}
91
+	for _, item := range deploymentConfigs.Items {
92
+		if selector.Matches(labels.Set(item.Labels)) {
93
+			filtered = append(filtered, item)
94
+		}
95
+	}
96
+
97
+	deploymentConfigs.Items = filtered
98
+	return &deploymentConfigs, err
99
+}
100
+
101
+func makeDeploymentConfigKey(id string) string {
102
+	return "/deploymentConfigs/" + id
103
+}
104
+
105
+// GetDeploymentConfig gets a specific DeploymentConfig specified by its ID.
106
+func (r *Etcd) GetDeploymentConfig(id string) (*api.DeploymentConfig, error) {
107
+	var deploymentConfig api.DeploymentConfig
108
+	key := makeDeploymentConfigKey(id)
109
+	err := r.ExtractObj(key, &deploymentConfig, false)
110
+	if tools.IsEtcdNotFound(err) {
111
+		return nil, errors.NewNotFound("deploymentConfig", id)
112
+	}
113
+	if err != nil {
114
+		return nil, err
115
+	}
116
+	return &deploymentConfig, nil
117
+}
118
+
119
+// CreateDeploymentConfig creates a new DeploymentConfig.
120
+func (r *Etcd) CreateDeploymentConfig(deploymentConfig *api.DeploymentConfig) error {
121
+	err := r.CreateObj(makeDeploymentConfigKey(deploymentConfig.ID), deploymentConfig)
122
+	if tools.IsEtcdNodeExist(err) {
123
+		return errors.NewAlreadyExists("deploymentConfig", deploymentConfig.ID)
124
+	}
125
+	return err
126
+}
127
+
128
+// UpdateDeploymentConfig replaces an existing DeploymentConfig.
129
+func (r *Etcd) UpdateDeploymentConfig(deploymentConfig *api.DeploymentConfig) error {
130
+	return r.SetObj(makeDeploymentConfigKey(deploymentConfig.ID), deploymentConfig)
131
+}
132
+
133
+// DeleteDeploymentConfig deletes a DeploymentConfig specified by its ID.
134
+func (r *Etcd) DeleteDeploymentConfig(id string) error {
135
+	key := makeDeploymentConfigKey(id)
136
+	err := r.Delete(key, false)
137
+	if tools.IsEtcdNotFound(err) {
138
+		return errors.NewNotFound("deploymentConfig", id)
139
+	}
140
+	return err
141
+}
0 142
new file mode 100644
... ...
@@ -0,0 +1,506 @@
0
+package etcd
1
+
2
+import (
3
+	"fmt"
4
+	"testing"
5
+
6
+	kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
11
+	"github.com/coreos/go-etcd/etcd"
12
+	"github.com/openshift/origin/pkg/deploy/api"
13
+
14
+	_ "github.com/openshift/origin/pkg/deploy/api/v1beta1"
15
+)
16
+
17
+func NewTestEtcd(client tools.EtcdClient) *Etcd {
18
+	return NewEtcd(client)
19
+}
20
+
21
+func TestEtcdListEmptyDeployments(t *testing.T) {
22
+	fakeClient := tools.NewFakeEtcdClient(t)
23
+	key := "/deployments"
24
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
25
+		R: &etcd.Response{
26
+			Node: &etcd.Node{
27
+				Nodes: []*etcd.Node{},
28
+			},
29
+		},
30
+		E: nil,
31
+	}
32
+	registry := NewTestEtcd(fakeClient)
33
+	deployments, err := registry.ListDeployments(labels.Everything())
34
+	if err != nil {
35
+		t.Errorf("unexpected error: %v", err)
36
+	}
37
+
38
+	if len(deployments.Items) != 0 {
39
+		t.Errorf("Unexpected deployments list: %#v", deployments)
40
+	}
41
+}
42
+
43
+func TestEtcdListErrorDeployments(t *testing.T) {
44
+	fakeClient := tools.NewFakeEtcdClient(t)
45
+	key := "/deployments"
46
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
47
+		R: &etcd.Response{
48
+			Node: nil,
49
+		},
50
+		E: fmt.Errorf("some error"),
51
+	}
52
+	registry := NewTestEtcd(fakeClient)
53
+	deployments, err := registry.ListDeployments(labels.Everything())
54
+	if err == nil {
55
+		t.Error("unexpected nil error")
56
+	}
57
+
58
+	if deployments != nil {
59
+		t.Errorf("Unexpected non-nil deployments: %#v", deployments)
60
+	}
61
+}
62
+
63
+func TestEtcdListEverythingDeployments(t *testing.T) {
64
+	fakeClient := tools.NewFakeEtcdClient(t)
65
+	key := "/deployments"
66
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
67
+		R: &etcd.Response{
68
+			Node: &etcd.Node{
69
+				Nodes: []*etcd.Node{
70
+					{
71
+						Value: runtime.EncodeOrDie(api.Deployment{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
72
+					},
73
+					{
74
+						Value: runtime.EncodeOrDie(api.Deployment{JSONBase: kubeapi.JSONBase{ID: "bar"}}),
75
+					},
76
+				},
77
+			},
78
+		},
79
+		E: nil,
80
+	}
81
+	registry := NewTestEtcd(fakeClient)
82
+	deployments, err := registry.ListDeployments(labels.Everything())
83
+	if err != nil {
84
+		t.Errorf("unexpected error: %v", err)
85
+	}
86
+
87
+	if len(deployments.Items) != 2 || deployments.Items[0].ID != "foo" || deployments.Items[1].ID != "bar" {
88
+		t.Errorf("Unexpected deployments list: %#v", deployments)
89
+	}
90
+}
91
+
92
+func TestEtcdListFilteredDeployments(t *testing.T) {
93
+	fakeClient := tools.NewFakeEtcdClient(t)
94
+	key := "/deployments"
95
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
96
+		R: &etcd.Response{
97
+			Node: &etcd.Node{
98
+				Nodes: []*etcd.Node{
99
+					{
100
+						Value: runtime.EncodeOrDie(api.Deployment{
101
+							JSONBase: kubeapi.JSONBase{ID: "foo"},
102
+							Labels:   map[string]string{"env": "prod"},
103
+						}),
104
+					},
105
+					{
106
+						Value: runtime.EncodeOrDie(api.Deployment{
107
+							JSONBase: kubeapi.JSONBase{ID: "bar"},
108
+							Labels:   map[string]string{"env": "dev"},
109
+						}),
110
+					},
111
+				},
112
+			},
113
+		},
114
+		E: nil,
115
+	}
116
+	registry := NewTestEtcd(fakeClient)
117
+	deployments, err := registry.ListDeployments(labels.SelectorFromSet(labels.Set{"env": "dev"}))
118
+	if err != nil {
119
+		t.Errorf("unexpected error: %v", err)
120
+	}
121
+
122
+	if len(deployments.Items) != 1 || deployments.Items[0].ID != "bar" {
123
+		t.Errorf("Unexpected deployments list: %#v", deployments)
124
+	}
125
+}
126
+
127
+func TestEtcdGetDeployments(t *testing.T) {
128
+	fakeClient := tools.NewFakeEtcdClient(t)
129
+	fakeClient.Set("/deployments/foo", runtime.EncodeOrDie(api.Deployment{JSONBase: kubeapi.JSONBase{ID: "foo"}}), 0)
130
+	registry := NewTestEtcd(fakeClient)
131
+	deployment, err := registry.GetDeployment("foo")
132
+	if err != nil {
133
+		t.Errorf("unexpected error: %v", err)
134
+	}
135
+
136
+	if deployment.ID != "foo" {
137
+		t.Errorf("Unexpected deployment: %#v", deployment)
138
+	}
139
+}
140
+
141
+func TestEtcdGetNotFoundDeployments(t *testing.T) {
142
+	fakeClient := tools.NewFakeEtcdClient(t)
143
+	fakeClient.Data["/deployments/foo"] = tools.EtcdResponseWithError{
144
+		R: &etcd.Response{
145
+			Node: nil,
146
+		},
147
+		E: tools.EtcdErrorNotFound,
148
+	}
149
+	registry := NewTestEtcd(fakeClient)
150
+	deployment, err := registry.GetDeployment("foo")
151
+	if err == nil {
152
+		t.Errorf("Unexpected non-error.")
153
+	}
154
+	if deployment != nil {
155
+		t.Errorf("Unexpected deployment: %#v", deployment)
156
+	}
157
+}
158
+
159
+func TestEtcdCreateDeployments(t *testing.T) {
160
+	fakeClient := tools.NewFakeEtcdClient(t)
161
+	fakeClient.TestIndex = true
162
+	fakeClient.Data["/deployments/foo"] = tools.EtcdResponseWithError{
163
+		R: &etcd.Response{
164
+			Node: nil,
165
+		},
166
+		E: tools.EtcdErrorNotFound,
167
+	}
168
+	registry := NewTestEtcd(fakeClient)
169
+	err := registry.CreateDeployment(&api.Deployment{
170
+		JSONBase: kubeapi.JSONBase{
171
+			ID: "foo",
172
+		},
173
+	})
174
+	if err != nil {
175
+		t.Fatalf("unexpected error: %v", err)
176
+	}
177
+
178
+	resp, err := fakeClient.Get("/deployments/foo", false, false)
179
+	if err != nil {
180
+		t.Fatalf("Unexpected error %v", err)
181
+	}
182
+	var deployment api.Deployment
183
+	err = runtime.DecodeInto([]byte(resp.Node.Value), &deployment)
184
+	if err != nil {
185
+		t.Errorf("unexpected error: %v", err)
186
+	}
187
+
188
+	if deployment.ID != "foo" {
189
+		t.Errorf("Unexpected deployment: %#v %s", deployment, resp.Node.Value)
190
+	}
191
+}
192
+
193
+func TestEtcdCreateAlreadyExistsDeployments(t *testing.T) {
194
+	fakeClient := tools.NewFakeEtcdClient(t)
195
+	fakeClient.Data["/deployments/foo"] = tools.EtcdResponseWithError{
196
+		R: &etcd.Response{
197
+			Node: &etcd.Node{
198
+				Value: runtime.EncodeOrDie(api.Deployment{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
199
+			},
200
+		},
201
+		E: nil,
202
+	}
203
+	registry := NewTestEtcd(fakeClient)
204
+	err := registry.CreateDeployment(&api.Deployment{
205
+		JSONBase: kubeapi.JSONBase{
206
+			ID: "foo",
207
+		},
208
+	})
209
+	if err == nil {
210
+		t.Error("Unexpected non-error")
211
+	}
212
+	if !errors.IsAlreadyExists(err) {
213
+		t.Errorf("Expected 'already exists' error, got %#v", err)
214
+	}
215
+}
216
+
217
+func TestEtcdUpdateOkDeployments(t *testing.T) {
218
+	fakeClient := tools.NewFakeEtcdClient(t)
219
+	registry := NewTestEtcd(fakeClient)
220
+	err := registry.UpdateDeployment(&api.Deployment{})
221
+	if err != nil {
222
+		t.Error("Unexpected error")
223
+	}
224
+}
225
+
226
+func TestEtcdDeleteNotFoundDeployments(t *testing.T) {
227
+	fakeClient := tools.NewFakeEtcdClient(t)
228
+	fakeClient.Err = tools.EtcdErrorNotFound
229
+	registry := NewTestEtcd(fakeClient)
230
+	err := registry.DeleteDeployment("foo")
231
+	if err == nil {
232
+		t.Error("Unexpected non-error")
233
+	}
234
+	if !errors.IsNotFound(err) {
235
+		t.Errorf("Expected 'not found' error, got %#v", err)
236
+	}
237
+}
238
+
239
+func TestEtcdDeleteErrorDeployments(t *testing.T) {
240
+	fakeClient := tools.NewFakeEtcdClient(t)
241
+	fakeClient.Err = fmt.Errorf("Some error")
242
+	registry := NewTestEtcd(fakeClient)
243
+	err := registry.DeleteDeployment("foo")
244
+	if err == nil {
245
+		t.Error("Unexpected non-error")
246
+	}
247
+}
248
+
249
+func TestEtcdDeleteOkDeployments(t *testing.T) {
250
+	fakeClient := tools.NewFakeEtcdClient(t)
251
+	registry := NewTestEtcd(fakeClient)
252
+	key := "/deployments/foo"
253
+	err := registry.DeleteDeployment("foo")
254
+	if err != nil {
255
+		t.Errorf("Unexpected error: %#v", err)
256
+	}
257
+	if len(fakeClient.DeletedKeys) != 1 {
258
+		t.Errorf("Expected 1 delete, found %#v", fakeClient.DeletedKeys)
259
+	} else if fakeClient.DeletedKeys[0] != key {
260
+		t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
261
+	}
262
+}
263
+
264
+func TestEtcdListEmptyDeploymentConfig(t *testing.T) {
265
+	fakeClient := tools.NewFakeEtcdClient(t)
266
+	key := "/deploymentConfigs"
267
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
268
+		R: &etcd.Response{
269
+			Node: &etcd.Node{
270
+				Nodes: []*etcd.Node{},
271
+			},
272
+		},
273
+		E: nil,
274
+	}
275
+	registry := NewTestEtcd(fakeClient)
276
+	deploymentConfigs, err := registry.ListDeploymentConfigs(labels.Everything())
277
+	if err != nil {
278
+		t.Errorf("unexpected error: %v", err)
279
+	}
280
+
281
+	if len(deploymentConfigs.Items) != 0 {
282
+		t.Errorf("Unexpected deploymentConfigs list: %#v", deploymentConfigs)
283
+	}
284
+}
285
+
286
+func TestEtcdListErrorDeploymentConfig(t *testing.T) {
287
+	fakeClient := tools.NewFakeEtcdClient(t)
288
+	key := "/deploymentConfigs"
289
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
290
+		R: &etcd.Response{
291
+			Node: nil,
292
+		},
293
+		E: fmt.Errorf("some error"),
294
+	}
295
+	registry := NewTestEtcd(fakeClient)
296
+	deploymentConfigs, err := registry.ListDeploymentConfigs(labels.Everything())
297
+	if err == nil {
298
+		t.Error("unexpected nil error")
299
+	}
300
+
301
+	if deploymentConfigs != nil {
302
+		t.Errorf("Unexpected non-nil deploymentConfigs: %#v", deploymentConfigs)
303
+	}
304
+}
305
+
306
+func TestEtcdListEverythingDeploymentConfig(t *testing.T) {
307
+	fakeClient := tools.NewFakeEtcdClient(t)
308
+	key := "/deploymentConfigs"
309
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
310
+		R: &etcd.Response{
311
+			Node: &etcd.Node{
312
+				Nodes: []*etcd.Node{
313
+					{
314
+						Value: runtime.EncodeOrDie(api.DeploymentConfig{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
315
+					},
316
+					{
317
+						Value: runtime.EncodeOrDie(api.DeploymentConfig{JSONBase: kubeapi.JSONBase{ID: "bar"}}),
318
+					},
319
+				},
320
+			},
321
+		},
322
+		E: nil,
323
+	}
324
+	registry := NewTestEtcd(fakeClient)
325
+	deploymentConfigs, err := registry.ListDeploymentConfigs(labels.Everything())
326
+	if err != nil {
327
+		t.Errorf("unexpected error: %v", err)
328
+	}
329
+
330
+	if len(deploymentConfigs.Items) != 2 || deploymentConfigs.Items[0].ID != "foo" || deploymentConfigs.Items[1].ID != "bar" {
331
+		t.Errorf("Unexpected deploymentConfigs list: %#v", deploymentConfigs)
332
+	}
333
+}
334
+
335
+func TestEtcdListFilteredDeploymentConfig(t *testing.T) {
336
+	fakeClient := tools.NewFakeEtcdClient(t)
337
+	key := "/deploymentConfigs"
338
+	fakeClient.Data[key] = tools.EtcdResponseWithError{
339
+		R: &etcd.Response{
340
+			Node: &etcd.Node{
341
+				Nodes: []*etcd.Node{
342
+					{
343
+						Value: runtime.EncodeOrDie(api.DeploymentConfig{
344
+							JSONBase: kubeapi.JSONBase{ID: "foo"},
345
+							Labels:   map[string]string{"env": "prod"},
346
+						}),
347
+					},
348
+					{
349
+						Value: runtime.EncodeOrDie(api.DeploymentConfig{
350
+							JSONBase: kubeapi.JSONBase{ID: "bar"},
351
+							Labels:   map[string]string{"env": "dev"},
352
+						}),
353
+					},
354
+				},
355
+			},
356
+		},
357
+		E: nil,
358
+	}
359
+	registry := NewTestEtcd(fakeClient)
360
+	deploymentConfigs, err := registry.ListDeploymentConfigs(labels.SelectorFromSet(labels.Set{"env": "dev"}))
361
+	if err != nil {
362
+		t.Errorf("unexpected error: %v", err)
363
+	}
364
+
365
+	if len(deploymentConfigs.Items) != 1 || deploymentConfigs.Items[0].ID != "bar" {
366
+		t.Errorf("Unexpected deploymentConfigs list: %#v", deploymentConfigs)
367
+	}
368
+}
369
+
370
+func TestEtcdGetDeploymentConfig(t *testing.T) {
371
+	fakeClient := tools.NewFakeEtcdClient(t)
372
+	fakeClient.Set("/deploymentConfigs/foo", runtime.EncodeOrDie(api.DeploymentConfig{JSONBase: kubeapi.JSONBase{ID: "foo"}}), 0)
373
+	registry := NewTestEtcd(fakeClient)
374
+	deployment, err := registry.GetDeploymentConfig("foo")
375
+	if err != nil {
376
+		t.Errorf("unexpected error: %v", err)
377
+	}
378
+
379
+	if deployment.ID != "foo" {
380
+		t.Errorf("Unexpected deployment: %#v", deployment)
381
+	}
382
+}
383
+
384
+func TestEtcdGetNotFoundDeploymentConfig(t *testing.T) {
385
+	fakeClient := tools.NewFakeEtcdClient(t)
386
+	fakeClient.Data["/deploymentConfigs/foo"] = tools.EtcdResponseWithError{
387
+		R: &etcd.Response{
388
+			Node: nil,
389
+		},
390
+		E: tools.EtcdErrorNotFound,
391
+	}
392
+	registry := NewTestEtcd(fakeClient)
393
+	deployment, err := registry.GetDeploymentConfig("foo")
394
+	if err == nil {
395
+		t.Errorf("Unexpected non-error.")
396
+	}
397
+	if deployment != nil {
398
+		t.Errorf("Unexpected deployment: %#v", deployment)
399
+	}
400
+}
401
+
402
+func TestEtcdCreateDeploymentConfig(t *testing.T) {
403
+	fakeClient := tools.NewFakeEtcdClient(t)
404
+	fakeClient.TestIndex = true
405
+	fakeClient.Data["/deploymentConfigs/foo"] = tools.EtcdResponseWithError{
406
+		R: &etcd.Response{
407
+			Node: nil,
408
+		},
409
+		E: tools.EtcdErrorNotFound,
410
+	}
411
+	registry := NewTestEtcd(fakeClient)
412
+	err := registry.CreateDeploymentConfig(&api.DeploymentConfig{
413
+		JSONBase: kubeapi.JSONBase{
414
+			ID: "foo",
415
+		},
416
+	})
417
+	if err != nil {
418
+		t.Fatalf("unexpected error: %v", err)
419
+	}
420
+
421
+	resp, err := fakeClient.Get("/deploymentConfigs/foo", false, false)
422
+	if err != nil {
423
+		t.Fatalf("Unexpected error %v", err)
424
+	}
425
+	var d api.DeploymentConfig
426
+	err = runtime.DecodeInto([]byte(resp.Node.Value), &d)
427
+	if err != nil {
428
+		t.Errorf("unexpected error: %v", err)
429
+	}
430
+
431
+	if d.ID != "foo" {
432
+		t.Errorf("Unexpected deploymentConfig: %#v %s", d, resp.Node.Value)
433
+	}
434
+}
435
+
436
+func TestEtcdCreateAlreadyExistsDeploymentConfig(t *testing.T) {
437
+	fakeClient := tools.NewFakeEtcdClient(t)
438
+	fakeClient.Data["/deploymentConfigs/foo"] = tools.EtcdResponseWithError{
439
+		R: &etcd.Response{
440
+			Node: &etcd.Node{
441
+				Value: runtime.EncodeOrDie(api.DeploymentConfig{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
442
+			},
443
+		},
444
+		E: nil,
445
+	}
446
+	registry := NewTestEtcd(fakeClient)
447
+	err := registry.CreateDeploymentConfig(&api.DeploymentConfig{
448
+		JSONBase: kubeapi.JSONBase{
449
+			ID: "foo",
450
+		},
451
+	})
452
+	if err == nil {
453
+		t.Error("Unexpected non-error")
454
+	}
455
+	if !errors.IsAlreadyExists(err) {
456
+		t.Errorf("Expected 'already exists' error, got %#v", err)
457
+	}
458
+}
459
+
460
+func TestEtcdUpdateOkDeploymentConfig(t *testing.T) {
461
+	fakeClient := tools.NewFakeEtcdClient(t)
462
+	registry := NewTestEtcd(fakeClient)
463
+	err := registry.UpdateDeploymentConfig(&api.DeploymentConfig{})
464
+	if err != nil {
465
+		t.Error("Unexpected error")
466
+	}
467
+}
468
+
469
+func TestEtcdDeleteNotFoundDeploymentConfig(t *testing.T) {
470
+	fakeClient := tools.NewFakeEtcdClient(t)
471
+	fakeClient.Err = tools.EtcdErrorNotFound
472
+	registry := NewTestEtcd(fakeClient)
473
+	err := registry.DeleteDeploymentConfig("foo")
474
+	if err == nil {
475
+		t.Error("Unexpected non-error")
476
+	}
477
+	if !errors.IsNotFound(err) {
478
+		t.Errorf("Expected 'not found' error, got %#v", err)
479
+	}
480
+}
481
+
482
+func TestEtcdDeleteErrorDeploymentConfig(t *testing.T) {
483
+	fakeClient := tools.NewFakeEtcdClient(t)
484
+	fakeClient.Err = fmt.Errorf("Some error")
485
+	registry := NewTestEtcd(fakeClient)
486
+	err := registry.DeleteDeploymentConfig("foo")
487
+	if err == nil {
488
+		t.Error("Unexpected non-error")
489
+	}
490
+}
491
+
492
+func TestEtcdDeleteOkDeploymentConfig(t *testing.T) {
493
+	fakeClient := tools.NewFakeEtcdClient(t)
494
+	registry := NewTestEtcd(fakeClient)
495
+	key := "/deploymentConfigs/foo"
496
+	err := registry.DeleteDeploymentConfig("foo")
497
+	if err != nil {
498
+		t.Errorf("Unexpected error: %#v", err)
499
+	}
500
+	if len(fakeClient.DeletedKeys) != 1 {
501
+		t.Errorf("Expected 1 delete, found %#v", fakeClient.DeletedKeys)
502
+	} else if fakeClient.DeletedKeys[0] != key {
503
+		t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
504
+	}
505
+}
0 506
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package test
1
+
2
+import (
3
+	"sync"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
6
+	"github.com/openshift/origin/pkg/deploy/api"
7
+)
8
+
9
+type DeploymentRegistry struct {
10
+	Err         error
11
+	Deployment  *api.Deployment
12
+	Deployments *api.DeploymentList
13
+	sync.Mutex
14
+}
15
+
16
+func NewDeploymentRegistry() *DeploymentRegistry {
17
+	return &DeploymentRegistry{}
18
+}
19
+
20
+func (r *DeploymentRegistry) ListDeployments(selector labels.Selector) (*api.DeploymentList, error) {
21
+	r.Lock()
22
+	defer r.Unlock()
23
+
24
+	return r.Deployments, r.Err
25
+}
26
+
27
+func (r *DeploymentRegistry) GetDeployment(id string) (*api.Deployment, error) {
28
+	r.Lock()
29
+	defer r.Unlock()
30
+
31
+	return r.Deployment, r.Err
32
+}
33
+
34
+func (r *DeploymentRegistry) CreateDeployment(deployment *api.Deployment) error {
35
+	r.Lock()
36
+	defer r.Unlock()
37
+
38
+	r.Deployment = deployment
39
+	return r.Err
40
+}
41
+
42
+func (r *DeploymentRegistry) UpdateDeployment(deployment *api.Deployment) error {
43
+	r.Lock()
44
+	defer r.Unlock()
45
+
46
+	r.Deployment = deployment
47
+	return r.Err
48
+}
49
+
50
+func (r *DeploymentRegistry) DeleteDeployment(id string) error {
51
+	r.Lock()
52
+	defer r.Unlock()
53
+
54
+	return r.Err
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package test
1
+
2
+import (
3
+	"sync"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
6
+	"github.com/openshift/origin/pkg/deploy/api"
7
+)
8
+
9
+type DeploymentConfigRegistry struct {
10
+	Err               error
11
+	DeploymentConfig  *api.DeploymentConfig
12
+	DeploymentConfigs *api.DeploymentConfigList
13
+	sync.Mutex
14
+}
15
+
16
+func NewDeploymentConfigRegistry() *DeploymentConfigRegistry {
17
+	return &DeploymentConfigRegistry{}
18
+}
19
+
20
+func (r *DeploymentConfigRegistry) ListDeploymentConfigs(selector labels.Selector) (*api.DeploymentConfigList, error) {
21
+	r.Lock()
22
+	defer r.Unlock()
23
+
24
+	return r.DeploymentConfigs, r.Err
25
+}
26
+
27
+func (r *DeploymentConfigRegistry) GetDeploymentConfig(id string) (*api.DeploymentConfig, error) {
28
+	r.Lock()
29
+	defer r.Unlock()
30
+
31
+	return r.DeploymentConfig, r.Err
32
+}
33
+
34
+func (r *DeploymentConfigRegistry) CreateDeploymentConfig(image *api.DeploymentConfig) error {
35
+	r.Lock()
36
+	defer r.Unlock()
37
+
38
+	r.DeploymentConfig = image
39
+	return r.Err
40
+}
41
+
42
+func (r *DeploymentConfigRegistry) UpdateDeploymentConfig(image *api.DeploymentConfig) error {
43
+	r.Lock()
44
+	defer r.Unlock()
45
+
46
+	r.DeploymentConfig = image
47
+	return r.Err
48
+}
49
+
50
+func (r *DeploymentConfigRegistry) DeleteDeploymentConfig(id string) error {
51
+	r.Lock()
52
+	defer r.Unlock()
53
+
54
+	return r.Err
55
+}