Browse code

Issue #333, #528 - add number to builds

Maciej Szulik authored on 2015/03/11 23:17:34
Showing 11 changed files
... ...
@@ -265,6 +265,9 @@ type BuildConfig struct {
265 265
 	// are defined, a new build can only occur as a result of an explicit client build creation.
266 266
 	Triggers []BuildTriggerPolicy `json:"triggers,omitempty"`
267 267
 
268
+	// LastVersion is used to inform about number of last triggered build.
269
+	LastVersion int `json:"lastVersion,omitempty"`
270
+
268 271
 	// Parameters holds all the input necessary to produce a new build. A build config may only
269 272
 	// define either the Output.To or Output.DockerImageReference fields, but not both.
270 273
 	Parameters BuildParameters `json:"parameters,omitempty"`
... ...
@@ -281,6 +281,9 @@ type BuildConfig struct {
281 281
 	// are defined, a new build can only occur as a result of an explicit client build creation.
282 282
 	Triggers []BuildTriggerPolicy `json:"triggers,omitempty"`
283 283
 
284
+	// LastVersion is used to inform about number of last triggered build.
285
+	LastVersion int `json:"lastVersion,omitempty"`
286
+
284 287
 	// Parameters holds all the input necessary to produce a new build. A build config may only
285 288
 	// define either the Output.To or Output.DockerImageReference fields, but not both.
286 289
 	Parameters BuildParameters `json:"parameters,omitempty"`
... ...
@@ -2,10 +2,12 @@ package util
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+
5 6
 	"github.com/golang/glog"
6 7
 
7 8
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9 11
 	buildapi "github.com/openshift/origin/pkg/build/api"
10 12
 	osclient "github.com/openshift/origin/pkg/client"
11 13
 	imageapi "github.com/openshift/origin/pkg/image/api"
... ...
@@ -14,6 +16,7 @@ import (
14 14
 // GenerateBuildFromConfig creates a new build based on a given BuildConfig. Optionally a SourceRevision for the new
15 15
 // build can be specified.  Also optionally a list of image names to be substituted can be supplied.  Values in the BuildConfig
16 16
 // that have a substitution provided will be replaced in the resulting Build
17
+// You NEED to update BuildConfig object after calling this!
17 18
 func GenerateBuildFromConfig(bc *buildapi.BuildConfig, ref *buildapi.SourceRevision, imageSubstitutions map[string]string,
18 19
 	imageRepoSubstitutions map[kapi.ObjectReference]string) (build *buildapi.Build) {
19 20
 	// Need to copy the buildConfig here so that it doesn't share pointers with
... ...
@@ -29,6 +32,7 @@ func GenerateBuildFromConfig(bc *buildapi.BuildConfig, ref *buildapi.SourceRevis
29 29
 			Revision: ref,
30 30
 		},
31 31
 		ObjectMeta: kapi.ObjectMeta{
32
+			Name:   GetNextBuildName(bc),
32 33
 			Labels: bcCopy.Labels,
33 34
 		},
34 35
 	}
... ...
@@ -59,6 +63,7 @@ func GenerateBuildFromBuild(build *buildapi.Build) *buildapi.Build {
59 59
 	return &buildapi.Build{
60 60
 		Parameters: buildCopy.Parameters,
61 61
 		ObjectMeta: kapi.ObjectMeta{
62
+			Name:   getNextBuildNameFromBuild(buildCopy),
62 63
 			Labels: buildCopy.ObjectMeta.Labels,
63 64
 		},
64 65
 	}
... ...
@@ -71,6 +76,7 @@ func GenerateBuildFromBuild(build *buildapi.Build) *buildapi.Build {
71 71
 // an ImageChangeTrigger).  If there is a match in the image repo list, the resulting build will use
72 72
 // the image tag from the corresponding image repo rather than the image field from the buildconfig
73 73
 // as the base image for the build.
74
+// You NEED to update BuildConfig object after calling this!
74 75
 func GenerateBuildWithImageTag(config *buildapi.BuildConfig, revision *buildapi.SourceRevision, imageRepoGetter osclient.ImageRepositoryNamespaceGetter) (*buildapi.Build, error) {
75 76
 	var build *buildapi.Build
76 77
 	var err error
... ...
@@ -241,3 +247,14 @@ func SubstituteImageRepoReferences(build *buildapi.Build, imageRepo kapi.ObjectR
241 241
 func GetBuildPodName(build *buildapi.Build) string {
242 242
 	return fmt.Sprintf("pod-%s", build.Name)
243 243
 }
244
+
245
+// GetNextBuildName returns name of the next build and increments BuildConfig's LastVersion.
246
+func GetNextBuildName(bc *buildapi.BuildConfig) string {
247
+	bc.LastVersion++
248
+	return fmt.Sprintf("%s-%d", bc.Name, bc.LastVersion)
249
+}
250
+
251
+// getNextBuildNameFromBuild returns name of the next build with random uuid added at the end
252
+func getNextBuildNameFromBuild(build *buildapi.Build) string {
253
+	return fmt.Sprintf("%s-%d", build.Name, int32(util.Now().Unix()))
254
+}
... ...
@@ -3,6 +3,7 @@ package util
3 3
 import (
4 4
 	"fmt"
5 5
 	"reflect"
6
+	"regexp"
6 7
 	"testing"
7 8
 
8 9
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
... ...
@@ -597,6 +598,24 @@ func TestGetBuildPodName(t *testing.T) {
597 597
 	}
598 598
 }
599 599
 
600
+func TestGetNextBuildName(t *testing.T) {
601
+	bc := mockBuildConfig(mockSource(), mockSTIStrategyForImage(), mockOutput())
602
+	if expected, actual := bc.Name+"-1", GetNextBuildName(bc); expected != actual {
603
+		t.Errorf("Wrong buildName, expected %s, got %s", expected, actual)
604
+	}
605
+	if expected, actual := 1, bc.LastVersion; expected != actual {
606
+		t.Errorf("Wrong version, expected %d, got %d", expected, actual)
607
+	}
608
+}
609
+
610
+func TestGetNextBuildNameFromBuild(t *testing.T) {
611
+	buildName := getNextBuildNameFromBuild(&buildapi.Build{ObjectMeta: kapi.ObjectMeta{Name: "mybuild-1"}})
612
+	if matched, err := regexp.MatchString(`^mybuild-1-\d+$`, buildName); !matched || err != nil {
613
+		t.Errorf("Unexpected build name, got %s", buildName)
614
+	}
615
+
616
+}
617
+
600 618
 func mockSource() buildapi.BuildSource {
601 619
 	return buildapi.BuildSource{
602 620
 		Type: buildapi.BuildSourceGit,
... ...
@@ -27,10 +27,11 @@ type Plugin interface {
27 27
 
28 28
 // controller used for processing webhook requests.
29 29
 type controller struct {
30
-	buildCreator      buildclient.BuildCreator
31
-	buildConfigGetter buildclient.BuildConfigGetter
32
-	imageRepoGetter   osclient.ImageRepositoryNamespaceGetter
33
-	plugins           map[string]Plugin
30
+	buildCreator       buildclient.BuildCreator
31
+	buildConfigGetter  buildclient.BuildConfigGetter
32
+	buildConfigUpdater buildclient.BuildConfigUpdater
33
+	imageRepoGetter    osclient.ImageRepositoryNamespaceGetter
34
+	plugins            map[string]Plugin
34 35
 }
35 36
 
36 37
 // urlVars holds parsed URL parts.
... ...
@@ -43,12 +44,14 @@ type urlVars struct {
43 43
 }
44 44
 
45 45
 // NewController creates new webhook controller and feed it with provided plugins.
46
-func NewController(buildConfigGetter buildclient.BuildConfigGetter, buildCreator buildclient.BuildCreator, imageRepoGetter osclient.ImageRepositoryNamespaceGetter, plugins map[string]Plugin) http.Handler {
46
+func NewController(buildConfigGetter buildclient.BuildConfigGetter, buildConfigUpdater buildclient.BuildConfigUpdater,
47
+	buildCreator buildclient.BuildCreator, imageRepoGetter osclient.ImageRepositoryNamespaceGetter, plugins map[string]Plugin) http.Handler {
47 48
 	return &controller{
48
-		buildConfigGetter: buildConfigGetter,
49
-		buildCreator:      buildCreator,
50
-		imageRepoGetter:   imageRepoGetter,
51
-		plugins:           plugins,
49
+		buildConfigGetter:  buildConfigGetter,
50
+		buildConfigUpdater: buildConfigUpdater,
51
+		buildCreator:       buildCreator,
52
+		imageRepoGetter:    imageRepoGetter,
53
+		plugins:            plugins,
52 54
 	}
53 55
 }
54 56
 
... ...
@@ -93,6 +96,9 @@ func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request) {
93 93
 		glog.V(4).Infof("Failed creating new build: %v", err)
94 94
 		badRequest(w, err.Error())
95 95
 	}
96
+	if err := c.buildConfigUpdater.Update(buildCfg); err != nil {
97
+		badRequest(w, err.Error())
98
+	}
96 99
 }
97 100
 
98 101
 // parseURL retrieves the namespace from the query parameters and returns a context wrapping the namespace,
... ...
@@ -48,6 +48,12 @@ func (*okBuildConfigGetter) Get(namespace, name string) (*api.BuildConfig, error
48 48
 	}, nil
49 49
 }
50 50
 
51
+type okBuildConfigUpdater struct{}
52
+
53
+func (*okBuildConfigUpdater) Update(buildConfig *api.BuildConfig) error {
54
+	return nil
55
+}
56
+
51 57
 type okBuildCreator struct{}
52 58
 
53 59
 func (*okBuildCreator) Create(namespace string, build *api.Build) error {
... ...
@@ -66,6 +72,12 @@ func (*errorBuildConfigGetter) Get(namespace, name string) (*api.BuildConfig, er
66 66
 	return &api.BuildConfig{}, errors.New("BuildConfig error!")
67 67
 }
68 68
 
69
+type errorBuildConfigUpdater struct{}
70
+
71
+func (*errorBuildConfigUpdater) Update(buildConfig *api.BuildConfig) error {
72
+	return errors.New("BuildConfig error!")
73
+}
74
+
69 75
 type pathPlugin struct {
70 76
 	Path string
71 77
 }
... ...
@@ -82,7 +94,8 @@ func (*errPlugin) Extract(buildCfg *api.BuildConfig, secret, path string, req *h
82 82
 }
83 83
 
84 84
 func TestParseUrlError(t *testing.T) {
85
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
85
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
86
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
86 87
 	defer server.Close()
87 88
 
88 89
 	resp, err := http.Post(server.URL, "application/json", nil)
... ...
@@ -97,9 +110,10 @@ func TestParseUrlError(t *testing.T) {
97 97
 }
98 98
 
99 99
 func TestParseUrlOK(t *testing.T) {
100
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
101
-		"pathplugin": &pathPlugin{},
102
-	}))
100
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
101
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
102
+			"pathplugin": &pathPlugin{},
103
+		}))
103 104
 	defer server.Close()
104 105
 
105 106
 	resp, err := http.Post(server.URL+"/build100/secret101/pathplugin",
... ...
@@ -116,9 +130,10 @@ func TestParseUrlOK(t *testing.T) {
116 116
 
117 117
 func TestParseUrlLong(t *testing.T) {
118 118
 	plugin := &pathPlugin{}
119
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
120
-		"pathplugin": plugin,
121
-	}))
119
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
120
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
121
+			"pathplugin": plugin,
122
+		}))
122 123
 	defer server.Close()
123 124
 
124 125
 	resp, err := http.Post(server.URL+"/build100/secret101/pathplugin/some/more/args",
... ...
@@ -137,7 +152,8 @@ func TestParseUrlLong(t *testing.T) {
137 137
 }
138 138
 
139 139
 func TestInvokeWebhookErrorSecret(t *testing.T) {
140
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
140
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
141
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
141 142
 	defer server.Close()
142 143
 
143 144
 	resp, err := http.Post(server.URL+"/build100/wrongsecret/somePlugin",
... ...
@@ -153,7 +169,8 @@ func TestInvokeWebhookErrorSecret(t *testing.T) {
153 153
 }
154 154
 
155 155
 func TestInvokeWebhookMissingPlugin(t *testing.T) {
156
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
156
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
157
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
157 158
 	defer server.Close()
158 159
 
159 160
 	resp, err := http.Post(server.URL+"/build100/secret101/missingplugin",
... ...
@@ -170,9 +187,10 @@ func TestInvokeWebhookMissingPlugin(t *testing.T) {
170 170
 }
171 171
 
172 172
 func TestInvokeWebhookErrorBuildConfig(t *testing.T) {
173
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &errorBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
174
-		"okPlugin": &pathPlugin{},
175
-	}))
173
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
174
+		&errorBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
175
+			"okPlugin": &pathPlugin{},
176
+		}))
176 177
 	defer server.Close()
177 178
 
178 179
 	resp, err := http.Post(server.URL+"/build100/secret101/okPlugin",
... ...
@@ -189,7 +207,8 @@ func TestInvokeWebhookErrorBuildConfig(t *testing.T) {
189 189
 }
190 190
 
191 191
 func TestInvokeWebhookErrorGetConfig(t *testing.T) {
192
-	server := httptest.NewServer(NewController(&errorBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
192
+	server := httptest.NewServer(NewController(&errorBuildConfigGetter{}, &okBuildConfigUpdater{},
193
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, nil))
193 194
 	defer server.Close()
194 195
 
195 196
 	resp, err := http.Post(server.URL+"/build100/secret101/errPlugin",
... ...
@@ -206,9 +225,10 @@ func TestInvokeWebhookErrorGetConfig(t *testing.T) {
206 206
 }
207 207
 
208 208
 func TestInvokeWebhookErrorCreateBuild(t *testing.T) {
209
-	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
210
-		"errPlugin": &errPlugin{},
211
-	}))
209
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
210
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
211
+			"errPlugin": &errPlugin{},
212
+		}))
212 213
 	defer server.Close()
213 214
 
214 215
 	resp, err := http.Post(server.URL+"/build100/secret101/errPlugin",
... ...
@@ -224,6 +244,26 @@ func TestInvokeWebhookErrorCreateBuild(t *testing.T) {
224 224
 	}
225 225
 }
226 226
 
227
+func TestInvokeWebhookErrorUpdateBuildConfig(t *testing.T) {
228
+	server := httptest.NewServer(NewController(&okBuildConfigGetter{}, &errorBuildConfigUpdater{},
229
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]Plugin{
230
+			"okPlugin": &pathPlugin{},
231
+		}))
232
+	defer server.Close()
233
+
234
+	resp, err := http.Post(server.URL+"/build100/secret101/okPlugin",
235
+		"application/json", nil)
236
+	if err != nil {
237
+		t.Fatalf("Unexpected error: %v", err)
238
+	}
239
+	body, _ := ioutil.ReadAll(resp.Body)
240
+	if resp.StatusCode != http.StatusBadRequest &&
241
+		!strings.Contains(string(body), "BuildConfig error!") {
242
+		t.Errorf("Wrong response code, expecting 400, got %s: %s!", resp.Status,
243
+			string(body))
244
+	}
245
+}
246
+
227 247
 type mockOkBuildCreator struct {
228 248
 	testBuildInterface
229 249
 }
... ...
@@ -246,7 +286,7 @@ func (i *testBuildConfigInterface) Get(namespace, name string) (*api.BuildConfig
246 246
 	return i.GetBuildConfigFunc(namespace, name)
247 247
 }
248 248
 
249
-func TestInvokeWebhookOk(t *testing.T) {
249
+func TestInvokeWebhookOK(t *testing.T) {
250 250
 	var buildRequest *api.Build
251 251
 	buildConfig := &api.BuildConfig{
252 252
 		Parameters: api.BuildParameters{
... ...
@@ -268,6 +308,7 @@ func TestInvokeWebhookOk(t *testing.T) {
268 268
 				},
269 269
 			},
270 270
 		},
271
+		&okBuildConfigUpdater{},
271 272
 		&mockOkBuildCreator{
272 273
 			testBuildInterface: testBuildInterface{
273 274
 				CreateFunc: func(namespace string, build *api.Build) error {
... ...
@@ -57,6 +57,12 @@ var mockBuildStrategy api.BuildStrategy = api.BuildStrategy{
57 57
 	},
58 58
 }
59 59
 
60
+type okBuildConfigUpdater struct{}
61
+
62
+func (*okBuildConfigUpdater) Update(buildConfig *api.BuildConfig) error {
63
+	return nil
64
+}
65
+
60 66
 type okBuildCreator struct{}
61 67
 
62 68
 func (c *okBuildCreator) Create(namespace string, build *api.Build) error {
... ...
@@ -64,7 +70,8 @@ func (c *okBuildCreator) Create(namespace string, build *api.Build) error {
64 64
 }
65 65
 
66 66
 func TestWrongMethod(t *testing.T) {
67
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
67
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
68
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
68 69
 	defer server.Close()
69 70
 
70 71
 	resp, _ := http.Get(server.URL + "/build100/secret101/github")
... ...
@@ -76,7 +83,8 @@ func TestWrongMethod(t *testing.T) {
76 76
 }
77 77
 
78 78
 func TestWrongContentType(t *testing.T) {
79
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
79
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
80
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
80 81
 	defer server.Close()
81 82
 
82 83
 	client := &http.Client{}
... ...
@@ -93,7 +101,8 @@ func TestWrongContentType(t *testing.T) {
93 93
 }
94 94
 
95 95
 func TestWrongUserAgent(t *testing.T) {
96
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
96
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
97
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
97 98
 	defer server.Close()
98 99
 
99 100
 	client := &http.Client{}
... ...
@@ -110,7 +119,8 @@ func TestWrongUserAgent(t *testing.T) {
110 110
 }
111 111
 
112 112
 func TestMissingGithubEvent(t *testing.T) {
113
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
113
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
114
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
114 115
 	defer server.Close()
115 116
 
116 117
 	client := &http.Client{}
... ...
@@ -126,7 +136,8 @@ func TestMissingGithubEvent(t *testing.T) {
126 126
 }
127 127
 
128 128
 func TestWrongGithubEvent(t *testing.T) {
129
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
129
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
130
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
130 131
 	defer server.Close()
131 132
 
132 133
 	client := &http.Client{}
... ...
@@ -143,7 +154,8 @@ func TestWrongGithubEvent(t *testing.T) {
143 143
 }
144 144
 
145 145
 func TestJsonPingEvent(t *testing.T) {
146
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
146
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
147
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
147 148
 	defer server.Close()
148 149
 
149 150
 	postFile("ping", "pingevent.json", server.URL+"/build100/secret101/github",
... ...
@@ -151,14 +163,16 @@ func TestJsonPingEvent(t *testing.T) {
151 151
 }
152 152
 
153 153
 func TestJsonPushEventError(t *testing.T) {
154
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
154
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
155
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
155 156
 	defer server.Close()
156 157
 
157 158
 	post("push", []byte{}, server.URL+"/build100/secret101/github", http.StatusBadRequest, t)
158 159
 }
159 160
 
160 161
 func TestJsonPushEvent(t *testing.T) {
161
-	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
162
+	server := httptest.NewServer(webhook.NewController(&okBuildConfigGetter{}, &okBuildConfigUpdater{},
163
+		&okBuildCreator{}, &okImageRepositoryNamespaceGetter{}, map[string]webhook.Plugin{"github": New()}))
162 164
 	defer server.Close()
163 165
 
164 166
 	postFile("push", "pushevent.json", server.URL+"/build100/secret101/github",
... ...
@@ -52,9 +52,10 @@ func NewCmdStartBuild(fullName string, f *clientcmd.Factory, out io.Writer) *cob
52 52
 			checkErr(err)
53 53
 
54 54
 			var newBuild *buildapi.Build
55
+			var config *buildapi.BuildConfig
55 56
 			if len(buildName) == 0 {
56 57
 				// from build config
57
-				config, err := client.BuildConfigs(namespace).Get(args[0])
58
+				config, err = client.BuildConfigs(namespace).Get(args[0])
58 59
 				checkErr(err)
59 60
 
60 61
 				newBuild, err = buildutil.GenerateBuildWithImageTag(config, nil, client.ImageRepositories(kapi.NamespaceAll).(osclient.ImageRepositoryNamespaceGetter))
... ...
@@ -70,6 +71,11 @@ func NewCmdStartBuild(fullName string, f *clientcmd.Factory, out io.Writer) *cob
70 70
 			newBuild, err = client.Builds(namespace).Create(newBuild)
71 71
 			checkErr(err)
72 72
 
73
+			if config != nil {
74
+				_, err := client.BuildConfigs(namespace).Update(config)
75
+				checkErr(err)
76
+			}
77
+
73 78
 			if follow {
74 79
 				set := labels.Set(newBuild.Labels)
75 80
 				selector := labels.SelectorFromSet(set)
... ...
@@ -3,6 +3,7 @@ package describe
3 3
 import (
4 4
 	"fmt"
5 5
 	"reflect"
6
+	"strconv"
6 7
 	"strings"
7 8
 	"text/tabwriter"
8 9
 
... ...
@@ -234,6 +235,11 @@ func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error)
234 234
 
235 235
 	return tabbedString(func(out *tabwriter.Writer) error {
236 236
 		formatMeta(out, buildConfig.ObjectMeta)
237
+		if buildConfig.LastVersion == 0 {
238
+			formatString(out, "Latest Version", "Never built")
239
+		} else {
240
+			formatString(out, "Latest Version", strconv.Itoa(buildConfig.LastVersion))
241
+		}
237 242
 		describeBuildParameters(buildConfig.Parameters, out)
238 243
 		d.DescribeTriggers(buildConfig, d.host, out)
239 244
 		return nil
... ...
@@ -247,8 +247,10 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin
247 247
 
248 248
 func (c *MasterConfig) InstallUnprotectedAPI(container *restful.Container) []string {
249 249
 	bcClient, _ := c.BuildControllerClients()
250
+	bcGetterUpdater := buildclient.NewOSClientBuildConfigClient(bcClient)
250 251
 	handler := webhook.NewController(
251
-		buildclient.NewOSClientBuildConfigClient(bcClient),
252
+		bcGetterUpdater,
253
+		bcGetterUpdater,
252 254
 		buildclient.NewOSClientBuildClient(bcClient),
253 255
 		bcClient.ImageRepositories(kapi.NamespaceAll).(osclient.ImageRepositoryNamespaceGetter),
254 256
 		map[string]webhook.Plugin{
... ...
@@ -223,10 +223,12 @@ func NewTestBuildOpenshift(t *testing.T) *testBuildOpenshift {
223 223
 	}
224 224
 
225 225
 	openshift.whPrefix = "/osapi/v1beta1/buildConfigHooks/"
226
+	bcClient := buildclient.NewOSClientBuildConfigClient(osClient)
226 227
 	osMux.Handle(openshift.whPrefix, http.StripPrefix(openshift.whPrefix,
227
-		webhook.NewController(buildclient.NewOSClientBuildConfigClient(osClient), buildclient.NewOSClientBuildClient(osClient), osClient.ImageRepositories(kapi.NamespaceAll).(osclient.ImageRepositoryNamespaceGetter), map[string]webhook.Plugin{
228
-			"github": github.New(),
229
-		})))
228
+		webhook.NewController(bcClient, bcClient, buildclient.NewOSClientBuildClient(osClient),
229
+			osClient.ImageRepositories(kapi.NamespaceAll).(osclient.ImageRepositoryNamespaceGetter), map[string]webhook.Plugin{
230
+				"github": github.New(),
231
+			})))
230 232
 
231 233
 	bcFactory := buildcontrollerfactory.BuildControllerFactory{
232 234
 		OSClient:     osClient,