Browse code

Merge pull request #10789 from bparees/newapp_app_label

Merged by openshift-bot

OpenShift Bot authored on 2016/09/28 23:34:52
Showing 5 changed files
... ...
@@ -249,16 +249,24 @@ func (o *NewAppOptions) RunNewApp() error {
249 249
 		return err
250 250
 	}
251 251
 
252
-	// if the user has set the "app" label explicitly on their objects in the template,
253
-	// we should not return a failure when we can't set it ourselves.
254
-	ignoreLabelFailure := false
255
-	if len(config.Labels) == 0 && len(result.Name) > 0 {
256
-		config.Labels = map[string]string{"app": result.Name}
257
-		ignoreLabelFailure = true
252
+	// set labels explicitly supplied by the user on the command line
253
+	if err := setLabels(config.Labels, result); err != nil {
254
+		return err
258 255
 	}
259 256
 
260
-	if err := setLabels(config.Labels, result, ignoreLabelFailure); err != nil {
261
-		return err
257
+	if len(result.Name) > 0 {
258
+		// only set the computed implicit "app" label on objects if no object we've produced
259
+		// already has the "app" label.
260
+		appLabel := map[string]string{"app": result.Name}
261
+		hasAppLabel, err := hasLabel(appLabel, result)
262
+		if err != nil {
263
+			return err
264
+		}
265
+		if !hasAppLabel {
266
+			if err := setLabels(appLabel, result); err != nil {
267
+				return err
268
+			}
269
+		}
262 270
 	}
263 271
 
264 272
 	if err := setAnnotations(map[string]string{newcmd.GeneratedByNamespace: newcmd.GeneratedByNewApp}, result); err != nil {
... ...
@@ -507,16 +515,30 @@ func setAnnotations(annotations map[string]string, result *newcmd.AppResult) err
507 507
 	return nil
508 508
 }
509 509
 
510
-func setLabels(labels map[string]string, result *newcmd.AppResult, ignoreFailure bool) error {
510
+func setLabels(labels map[string]string, result *newcmd.AppResult) error {
511 511
 	for _, object := range result.List.Items {
512 512
 		err := util.AddObjectLabels(object, labels)
513
-		if err != nil && !ignoreFailure {
513
+		if err != nil {
514 514
 			return err
515 515
 		}
516 516
 	}
517 517
 	return nil
518 518
 }
519 519
 
520
+func hasLabel(labels map[string]string, result *newcmd.AppResult) (bool, error) {
521
+	for _, obj := range result.List.Items {
522
+		objCopy, err := kapi.Scheme.DeepCopy(obj)
523
+		if err != nil {
524
+			return false, err
525
+		}
526
+		err = util.AddObjectLabelsWithFlags(objCopy.(runtime.Object), labels, util.ErrorOnExistingDstKey)
527
+		if err != nil {
528
+			return true, nil
529
+		}
530
+	}
531
+	return false, nil
532
+}
533
+
520 534
 // isInvalidTriggerError returns true if the given error is
521 535
 // a validation error that contains 'invalid trigger type' in its
522 536
 // error message. This error is returned from older servers that
... ...
@@ -208,7 +208,7 @@ func (o *NewBuildOptions) RunNewBuild() error {
208 208
 		config.Labels = map[string]string{"build": result.Name}
209 209
 	}
210 210
 
211
-	if err := setLabels(config.Labels, result, false); err != nil {
211
+	if err := setLabels(config.Labels, result); err != nil {
212 212
 		return err
213 213
 	}
214 214
 	if err := setAnnotations(map[string]string{newcmd.GeneratedByNamespace: newcmd.GeneratedByNewBuild}, result); err != nil {
... ...
@@ -18,8 +18,9 @@ const (
18 18
 	ErrorOnDifferentDstKeyValue
19 19
 )
20 20
 
21
-// AddObjectLabels adds new label(s) to a single runtime.Object
22
-func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
21
+// AddObjectLabelsWithFlags will set labels on the target object.  Label overwrite behavior
22
+// is controlled by the flags argument.
23
+func AddObjectLabelsWithFlags(obj runtime.Object, labels labels.Set, flags int) error {
23 24
 	if labels == nil {
24 25
 		return nil
25 26
 	}
... ...
@@ -39,12 +40,12 @@ func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
39 39
 
40 40
 		switch objType := obj.(type) {
41 41
 		case *deployapi.DeploymentConfig:
42
-			if err := addDeploymentConfigNestedLabels(objType, labels); err != nil {
42
+			if err := addDeploymentConfigNestedLabels(objType, labels, flags); err != nil {
43 43
 				return fmt.Errorf("unable to add nested labels to %s/%s: %v", obj.GetObjectKind().GroupVersionKind(), accessor.GetName(), err)
44 44
 			}
45 45
 		}
46 46
 
47
-		if err := MergeInto(metaLabels, labels, OverwriteExistingDstKey); err != nil {
47
+		if err := MergeInto(metaLabels, labels, flags); err != nil {
48 48
 			return fmt.Errorf("unable to add labels to %s/%s: %v", obj.GetObjectKind().GroupVersionKind(), accessor.GetName(), err)
49 49
 		}
50 50
 
... ...
@@ -68,7 +69,7 @@ func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
68 68
 						existing = found
69 69
 					}
70 70
 				}
71
-				if err := MergeInto(existing, labels, OverwriteExistingDstKey); err != nil {
71
+				if err := MergeInto(existing, labels, flags); err != nil {
72 72
 					return err
73 73
 				}
74 74
 				m["labels"] = mapToGeneric(existing)
... ...
@@ -83,7 +84,7 @@ func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
83 83
 			if found, ok := interfaceToStringMap(obj); ok {
84 84
 				existing = found
85 85
 			}
86
-			if err := MergeInto(existing, labels, OverwriteExistingDstKey); err != nil {
86
+			if err := MergeInto(existing, labels, flags); err != nil {
87 87
 				return err
88 88
 			}
89 89
 			unstruct.Object["labels"] = mapToGeneric(existing)
... ...
@@ -92,6 +93,13 @@ func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
92 92
 	}
93 93
 
94 94
 	return nil
95
+
96
+}
97
+
98
+// AddObjectLabels adds new label(s) to a single runtime.Object, overwriting
99
+// existing labels that have the same key.
100
+func AddObjectLabels(obj runtime.Object, labels labels.Set) error {
101
+	return AddObjectLabelsWithFlags(obj, labels, OverwriteExistingDstKey)
95 102
 }
96 103
 
97 104
 // AddObjectAnnotations adds new annotation(s) to a single runtime.Object
... ...
@@ -168,11 +176,11 @@ func AddObjectAnnotations(obj runtime.Object, annotations map[string]string) err
168 168
 }
169 169
 
170 170
 // addDeploymentConfigNestedLabels adds new label(s) to a nested labels of a single DeploymentConfig object
171
-func addDeploymentConfigNestedLabels(obj *deployapi.DeploymentConfig, labels labels.Set) error {
171
+func addDeploymentConfigNestedLabels(obj *deployapi.DeploymentConfig, labels labels.Set, flags int) error {
172 172
 	if obj.Spec.Template.Labels == nil {
173 173
 		obj.Spec.Template.Labels = make(map[string]string)
174 174
 	}
175
-	if err := MergeInto(obj.Spec.Template.Labels, labels, OverwriteExistingDstKey); err != nil {
175
+	if err := MergeInto(obj.Spec.Template.Labels, labels, flags); err != nil {
176 176
 		return fmt.Errorf("unable to add labels to Template.DeploymentConfig.Template.ControllerTemplate.Template: %v", err)
177 177
 	}
178 178
 	return nil
... ...
@@ -47,6 +47,7 @@ os::cmd::expect_success 'oc new-app php mysql'
47 47
 os::cmd::expect_success 'oc delete all -l app=php'
48 48
 os::cmd::expect_failure 'oc get dc/mysql'
49 49
 os::cmd::expect_failure 'oc get dc/php'
50
+os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-without-app-label.json -o yaml' 'app: ruby-helloworld-sample'
50 51
 
51 52
 # ensure non-duplicate invalid label errors show up
52 53
 os::cmd::expect_failure_and_text 'oc new-app nginx -l qwer1345%$$#=self' 'error: ImageStream "nginx" is invalid'
... ...
@@ -63,10 +64,10 @@ os::cmd::expect_success_and_text 'oc new-app ruby-helloworld-sample -o yaml' 'AD
63 63
 os::cmd::expect_success_and_text 'oc new-app ruby-helloworld-sample --param MYSQL_PASSWORD=hello -o yaml' 'hello'
64 64
 
65 65
 # verify we can create from a template when some objects in the template declare an app label
66
-# the app label should still be applied to the other objects in the template.
67
-os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-with-app-label.json -o yaml' 'app: ruby-helloworld-sample'
68
-# verify the existing app label on an object is overridden by new-app
69
-os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/template-with-app-label.json -o yaml' 'app: myapp'
66
+# the app label will not be applied to any objects in the template.
67
+os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/template-with-app-label.json -o yaml' 'app: ruby-helloworld-sample'
68
+# verify the existing app label on an object is not overridden by new-app
69
+os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-with-app-label.json -o yaml' 'app: myapp'
70 70
 
71 71
 # verify that a template can be passed in stdin
72 72
 os::cmd::expect_success 'cat examples/sample-app/application-template-stibuild.json | oc new-app -o yaml -f -'
73 73
new file mode 100644
... ...
@@ -0,0 +1,482 @@
0
+{
1
+  "kind": "Template",
2
+  "apiVersion": "v1",
3
+  "metadata": {
4
+    "name": "ruby-helloworld-sample",
5
+    "creationTimestamp": null,
6
+    "annotations": {
7
+      "description": "some objects in this template declare their own app label to confirm new-app will tolerate it",
8
+      "iconClass": "icon-ruby",
9
+      "tags": "instant-app,ruby,mysql"
10
+    }
11
+  },
12
+  "objects": [
13
+    {
14
+      "kind": "Service",
15
+      "apiVersion": "v1",
16
+      "metadata": {
17
+        "name": "frontend",
18
+        "creationTimestamp": null
19
+      },
20
+      "spec": {
21
+        "ports": [
22
+          {
23
+            "name": "web",
24
+            "protocol": "TCP",
25
+            "port": 5432,
26
+            "targetPort": 8080,
27
+            "nodePort": 0
28
+          }
29
+        ],
30
+        "selector": {
31
+          "name": "frontend"
32
+        },
33
+        "portalIP": "",
34
+        "type": "ClusterIP",
35
+        "sessionAffinity": "None"
36
+      },
37
+      "status": {
38
+        "loadBalancer": {}
39
+      }
40
+    },
41
+    {
42
+      "kind": "Route",
43
+      "apiVersion": "v1",
44
+      "metadata": {
45
+        "name": "route-edge",
46
+        "creationTimestamp": null
47
+      },
48
+      "spec": {
49
+        "host": "www.example.com",
50
+        "to": {
51
+          "kind": "Service",
52
+          "name": "frontend"
53
+        },
54
+        "tls": {
55
+          "termination": "edge",
56
+          "certificate": "-----BEGIN CERTIFICATE-----\nMIIDIjCCAgqgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBoTELMAkGA1UEBhMCVVMx\nCzAJBgNVBAgMAlNDMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl\nZmF1bHQgQ29tcGFueSBMdGQxEDAOBgNVBAsMB1Rlc3QgQ0ExGjAYBgNVBAMMEXd3\ndy5leGFtcGxlY2EuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUu\nY29tMB4XDTE1MDExMjE0MTk0MVoXDTE2MDExMjE0MTk0MVowfDEYMBYGA1UEAwwP\nd3d3LmV4YW1wbGUuY29tMQswCQYDVQQIDAJTQzELMAkGA1UEBhMCVVMxIjAgBgkq\nhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20xEDAOBgNVBAoMB0V4YW1wbGUx\nEDAOBgNVBAsMB0V4YW1wbGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrv\ngu6ZTTefNN7jjiZbS/xvQjyXjYMN7oVXv76jbX8gjMOmg9m0xoVZZFAE4XyQDuCm\n47VRx5Qrf/YLXmB2VtCFvB0AhXr5zSeWzPwaAPrjA4ebG+LUo24ziS8KqNxrFs1M\nmNrQUgZyQC6XIe1JHXc9t+JlL5UZyZQC1IfaJulDAgMBAAGjDTALMAkGA1UdEwQC\nMAAwDQYJKoZIhvcNAQEFBQADggEBAFCi7ZlkMnESvzlZCvv82Pq6S46AAOTPXdFd\nTMvrh12E1sdVALF1P1oYFJzG1EiZ5ezOx88fEDTW+Lxb9anw5/KJzwtWcfsupf1m\nV7J0D3qKzw5C1wjzYHh9/Pz7B1D0KthQRATQCfNf8s6bbFLaw/dmiIUhHLtIH5Qc\nyfrejTZbOSP77z8NOWir+BWWgIDDB2//3AkDIQvT20vmkZRhkqSdT7et4NmXOX/j\njhPti4b2Fie0LeuvgaOdKjCpQQNrYthZHXeVlOLRhMTSk3qUczenkKTOhvP7IS9q\n+Dzv5hqgSfvMG392KWh5f8xXfJNs4W5KLbZyl901MeReiLrPH3w=\n-----END CERTIFICATE-----",
57
+          "key": "-----BEGIN PRIVATE KEY-----\nMIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMrvgu6ZTTefNN7j\njiZbS/xvQjyXjYMN7oVXv76jbX8gjMOmg9m0xoVZZFAE4XyQDuCm47VRx5Qrf/YL\nXmB2VtCFvB0AhXr5zSeWzPwaAPrjA4ebG+LUo24ziS8KqNxrFs1MmNrQUgZyQC6X\nIe1JHXc9t+JlL5UZyZQC1IfaJulDAgMBAAECgYEAnxOjEj/vrLNLMZE1Q9H7PZVF\nWdP/JQVNvQ7tCpZ3ZdjxHwkvf//aQnuxS5yX2Rnf37BS/TZu+TIkK4373CfHomSx\nUTAn2FsLmOJljupgGcoeLx5K5nu7B7rY5L1NHvdpxZ4YjeISrRtEPvRakllENU5y\ngJE8c2eQOx08ZSRE4TkCQQD7dws2/FldqwdjJucYijsJVuUdoTqxP8gWL6bB251q\nelP2/a6W2elqOcWId28560jG9ZS3cuKvnmu/4LG88vZFAkEAzphrH3673oTsHN+d\nuBd5uyrlnGjWjuiMKv2TPITZcWBjB8nJDSvLneHF59MYwejNNEof2tRjgFSdImFH\nmi995wJBAMtPjW6wiqRz0i41VuT9ZgwACJBzOdvzQJfHgSD9qgFb1CU/J/hpSRIM\nkYvrXK9MbvQFvG6x4VuyT1W8mpe1LK0CQAo8VPpffhFdRpF7psXLK/XQ/0VLkG3O\nKburipLyBg/u9ZkaL0Ley5zL5dFBjTV2Qkx367Ic2b0u9AYTCcgi2DsCQQD3zZ7B\nv7BOm7MkylKokY2MduFFXU0Bxg6pfZ7q3rvg8gqhUFbaMStPRYg6myiDiW/JfLhF\nTcFT4touIo7oriFJ\n-----END PRIVATE KEY-----",
58
+          "caCertificate": "-----BEGIN CERTIFICATE-----\nMIIEFzCCAv+gAwIBAgIJALK1iUpF2VQLMA0GCSqGSIb3DQEBBQUAMIGhMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCU0MxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoG\nA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UECwwHVGVzdCBDQTEaMBgG\nA1UEAwwRd3d3LmV4YW1wbGVjYS5jb20xIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVA\nZXhhbXBsZS5jb20wHhcNMTUwMTEyMTQxNTAxWhcNMjUwMTA5MTQxNTAxWjCBoTEL\nMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlNDMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkx\nHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAOBgNVBAsMB1Rlc3QgQ0Ex\nGjAYBgNVBAMMEXd3dy5leGFtcGxlY2EuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFt\ncGxlQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nw2rK1J2NMtQj0KDug7g7HRKl5jbf0QMkMKyTU1fBtZ0cCzvsF4CqV11LK4BSVWaK\nrzkaXe99IVJnH8KdOlDl5Dh/+cJ3xdkClSyeUT4zgb6CCBqg78ePp+nN11JKuJlV\nIG1qdJpB1J5O/kCLsGcTf7RS74MtqMFo96446Zvt7YaBhWPz6gDaO/TUzfrNcGLA\nEfHVXkvVWqb3gqXUztZyVex/gtP9FXQ7gxTvJml7UkmT0VAFjtZnCqmFxpLZFZ15\n+qP9O7Q2MpsGUO/4vDAuYrKBeg1ZdPSi8gwqUP2qWsGd9MIWRv3thI2903BczDc7\nr8WaIbm37vYZAS9G56E4+wIDAQABo1AwTjAdBgNVHQ4EFgQUugLrSJshOBk5TSsU\nANs4+SmJUGwwHwYDVR0jBBgwFoAUugLrSJshOBk5TSsUANs4+SmJUGwwDAYDVR0T\nBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaMJ33zAMV4korHo5aPfayV3uHoYZ\n1ChzP3eSsF+FjoscpoNSKs91ZXZF6LquzoNezbfiihK4PYqgwVD2+O0/Ty7UjN4S\nqzFKVR4OS/6lCJ8YncxoFpTntbvjgojf1DEataKFUN196PAANc3yz8cWHF4uvjPv\nWkgFqbIjb+7D1YgglNyovXkRDlRZl0LD1OQ0ZWhd4Ge1qx8mmmanoBeYZ9+DgpFC\nj9tQAbS867yeOryNe7sEOIpXAAqK/DTu0hB6+ySsDfMo4piXCc2aA/eI2DCuw08e\nw17Dz9WnupZjVdwTKzDhFgJZMLDqn37HQnT6EemLFqbcR0VPEnfyhDtZIQ==\n-----END CERTIFICATE-----"
59
+        }
60
+      },
61
+      "status": {}
62
+    },
63
+    {
64
+      "kind": "ImageStream",
65
+      "apiVersion": "v1",
66
+      "metadata": {
67
+        "name": "origin-ruby-sample",
68
+        "creationTimestamp": null
69
+      },
70
+      "spec": {},
71
+      "status": {
72
+        "dockerImageRepository": ""
73
+      }
74
+    },
75
+    {
76
+      "kind": "ImageStream",
77
+      "apiVersion": "v1",
78
+      "metadata": {
79
+        "name": "ruby-22-centos7",
80
+        "creationTimestamp": null
81
+      },
82
+      "spec": {
83
+        "dockerImageRepository": "centos/ruby-22-centos7"
84
+      },
85
+      "status": {
86
+        "dockerImageRepository": ""
87
+      }
88
+    },
89
+    {
90
+      "kind": "BuildConfig",
91
+      "apiVersion": "v1",
92
+      "metadata": {
93
+        "name": "ruby-sample-build",
94
+        "creationTimestamp": null,
95
+        "labels": {
96
+          "name": "ruby-sample-build"
97
+        }
98
+      },
99
+      "spec": {
100
+        "triggers": [
101
+          {
102
+            "type": "GitHub",
103
+            "github": {
104
+              "secret": "secret101"
105
+            }
106
+          },
107
+          {
108
+            "type": "Generic",
109
+            "generic": {
110
+              "secret": "secret101"
111
+            }
112
+          },
113
+          {
114
+            "type": "ImageChange",
115
+            "imageChange": {}
116
+          },
117
+          {
118
+            "type": "ConfigChange"
119
+          }
120
+        ],
121
+        "source": {
122
+          "type": "Git",
123
+          "git": {
124
+            "uri": "https://github.com/openshift/ruby-hello-world.git"
125
+          }
126
+        },
127
+        "strategy": {
128
+          "type": "Source",
129
+          "sourceStrategy": {
130
+            "from": {
131
+              "kind": "ImageStreamTag",
132
+              "name": "ruby-22-centos7:latest"
133
+            },
134
+            "env": [
135
+              {
136
+                "name": "EXAMPLE",
137
+                "value": "sample-app"
138
+              }
139
+            ]
140
+          }
141
+        },
142
+        "output": {
143
+          "to": {
144
+            "kind": "ImageStreamTag",
145
+            "name": "origin-ruby-sample:latest"
146
+          }
147
+        },
148
+        "postCommit": {
149
+          "args": ["bundle", "exec", "rake", "test"]
150
+        },
151
+        "resources": {}
152
+      },
153
+      "status": {
154
+        "lastVersion": 0
155
+      }
156
+    },
157
+    {
158
+      "kind": "DeploymentConfig",
159
+      "apiVersion": "v1",
160
+      "metadata": {
161
+        "name": "frontend",
162
+        "creationTimestamp": null
163
+      },
164
+      "spec": {
165
+        "strategy": {
166
+          "type": "Rolling",
167
+          "rollingParams": {
168
+            "updatePeriodSeconds": 1,
169
+            "intervalSeconds": 1,
170
+            "timeoutSeconds": 120,
171
+            "pre": {
172
+              "failurePolicy": "Abort",
173
+              "execNewPod": {
174
+                "command": [
175
+                  "/bin/true"
176
+                ],
177
+                "env": [
178
+                  {
179
+                    "name": "CUSTOM_VAR1",
180
+                    "value": "custom_value1"
181
+                  }
182
+                ],
183
+                "containerName": "ruby-helloworld"
184
+              }
185
+            },
186
+            "post": {
187
+              "failurePolicy": "Ignore",
188
+              "execNewPod": {
189
+                "command": [
190
+                  "/bin/false"
191
+                ],
192
+                "env": [
193
+                  {
194
+                    "name": "CUSTOM_VAR2",
195
+                    "value": "custom_value2"
196
+                  }
197
+                ],
198
+                "containerName": "ruby-helloworld"
199
+              }
200
+            }
201
+          },
202
+          "resources": {}
203
+        },
204
+        "triggers": [
205
+          {
206
+            "type": "ImageChange",
207
+            "imageChangeParams": {
208
+              "automatic": true,
209
+              "containerNames": [
210
+                "ruby-helloworld"
211
+              ],
212
+              "from": {
213
+                "kind": "ImageStreamTag",
214
+                "name": "origin-ruby-sample:latest"
215
+              }
216
+            }
217
+          },
218
+          {
219
+            "type": "ConfigChange"
220
+          }
221
+        ],
222
+        "replicas": 2,
223
+        "selector": {
224
+          "name": "frontend"
225
+        },
226
+        "template": {
227
+          "metadata": {
228
+            "creationTimestamp": null,
229
+            "labels": {
230
+              "name": "frontend"
231
+            }
232
+          },
233
+          "spec": {
234
+            "containers": [
235
+              {
236
+                "name": "ruby-helloworld",
237
+                "image": "origin-ruby-sample",
238
+                "ports": [
239
+                  {
240
+                    "containerPort": 8080,
241
+                    "protocol": "TCP"
242
+                  }
243
+                ],
244
+                "env": [
245
+                  {
246
+                    "name": "ADMIN_USERNAME",
247
+                    "value": "${ADMIN_USERNAME}"
248
+                  },
249
+                  {
250
+                    "name": "ADMIN_PASSWORD",
251
+                    "value": "${ADMIN_PASSWORD}"
252
+                  },
253
+                  {
254
+                    "name": "MYSQL_USER",
255
+                    "value": "${MYSQL_USER}"
256
+                  },
257
+                  {
258
+                    "name": "MYSQL_PASSWORD",
259
+                    "value": "${MYSQL_PASSWORD}"
260
+                  },
261
+                  {
262
+                    "name": "MYSQL_DATABASE",
263
+                    "value": "${MYSQL_DATABASE}"
264
+                  }
265
+                ],
266
+                "resources": {},
267
+                "terminationMessagePath": "/dev/termination-log",
268
+                "imagePullPolicy": "IfNotPresent",
269
+                "securityContext": {
270
+                  "capabilities": {},
271
+                  "privileged": false
272
+                }
273
+              }
274
+            ],
275
+            "restartPolicy": "Always",
276
+            "dnsPolicy": "ClusterFirst"
277
+          }
278
+        }
279
+      },
280
+      "status": {}
281
+    },
282
+    {
283
+      "kind": "Service",
284
+      "apiVersion": "v1",
285
+      "metadata": {
286
+        "name": "database",
287
+        "creationTimestamp": null
288
+      },
289
+      "spec": {
290
+        "ports": [
291
+          {
292
+            "name": "db",
293
+            "protocol": "TCP",
294
+            "port": 5434,
295
+            "targetPort": 3306,
296
+            "nodePort": 0
297
+          }
298
+        ],
299
+        "selector": {
300
+          "name": "database"
301
+        },
302
+        "portalIP": "",
303
+        "type": "ClusterIP",
304
+        "sessionAffinity": "None"
305
+      },
306
+      "status": {
307
+        "loadBalancer": {}
308
+      }
309
+    },
310
+    {
311
+      "kind": "DeploymentConfig",
312
+      "apiVersion": "v1",
313
+      "metadata": {
314
+        "name": "database",
315
+        "creationTimestamp": null
316
+      },
317
+      "spec": {
318
+        "strategy": {
319
+          "type": "Recreate",
320
+          "recreateParams": {
321
+            "pre": {
322
+              "failurePolicy": "Abort",
323
+              "execNewPod": {
324
+                "command": [
325
+                  "/bin/true"
326
+                ],
327
+                "env": [
328
+                  {
329
+                    "name": "CUSTOM_VAR1",
330
+                    "value": "custom_value1"
331
+                  }
332
+                ],
333
+                "containerName": "ruby-helloworld-database",
334
+                "volumes": ["ruby-helloworld-data"]
335
+              }
336
+            },
337
+            "mid": {
338
+              "failurePolicy": "Abort",
339
+              "execNewPod": {
340
+                "command": [
341
+                  "/bin/true"
342
+                ],
343
+                "env": [
344
+                  {
345
+                    "name": "CUSTOM_VAR2",
346
+                    "value": "custom_value2"
347
+                  }
348
+                ],
349
+                "containerName": "ruby-helloworld-database",
350
+                "volumes": ["ruby-helloworld-data"]
351
+              }
352
+            },
353
+            "post": {
354
+              "failurePolicy": "Ignore",
355
+              "execNewPod": {
356
+                "command": [
357
+                  "/bin/false"
358
+                ],
359
+                "env": [
360
+                  {
361
+                    "name": "CUSTOM_VAR2",
362
+                    "value": "custom_value2"
363
+                  }
364
+                ],
365
+                "containerName": "ruby-helloworld-database",
366
+                "volumes": ["ruby-helloworld-data"]
367
+              }
368
+            }
369
+          },
370
+          "resources": {}
371
+        },
372
+        "triggers": [
373
+          {
374
+            "type": "ConfigChange"
375
+          }
376
+        ],
377
+        "replicas": 1,
378
+        "selector": {
379
+          "name": "database"
380
+        },
381
+        "template": {
382
+          "metadata": {
383
+            "creationTimestamp": null,
384
+            "labels": {
385
+              "name": "database"
386
+            }
387
+          },
388
+          "spec": {
389
+            "containers": [
390
+              {
391
+                "name": "ruby-helloworld-database",
392
+                "image": "openshift/mysql-55-centos7:latest",
393
+                "ports": [
394
+                  {
395
+                    "containerPort": 3306,
396
+                    "protocol": "TCP"
397
+                  }
398
+                ],
399
+                "env": [
400
+                  {
401
+                    "name": "MYSQL_USER",
402
+                    "value": "${MYSQL_USER}"
403
+                  },
404
+                  {
405
+                    "name": "MYSQL_PASSWORD",
406
+                    "value": "${MYSQL_PASSWORD}"
407
+                  },
408
+                  {
409
+                    "name": "MYSQL_DATABASE",
410
+                    "value": "${MYSQL_DATABASE}"
411
+                  }
412
+                ],
413
+                "resources": {},
414
+                "volumeMounts": [
415
+                  {
416
+                    "name": "ruby-helloworld-data",
417
+                    "mountPath": "/var/lib/mysql/data"
418
+                  }
419
+                ],
420
+                "terminationMessagePath": "/dev/termination-log",
421
+                "imagePullPolicy": "Always",
422
+                "securityContext": {
423
+                  "capabilities": {},
424
+                  "privileged": false
425
+                }
426
+              }
427
+            ],
428
+            "volumes": [
429
+              {
430
+                "name": "ruby-helloworld-data",
431
+                "emptyDir": {
432
+                  "medium": ""
433
+                }
434
+              }
435
+            ],
436
+            "restartPolicy": "Always",
437
+            "dnsPolicy": "ClusterFirst"
438
+          }
439
+        }
440
+      },
441
+      "status": {}
442
+    }
443
+  ],
444
+  "parameters": [
445
+    {
446
+      "name": "ADMIN_USERNAME",
447
+      "description": "administrator username",
448
+      "generate": "expression",
449
+      "from": "admin[A-Z0-9]{3}"
450
+    },
451
+    {
452
+      "name": "ADMIN_PASSWORD",
453
+      "description": "administrator password",
454
+      "generate": "expression",
455
+      "from": "[a-zA-Z0-9]{8}"
456
+    },
457
+    {
458
+      "name": "MYSQL_USER",
459
+      "description": "database username",
460
+      "generate": "expression",
461
+      "from": "user[A-Z0-9]{3}",
462
+      "required": true
463
+    },
464
+    {
465
+      "name": "MYSQL_PASSWORD",
466
+      "description": "database password",
467
+      "generate": "expression",
468
+      "from": "[a-zA-Z0-9]{8}",
469
+      "required": true
470
+    },
471
+    {
472
+      "name": "MYSQL_DATABASE",
473
+      "description": "database name",
474
+      "value": "root",
475
+      "required": true
476
+    }
477
+  ],
478
+  "labels": {
479
+    "template": "application-template-stibuild"
480
+  }
481
+}