... | ... |
@@ -12959,6 +12959,43 @@ |
12959 | 12959 |
"lastTriggeredImageID": { |
12960 | 12960 |
"type": "string", |
12961 | 12961 |
"description": "used internally to save last used image ID for build" |
12962 |
+ }, |
|
12963 |
+ "from": { |
|
12964 |
+ "$ref": "v1.ObjectReference", |
|
12965 |
+ "description": "reference to an ImageStreamTag that will trigger the build" |
|
12966 |
+ } |
|
12967 |
+ } |
|
12968 |
+ }, |
|
12969 |
+ "v1.ObjectReference": { |
|
12970 |
+ "id": "v1.ObjectReference", |
|
12971 |
+ "properties": { |
|
12972 |
+ "kind": { |
|
12973 |
+ "type": "string", |
|
12974 |
+ "description": "kind of the referent; see http://releases.k8s.io/v1.0.0/docs/api-conventions.md#types-kinds" |
|
12975 |
+ }, |
|
12976 |
+ "namespace": { |
|
12977 |
+ "type": "string", |
|
12978 |
+ "description": "namespace of the referent; see http://releases.k8s.io/v1.0.0/docs/namespaces.md" |
|
12979 |
+ }, |
|
12980 |
+ "name": { |
|
12981 |
+ "type": "string", |
|
12982 |
+ "description": "name of the referent; see http://releases.k8s.io/v1.0.0/docs/identifiers.md#names" |
|
12983 |
+ }, |
|
12984 |
+ "uid": { |
|
12985 |
+ "type": "string", |
|
12986 |
+ "description": "uid of the referent; see http://releases.k8s.io/v1.0.0/docs/identifiers.md#uids" |
|
12987 |
+ }, |
|
12988 |
+ "apiVersion": { |
|
12989 |
+ "type": "string", |
|
12990 |
+ "description": "API version of the referent" |
|
12991 |
+ }, |
|
12992 |
+ "resourceVersion": { |
|
12993 |
+ "type": "string", |
|
12994 |
+ "description": "specific resourceVersion to which this reference is made, if any: http://releases.k8s.io/v1.0.0/docs/api-conventions.md#concurrency-control-and-consistency" |
|
12995 |
+ }, |
|
12996 |
+ "fieldPath": { |
|
12997 |
+ "type": "string", |
|
12998 |
+ "description": "if referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]" |
|
12962 | 12999 |
} |
12963 | 13000 |
} |
12964 | 13001 |
}, |
... | ... |
@@ -13121,39 +13158,6 @@ |
13121 | 13121 |
} |
13122 | 13122 |
} |
13123 | 13123 |
}, |
13124 |
- "v1.ObjectReference": { |
|
13125 |
- "id": "v1.ObjectReference", |
|
13126 |
- "properties": { |
|
13127 |
- "kind": { |
|
13128 |
- "type": "string", |
|
13129 |
- "description": "kind of the referent; see http://releases.k8s.io/v1.0.0/docs/api-conventions.md#types-kinds" |
|
13130 |
- }, |
|
13131 |
- "namespace": { |
|
13132 |
- "type": "string", |
|
13133 |
- "description": "namespace of the referent; see http://releases.k8s.io/v1.0.0/docs/namespaces.md" |
|
13134 |
- }, |
|
13135 |
- "name": { |
|
13136 |
- "type": "string", |
|
13137 |
- "description": "name of the referent; see http://releases.k8s.io/v1.0.0/docs/identifiers.md#names" |
|
13138 |
- }, |
|
13139 |
- "uid": { |
|
13140 |
- "type": "string", |
|
13141 |
- "description": "uid of the referent; see http://releases.k8s.io/v1.0.0/docs/identifiers.md#uids" |
|
13142 |
- }, |
|
13143 |
- "apiVersion": { |
|
13144 |
- "type": "string", |
|
13145 |
- "description": "API version of the referent" |
|
13146 |
- }, |
|
13147 |
- "resourceVersion": { |
|
13148 |
- "type": "string", |
|
13149 |
- "description": "specific resourceVersion to which this reference is made, if any: http://releases.k8s.io/v1.0.0/docs/api-conventions.md#concurrency-control-and-consistency" |
|
13150 |
- }, |
|
13151 |
- "fieldPath": { |
|
13152 |
- "type": "string", |
|
13153 |
- "description": "if referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]" |
|
13154 |
- } |
|
13155 |
- } |
|
13156 |
- }, |
|
13157 | 13124 |
"v1.EnvVar": { |
13158 | 13125 |
"id": "v1.EnvVar", |
13159 | 13126 |
"required": [ |
... | ... |
@@ -13446,6 +13450,10 @@ |
13446 | 13446 |
"triggeredByImage": { |
13447 | 13447 |
"$ref": "v1.ObjectReference", |
13448 | 13448 |
"description": "image that triggered this build" |
13449 |
+ }, |
|
13450 |
+ "from": { |
|
13451 |
+ "$ref": "v1.ObjectReference", |
|
13452 |
+ "description": "ImageStreamTag that triggered this build" |
|
13449 | 13453 |
} |
13450 | 13454 |
} |
13451 | 13455 |
}, |
... | ... |
@@ -822,6 +822,15 @@ func deepCopy_api_BuildRequest(in buildapi.BuildRequest, out *buildapi.BuildRequ |
822 | 822 |
} else { |
823 | 823 |
out.TriggeredByImage = nil |
824 | 824 |
} |
825 |
+ if in.From != nil { |
|
826 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
827 |
+ return err |
|
828 |
+ } else { |
|
829 |
+ out.From = newVal.(*api.ObjectReference) |
|
830 |
+ } |
|
831 |
+ } else { |
|
832 |
+ out.From = nil |
|
833 |
+ } |
|
825 | 834 |
return nil |
826 | 835 |
} |
827 | 836 |
|
... | ... |
@@ -1058,6 +1067,15 @@ func deepCopy_api_GitSourceRevision(in buildapi.GitSourceRevision, out *buildapi |
1058 | 1058 |
|
1059 | 1059 |
func deepCopy_api_ImageChangeTrigger(in buildapi.ImageChangeTrigger, out *buildapi.ImageChangeTrigger, c *conversion.Cloner) error { |
1060 | 1060 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1061 |
+ if in.From != nil { |
|
1062 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
1063 |
+ return err |
|
1064 |
+ } else { |
|
1065 |
+ out.From = newVal.(*api.ObjectReference) |
|
1066 |
+ } |
|
1067 |
+ } else { |
|
1068 |
+ out.From = nil |
|
1069 |
+ } |
|
1061 | 1070 |
return nil |
1062 | 1071 |
} |
1063 | 1072 |
|
... | ... |
@@ -1034,6 +1034,14 @@ func convert_api_BuildRequest_To_v1_BuildRequest(in *buildapi.BuildRequest, out |
1034 | 1034 |
} else { |
1035 | 1035 |
out.TriggeredByImage = nil |
1036 | 1036 |
} |
1037 |
+ if in.From != nil { |
|
1038 |
+ out.From = new(v1.ObjectReference) |
|
1039 |
+ if err := convert_api_ObjectReference_To_v1_ObjectReference(in.From, out.From, s); err != nil { |
|
1040 |
+ return err |
|
1041 |
+ } |
|
1042 |
+ } else { |
|
1043 |
+ out.From = nil |
|
1044 |
+ } |
|
1037 | 1045 |
return nil |
1038 | 1046 |
} |
1039 | 1047 |
|
... | ... |
@@ -1183,6 +1191,14 @@ func convert_api_ImageChangeTrigger_To_v1_ImageChangeTrigger(in *buildapi.ImageC |
1183 | 1183 |
defaulting.(func(*buildapi.ImageChangeTrigger))(in) |
1184 | 1184 |
} |
1185 | 1185 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1186 |
+ if in.From != nil { |
|
1187 |
+ out.From = new(v1.ObjectReference) |
|
1188 |
+ if err := convert_api_ObjectReference_To_v1_ObjectReference(in.From, out.From, s); err != nil { |
|
1189 |
+ return err |
|
1190 |
+ } |
|
1191 |
+ } else { |
|
1192 |
+ out.From = nil |
|
1193 |
+ } |
|
1186 | 1194 |
return nil |
1187 | 1195 |
} |
1188 | 1196 |
|
... | ... |
@@ -1382,6 +1398,14 @@ func convert_v1_BuildRequest_To_api_BuildRequest(in *buildapiv1.BuildRequest, ou |
1382 | 1382 |
} else { |
1383 | 1383 |
out.TriggeredByImage = nil |
1384 | 1384 |
} |
1385 |
+ if in.From != nil { |
|
1386 |
+ out.From = new(api.ObjectReference) |
|
1387 |
+ if err := convert_v1_ObjectReference_To_api_ObjectReference(in.From, out.From, s); err != nil { |
|
1388 |
+ return err |
|
1389 |
+ } |
|
1390 |
+ } else { |
|
1391 |
+ out.From = nil |
|
1392 |
+ } |
|
1385 | 1393 |
return nil |
1386 | 1394 |
} |
1387 | 1395 |
|
... | ... |
@@ -1531,6 +1555,14 @@ func convert_v1_ImageChangeTrigger_To_api_ImageChangeTrigger(in *buildapiv1.Imag |
1531 | 1531 |
defaulting.(func(*buildapiv1.ImageChangeTrigger))(in) |
1532 | 1532 |
} |
1533 | 1533 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1534 |
+ if in.From != nil { |
|
1535 |
+ out.From = new(api.ObjectReference) |
|
1536 |
+ if err := convert_v1_ObjectReference_To_api_ObjectReference(in.From, out.From, s); err != nil { |
|
1537 |
+ return err |
|
1538 |
+ } |
|
1539 |
+ } else { |
|
1540 |
+ out.From = nil |
|
1541 |
+ } |
|
1534 | 1542 |
return nil |
1535 | 1543 |
} |
1536 | 1544 |
|
... | ... |
@@ -803,6 +803,15 @@ func deepCopy_v1_BuildRequest(in buildapiv1.BuildRequest, out *buildapiv1.BuildR |
803 | 803 |
} else { |
804 | 804 |
out.TriggeredByImage = nil |
805 | 805 |
} |
806 |
+ if in.From != nil { |
|
807 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
808 |
+ return err |
|
809 |
+ } else { |
|
810 |
+ out.From = newVal.(*v1.ObjectReference) |
|
811 |
+ } |
|
812 |
+ } else { |
|
813 |
+ out.From = nil |
|
814 |
+ } |
|
806 | 815 |
return nil |
807 | 816 |
} |
808 | 817 |
|
... | ... |
@@ -1039,6 +1048,15 @@ func deepCopy_v1_GitSourceRevision(in buildapiv1.GitSourceRevision, out *buildap |
1039 | 1039 |
|
1040 | 1040 |
func deepCopy_v1_ImageChangeTrigger(in buildapiv1.ImageChangeTrigger, out *buildapiv1.ImageChangeTrigger, c *conversion.Cloner) error { |
1041 | 1041 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1042 |
+ if in.From != nil { |
|
1043 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
1044 |
+ return err |
|
1045 |
+ } else { |
|
1046 |
+ out.From = newVal.(*v1.ObjectReference) |
|
1047 |
+ } |
|
1048 |
+ } else { |
|
1049 |
+ out.From = nil |
|
1050 |
+ } |
|
1042 | 1051 |
return nil |
1043 | 1052 |
} |
1044 | 1053 |
|
... | ... |
@@ -1072,6 +1072,14 @@ func convert_api_BuildRequest_To_v1beta3_BuildRequest(in *buildapi.BuildRequest, |
1072 | 1072 |
} else { |
1073 | 1073 |
out.TriggeredByImage = nil |
1074 | 1074 |
} |
1075 |
+ if in.From != nil { |
|
1076 |
+ out.From = new(v1beta3.ObjectReference) |
|
1077 |
+ if err := convert_api_ObjectReference_To_v1beta3_ObjectReference(in.From, out.From, s); err != nil { |
|
1078 |
+ return err |
|
1079 |
+ } |
|
1080 |
+ } else { |
|
1081 |
+ out.From = nil |
|
1082 |
+ } |
|
1075 | 1083 |
return nil |
1076 | 1084 |
} |
1077 | 1085 |
|
... | ... |
@@ -1221,6 +1229,14 @@ func convert_api_ImageChangeTrigger_To_v1beta3_ImageChangeTrigger(in *buildapi.I |
1221 | 1221 |
defaulting.(func(*buildapi.ImageChangeTrigger))(in) |
1222 | 1222 |
} |
1223 | 1223 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1224 |
+ if in.From != nil { |
|
1225 |
+ out.From = new(v1beta3.ObjectReference) |
|
1226 |
+ if err := convert_api_ObjectReference_To_v1beta3_ObjectReference(in.From, out.From, s); err != nil { |
|
1227 |
+ return err |
|
1228 |
+ } |
|
1229 |
+ } else { |
|
1230 |
+ out.From = nil |
|
1231 |
+ } |
|
1224 | 1232 |
return nil |
1225 | 1233 |
} |
1226 | 1234 |
|
... | ... |
@@ -1420,6 +1436,14 @@ func convert_v1beta3_BuildRequest_To_api_BuildRequest(in *buildapiv1beta3.BuildR |
1420 | 1420 |
} else { |
1421 | 1421 |
out.TriggeredByImage = nil |
1422 | 1422 |
} |
1423 |
+ if in.From != nil { |
|
1424 |
+ out.From = new(api.ObjectReference) |
|
1425 |
+ if err := convert_v1beta3_ObjectReference_To_api_ObjectReference(in.From, out.From, s); err != nil { |
|
1426 |
+ return err |
|
1427 |
+ } |
|
1428 |
+ } else { |
|
1429 |
+ out.From = nil |
|
1430 |
+ } |
|
1423 | 1431 |
return nil |
1424 | 1432 |
} |
1425 | 1433 |
|
... | ... |
@@ -1569,6 +1593,14 @@ func convert_v1beta3_ImageChangeTrigger_To_api_ImageChangeTrigger(in *buildapiv1 |
1569 | 1569 |
defaulting.(func(*buildapiv1beta3.ImageChangeTrigger))(in) |
1570 | 1570 |
} |
1571 | 1571 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1572 |
+ if in.From != nil { |
|
1573 |
+ out.From = new(api.ObjectReference) |
|
1574 |
+ if err := convert_v1beta3_ObjectReference_To_api_ObjectReference(in.From, out.From, s); err != nil { |
|
1575 |
+ return err |
|
1576 |
+ } |
|
1577 |
+ } else { |
|
1578 |
+ out.From = nil |
|
1579 |
+ } |
|
1572 | 1580 |
return nil |
1573 | 1581 |
} |
1574 | 1582 |
|
... | ... |
@@ -811,6 +811,15 @@ func deepCopy_v1beta3_BuildRequest(in buildapiv1beta3.BuildRequest, out *buildap |
811 | 811 |
} else { |
812 | 812 |
out.TriggeredByImage = nil |
813 | 813 |
} |
814 |
+ if in.From != nil { |
|
815 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
816 |
+ return err |
|
817 |
+ } else { |
|
818 |
+ out.From = newVal.(*v1beta3.ObjectReference) |
|
819 |
+ } |
|
820 |
+ } else { |
|
821 |
+ out.From = nil |
|
822 |
+ } |
|
814 | 823 |
return nil |
815 | 824 |
} |
816 | 825 |
|
... | ... |
@@ -1047,6 +1056,15 @@ func deepCopy_v1beta3_GitSourceRevision(in buildapiv1beta3.GitSourceRevision, ou |
1047 | 1047 |
|
1048 | 1048 |
func deepCopy_v1beta3_ImageChangeTrigger(in buildapiv1beta3.ImageChangeTrigger, out *buildapiv1beta3.ImageChangeTrigger, c *conversion.Cloner) error { |
1049 | 1049 |
out.LastTriggeredImageID = in.LastTriggeredImageID |
1050 |
+ if in.From != nil { |
|
1051 |
+ if newVal, err := c.DeepCopy(in.From); err != nil { |
|
1052 |
+ return err |
|
1053 |
+ } else { |
|
1054 |
+ out.From = newVal.(*v1beta3.ObjectReference) |
|
1055 |
+ } |
|
1056 |
+ } else { |
|
1057 |
+ out.From = nil |
|
1058 |
+ } |
|
1050 | 1059 |
return nil |
1051 | 1060 |
} |
1052 | 1061 |
|
... | ... |
@@ -356,6 +356,12 @@ type ImageChangeTrigger struct { |
356 | 356 |
// LastTriggeredImageID is used internally by the ImageChangeController to save last |
357 | 357 |
// used image ID for build |
358 | 358 |
LastTriggeredImageID string |
359 |
+ |
|
360 |
+ // From is a reference to an ImageStreamTag that will trigger a build when updated |
|
361 |
+ // It is optional. If no From is specified, the From image from the build strategy |
|
362 |
+ // will be used. Only one ImageChangeTrigger with an empty From reference is allowed in |
|
363 |
+ // a build configuration. |
|
364 |
+ From *kapi.ObjectReference |
|
359 | 365 |
} |
360 | 366 |
|
361 | 367 |
// BuildTriggerPolicy describes a policy for a single trigger that results in a new Build. |
... | ... |
@@ -453,6 +459,9 @@ type BuildRequest struct { |
453 | 453 |
|
454 | 454 |
// TriggeredByImage is the Image that triggered this build. |
455 | 455 |
TriggeredByImage *kapi.ObjectReference |
456 |
+ |
|
457 |
+ // From is the reference to the ImageStreamTag that triggered the build. |
|
458 |
+ From *kapi.ObjectReference |
|
456 | 459 |
} |
457 | 460 |
|
458 | 461 |
// BuildLogOptions is the REST options for a build log |
... | ... |
@@ -341,6 +341,12 @@ type ImageChangeTrigger struct { |
341 | 341 |
// LastTriggeredImageID is used internally by the ImageChangeController to save last |
342 | 342 |
// used image ID for build |
343 | 343 |
LastTriggeredImageID string `json:"lastTriggeredImageID,omitempty" description:"used internally to save last used image ID for build"` |
344 |
+ |
|
345 |
+ // From is a reference to an ImageStreamTag that will trigger a build when updated |
|
346 |
+ // It is optional. If no From is specified, the From image from the build strategy |
|
347 |
+ // will be used. Only one ImageChangeTrigger with an empty From reference is allowed in |
|
348 |
+ // a build configuration. |
|
349 |
+ From *kapi.ObjectReference `json:"from,omitempty" description:"reference to an ImageStreamTag that will trigger the build"` |
|
344 | 350 |
} |
345 | 351 |
|
346 | 352 |
// BuildTriggerPolicy describes a policy for a single trigger that results in a new Build. |
... | ... |
@@ -427,6 +433,9 @@ type BuildRequest struct { |
427 | 427 |
|
428 | 428 |
// TriggeredByImage is the Image that triggered this build. |
429 | 429 |
TriggeredByImage *kapi.ObjectReference `json:"triggeredByImage,omitempty" description:"image that triggered this build"` |
430 |
+ |
|
431 |
+ // From is the reference to the ImageStreamTag that triggered the build. |
|
432 |
+ From *kapi.ObjectReference `json:"from,omitempty" description:"ImageStreamTag that triggered this build"` |
|
430 | 433 |
} |
431 | 434 |
|
432 | 435 |
// BuildLogOptions is the REST options for a build log |
... | ... |
@@ -330,6 +330,12 @@ type ImageChangeTrigger struct { |
330 | 330 |
// LastTriggeredImageID is used internally by the ImageChangeController to save last |
331 | 331 |
// used image ID for build |
332 | 332 |
LastTriggeredImageID string `json:"lastTriggeredImageID,omitempty"` |
333 |
+ |
|
334 |
+ // From is a reference to an ImageStreamTag that will trigger a build when updated |
|
335 |
+ // It is optional. If no From is specified, the From image from the build strategy |
|
336 |
+ // will be used. Only one ImageChangeTrigger with an empty From reference is allowed in |
|
337 |
+ // a build configuration. |
|
338 |
+ From *kapi.ObjectReference `json:"from,omitempty" description:"reference to an ImageStreamTag that will trigger the build"` |
|
333 | 339 |
} |
334 | 340 |
|
335 | 341 |
// BuildTriggerPolicy describes a policy for a single trigger that results in a new Build. |
... | ... |
@@ -409,6 +415,9 @@ type BuildRequest struct { |
409 | 409 |
|
410 | 410 |
// TriggeredByImage is the Image that triggered this build. |
411 | 411 |
TriggeredByImage *kapi.ObjectReference `json:"triggeredByImage,omitempty"` |
412 |
+ |
|
413 |
+ // From is the reference to the ImageStreamTag that triggered the build. |
|
414 |
+ From *kapi.ObjectReference `json:"from,omitempty" description:"ImageStreamTag that triggered this build"` |
|
412 | 415 |
} |
413 | 416 |
|
414 | 417 |
// BuildLogOptions is the REST options for a build log |
... | ... |
@@ -11,6 +11,7 @@ import ( |
11 | 11 |
|
12 | 12 |
oapi "github.com/openshift/origin/pkg/api" |
13 | 13 |
buildapi "github.com/openshift/origin/pkg/build/api" |
14 |
+ buildutil "github.com/openshift/origin/pkg/build/util" |
|
14 | 15 |
imageapi "github.com/openshift/origin/pkg/image/api" |
15 | 16 |
) |
16 | 17 |
|
... | ... |
@@ -36,22 +37,43 @@ func ValidateBuildUpdate(build *buildapi.Build, older *buildapi.Build) fielderro |
36 | 36 |
return allErrs |
37 | 37 |
} |
38 | 38 |
|
39 |
+// refKey returns a key for the given ObjectReference. If the ObjectReference |
|
40 |
+// doesn't include a namespace, the passed in namespace is used for the reference |
|
41 |
+func refKey(namespace string, ref *kapi.ObjectReference) string { |
|
42 |
+ if ref == nil || ref.Kind != "ImageStreamTag" { |
|
43 |
+ return "nil" |
|
44 |
+ } |
|
45 |
+ ns := ref.Namespace |
|
46 |
+ if ns == "" { |
|
47 |
+ ns = namespace |
|
48 |
+ } |
|
49 |
+ return fmt.Sprintf("%s/%s", ns, ref.Name) |
|
50 |
+} |
|
51 |
+ |
|
39 | 52 |
// ValidateBuildConfig tests required fields for a Build. |
40 | 53 |
func ValidateBuildConfig(config *buildapi.BuildConfig) fielderrors.ValidationErrorList { |
41 | 54 |
allErrs := fielderrors.ValidationErrorList{} |
42 | 55 |
allErrs = append(allErrs, validation.ValidateObjectMeta(&config.ObjectMeta, true, validation.NameIsDNSSubdomain).Prefix("metadata")...) |
43 | 56 |
|
44 |
- // allow only one ImageChangeTrigger for now |
|
45 |
- ictCount := 0 |
|
57 |
+ // image change triggers that refer |
|
58 |
+ fromRefs := map[string]struct{}{} |
|
46 | 59 |
for i, trg := range config.Spec.Triggers { |
47 | 60 |
allErrs = append(allErrs, validateTrigger(&trg).PrefixIndex(i).Prefix("triggers")...) |
48 |
- if trg.Type == buildapi.ImageChangeBuildTriggerType { |
|
49 |
- if ictCount++; ictCount > 1 { |
|
50 |
- allErrs = append(allErrs, fielderrors.NewFieldInvalid("triggers", config.Spec.Triggers, "only one ImageChange trigger is allowed")) |
|
51 |
- break |
|
52 |
- } |
|
61 |
+ if trg.Type != buildapi.ImageChangeBuildTriggerType || trg.ImageChange == nil { |
|
62 |
+ continue |
|
63 |
+ } |
|
64 |
+ from := trg.ImageChange.From |
|
65 |
+ if from == nil { |
|
66 |
+ from = buildutil.GetImageStreamForStrategy(config.Spec.Strategy) |
|
53 | 67 |
} |
68 |
+ fromKey := refKey(config.Namespace, from) |
|
69 |
+ _, exists := fromRefs[fromKey] |
|
70 |
+ if exists { |
|
71 |
+ allErrs = append(allErrs, fielderrors.NewFieldInvalid("triggers", config.Spec.Triggers, "multiple ImageChange triggers refer to the same image stream tag")) |
|
72 |
+ } |
|
73 |
+ fromRefs[fromKey] = struct{}{} |
|
54 | 74 |
} |
75 |
+ |
|
55 | 76 |
allErrs = append(allErrs, validateBuildSpec(&config.Spec.BuildSpec).Prefix("spec")...) |
56 | 77 |
return allErrs |
57 | 78 |
} |
... | ... |
@@ -311,7 +333,20 @@ func validateTrigger(trigger *buildapi.BuildTriggerPolicy) fielderrors.Validatio |
311 | 311 |
case buildapi.ImageChangeBuildTriggerType: |
312 | 312 |
if trigger.ImageChange == nil { |
313 | 313 |
allErrs = append(allErrs, fielderrors.NewFieldRequired("imageChange")) |
314 |
+ break |
|
315 |
+ } |
|
316 |
+ if trigger.ImageChange.From == nil { |
|
317 |
+ break |
|
318 |
+ } |
|
319 |
+ if kind := trigger.ImageChange.From.Kind; kind != "ImageStreamTag" { |
|
320 |
+ invalidKindErr := fielderrors.NewFieldInvalid( |
|
321 |
+ "imageChange.from.kind", |
|
322 |
+ kind, |
|
323 |
+ "only an ImageStreamTag type of reference is allowed in an ImageChange trigger.") |
|
324 |
+ allErrs = append(allErrs, invalidKindErr) |
|
325 |
+ break |
|
314 | 326 |
} |
327 |
+ allErrs = append(allErrs, validateFromImageReference(trigger.ImageChange.From).Prefix("from")...) |
|
315 | 328 |
default: |
316 | 329 |
allErrs = append(allErrs, fielderrors.NewFieldInvalid("type", trigger.Type, "invalid trigger type")) |
317 | 330 |
} |
... | ... |
@@ -1,7 +1,6 @@ |
1 | 1 |
package validation |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "strings" |
|
5 | 4 |
"testing" |
6 | 5 |
|
7 | 6 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
... | ... |
@@ -168,6 +167,12 @@ func TestBuildConfigValidationSuccess(t *testing.T) { |
168 | 168 |
}, |
169 | 169 |
}, |
170 | 170 |
}, |
171 |
+ Triggers: []buildapi.BuildTriggerPolicy{ |
|
172 |
+ { |
|
173 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
174 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
175 |
+ }, |
|
176 |
+ }, |
|
171 | 177 |
}, |
172 | 178 |
} |
173 | 179 |
if result := ValidateBuildConfig(buildConfig); len(result) > 0 { |
... | ... |
@@ -213,54 +218,253 @@ func TestBuildConfigValidationFailureRequiredName(t *testing.T) { |
213 | 213 |
} |
214 | 214 |
} |
215 | 215 |
|
216 |
-func TestBuildConfigValidationFailureTooManyICT(t *testing.T) { |
|
217 |
- buildConfig := &buildapi.BuildConfig{ |
|
218 |
- ObjectMeta: kapi.ObjectMeta{Name: "bar", Namespace: "foo"}, |
|
219 |
- Spec: buildapi.BuildConfigSpec{ |
|
220 |
- BuildSpec: buildapi.BuildSpec{ |
|
221 |
- Source: buildapi.BuildSource{ |
|
222 |
- Type: buildapi.BuildSourceGit, |
|
223 |
- Git: &buildapi.GitBuildSource{ |
|
224 |
- URI: "http://github.com/my/repository", |
|
216 |
+func TestBuildConfigImageChangeTriggers(t *testing.T) { |
|
217 |
+ tests := []struct { |
|
218 |
+ name string |
|
219 |
+ triggers []buildapi.BuildTriggerPolicy |
|
220 |
+ expectError bool |
|
221 |
+ errorType fielderrors.ValidationErrorType |
|
222 |
+ }{ |
|
223 |
+ { |
|
224 |
+ name: "valid default trigger", |
|
225 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
226 |
+ { |
|
227 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
228 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
229 |
+ }, |
|
230 |
+ }, |
|
231 |
+ expectError: false, |
|
232 |
+ }, |
|
233 |
+ { |
|
234 |
+ name: "more than one default trigger", |
|
235 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
236 |
+ { |
|
237 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
238 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
239 |
+ }, |
|
240 |
+ { |
|
241 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
242 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
243 |
+ }, |
|
244 |
+ }, |
|
245 |
+ expectError: true, |
|
246 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
247 |
+ }, |
|
248 |
+ { |
|
249 |
+ name: "missing image change struct", |
|
250 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
251 |
+ { |
|
252 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
253 |
+ }, |
|
254 |
+ }, |
|
255 |
+ expectError: true, |
|
256 |
+ errorType: fielderrors.ValidationErrorTypeRequired, |
|
257 |
+ }, |
|
258 |
+ { |
|
259 |
+ name: "only one default image change trigger", |
|
260 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
261 |
+ { |
|
262 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
263 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
264 |
+ }, |
|
265 |
+ { |
|
266 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
267 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
268 |
+ From: &kapi.ObjectReference{ |
|
269 |
+ Kind: "ImageStreamTag", |
|
270 |
+ Name: "myimage:tag", |
|
271 |
+ }, |
|
225 | 272 |
}, |
226 |
- ContextDir: "context", |
|
227 | 273 |
}, |
228 |
- Strategy: buildapi.BuildStrategy{ |
|
229 |
- Type: buildapi.DockerBuildStrategyType, |
|
230 |
- DockerStrategy: &buildapi.DockerBuildStrategy{}, |
|
274 |
+ }, |
|
275 |
+ expectError: false, |
|
276 |
+ }, |
|
277 |
+ { |
|
278 |
+ name: "invalid reference kind for trigger", |
|
279 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
280 |
+ { |
|
281 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
282 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
231 | 283 |
}, |
232 |
- Output: buildapi.BuildOutput{ |
|
233 |
- To: &kapi.ObjectReference{ |
|
234 |
- Kind: "DockerImage", |
|
235 |
- Name: "repository/data", |
|
284 |
+ { |
|
285 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
286 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
287 |
+ From: &kapi.ObjectReference{ |
|
288 |
+ Kind: "DockerImage", |
|
289 |
+ Name: "myimage:tag", |
|
290 |
+ }, |
|
236 | 291 |
}, |
237 | 292 |
}, |
238 | 293 |
}, |
239 |
- Triggers: []buildapi.BuildTriggerPolicy{ |
|
294 |
+ expectError: true, |
|
295 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
296 |
+ }, |
|
297 |
+ { |
|
298 |
+ name: "empty reference kind for trigger", |
|
299 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
240 | 300 |
{ |
241 | 301 |
Type: buildapi.ImageChangeBuildTriggerType, |
242 | 302 |
ImageChange: &buildapi.ImageChangeTrigger{}, |
243 | 303 |
}, |
244 | 304 |
{ |
305 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
306 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
307 |
+ From: &kapi.ObjectReference{ |
|
308 |
+ Name: "myimage:tag", |
|
309 |
+ }, |
|
310 |
+ }, |
|
311 |
+ }, |
|
312 |
+ }, |
|
313 |
+ expectError: true, |
|
314 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
315 |
+ }, |
|
316 |
+ { |
|
317 |
+ name: "duplicate imagestreamtag references", |
|
318 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
319 |
+ { |
|
320 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
321 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
322 |
+ From: &kapi.ObjectReference{ |
|
323 |
+ Kind: "ImageStreamTag", |
|
324 |
+ Name: "myimage:tag", |
|
325 |
+ }, |
|
326 |
+ }, |
|
327 |
+ }, |
|
328 |
+ { |
|
329 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
330 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
331 |
+ From: &kapi.ObjectReference{ |
|
332 |
+ Kind: "ImageStreamTag", |
|
333 |
+ Name: "myimage:tag", |
|
334 |
+ }, |
|
335 |
+ }, |
|
336 |
+ }, |
|
337 |
+ }, |
|
338 |
+ expectError: true, |
|
339 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
340 |
+ }, |
|
341 |
+ { |
|
342 |
+ name: "duplicate imagestreamtag - same as strategy ref", |
|
343 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
344 |
+ { |
|
245 | 345 |
Type: buildapi.ImageChangeBuildTriggerType, |
246 | 346 |
ImageChange: &buildapi.ImageChangeTrigger{}, |
247 | 347 |
}, |
348 |
+ { |
|
349 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
350 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
351 |
+ From: &kapi.ObjectReference{ |
|
352 |
+ Kind: "ImageStreamTag", |
|
353 |
+ Name: "builderimage:latest", |
|
354 |
+ }, |
|
355 |
+ }, |
|
356 |
+ }, |
|
248 | 357 |
}, |
358 |
+ expectError: true, |
|
359 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
360 |
+ }, |
|
361 |
+ { |
|
362 |
+ name: "imagestreamtag references with same name, different ns", |
|
363 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
364 |
+ { |
|
365 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
366 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
367 |
+ From: &kapi.ObjectReference{ |
|
368 |
+ Kind: "ImageStreamTag", |
|
369 |
+ Name: "myimage:tag", |
|
370 |
+ Namespace: "ns1", |
|
371 |
+ }, |
|
372 |
+ }, |
|
373 |
+ }, |
|
374 |
+ { |
|
375 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
376 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
377 |
+ From: &kapi.ObjectReference{ |
|
378 |
+ Kind: "ImageStreamTag", |
|
379 |
+ Name: "myimage:tag", |
|
380 |
+ Namespace: "ns2", |
|
381 |
+ }, |
|
382 |
+ }, |
|
383 |
+ }, |
|
384 |
+ }, |
|
385 |
+ expectError: false, |
|
386 |
+ }, |
|
387 |
+ { |
|
388 |
+ name: "imagestreamtag references with same name, same ns", |
|
389 |
+ triggers: []buildapi.BuildTriggerPolicy{ |
|
390 |
+ { |
|
391 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
392 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
393 |
+ From: &kapi.ObjectReference{ |
|
394 |
+ Kind: "ImageStreamTag", |
|
395 |
+ Name: "myimage:tag", |
|
396 |
+ Namespace: "ns", |
|
397 |
+ }, |
|
398 |
+ }, |
|
399 |
+ }, |
|
400 |
+ { |
|
401 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
402 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
403 |
+ From: &kapi.ObjectReference{ |
|
404 |
+ Kind: "ImageStreamTag", |
|
405 |
+ Name: "myimage:tag", |
|
406 |
+ Namespace: "ns", |
|
407 |
+ }, |
|
408 |
+ }, |
|
409 |
+ }, |
|
410 |
+ }, |
|
411 |
+ expectError: true, |
|
412 |
+ errorType: fielderrors.ValidationErrorTypeInvalid, |
|
249 | 413 |
}, |
250 | 414 |
} |
251 |
- errors := ValidateBuildConfig(buildConfig) |
|
252 |
- if len(errors) != 1 { |
|
253 |
- t.Fatalf("Unexpected validation errors %v", errors) |
|
254 |
- } |
|
255 |
- err := errors[0].(*fielderrors.ValidationError) |
|
256 |
- if err.Type != fielderrors.ValidationErrorTypeInvalid { |
|
257 |
- t.Errorf("Unexpected error type, expected %s, got %s", fielderrors.ValidationErrorTypeInvalid, err.Type) |
|
258 |
- } |
|
259 |
- if err.Field != "triggers" { |
|
260 |
- t.Errorf("Unexpected field name expected triggers, got %s", err.Field) |
|
261 |
- } |
|
262 |
- if !strings.Contains(err.Detail, "only one ImageChange trigger is allowed") { |
|
263 |
- t.Errorf("Unexpected error details: %s", err.Detail) |
|
415 |
+ |
|
416 |
+ for _, tc := range tests { |
|
417 |
+ buildConfig := &buildapi.BuildConfig{ |
|
418 |
+ ObjectMeta: kapi.ObjectMeta{Name: "bar", Namespace: "foo"}, |
|
419 |
+ Spec: buildapi.BuildConfigSpec{ |
|
420 |
+ BuildSpec: buildapi.BuildSpec{ |
|
421 |
+ Source: buildapi.BuildSource{ |
|
422 |
+ Type: buildapi.BuildSourceGit, |
|
423 |
+ Git: &buildapi.GitBuildSource{ |
|
424 |
+ URI: "http://github.com/my/repository", |
|
425 |
+ }, |
|
426 |
+ ContextDir: "context", |
|
427 |
+ }, |
|
428 |
+ Strategy: buildapi.BuildStrategy{ |
|
429 |
+ Type: buildapi.SourceBuildStrategyType, |
|
430 |
+ SourceStrategy: &buildapi.SourceBuildStrategy{ |
|
431 |
+ From: kapi.ObjectReference{ |
|
432 |
+ Kind: "ImageStreamTag", |
|
433 |
+ Name: "builderimage:latest", |
|
434 |
+ }, |
|
435 |
+ }, |
|
436 |
+ }, |
|
437 |
+ Output: buildapi.BuildOutput{ |
|
438 |
+ To: &kapi.ObjectReference{ |
|
439 |
+ Kind: "DockerImage", |
|
440 |
+ Name: "repository/data", |
|
441 |
+ }, |
|
442 |
+ }, |
|
443 |
+ }, |
|
444 |
+ Triggers: tc.triggers, |
|
445 |
+ }, |
|
446 |
+ } |
|
447 |
+ errors := ValidateBuildConfig(buildConfig) |
|
448 |
+ // Check whether an error was returned |
|
449 |
+ if hasError := len(errors) > 0; hasError != tc.expectError { |
|
450 |
+ t.Errorf("%s: did not get expected result: %#v", tc.name, errors) |
|
451 |
+ } |
|
452 |
+ // Check whether it's the expected error type |
|
453 |
+ if len(errors) > 0 && tc.expectError && tc.errorType != "" { |
|
454 |
+ verr, ok := errors[0].(*fielderrors.ValidationError) |
|
455 |
+ if !ok { |
|
456 |
+ t.Errorf("%s: unexpected error: %#v. Expected ValidationError of type: %s", tc.name, errors[0], verr.Type) |
|
457 |
+ continue |
|
458 |
+ } |
|
459 |
+ if verr.Type != tc.errorType { |
|
460 |
+ t.Errorf("%s: unexpected error type. Expected: %s. Got: %s", tc.name, tc.errorType, verr.Type) |
|
461 |
+ } |
|
462 |
+ } |
|
264 | 463 |
} |
265 | 464 |
} |
266 | 465 |
|
... | ... |
@@ -59,21 +59,32 @@ func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) erro |
59 | 59 |
for _, bc := range c.BuildConfigStore.List() { |
60 | 60 |
config := bc.(*buildapi.BuildConfig) |
61 | 61 |
|
62 |
- from := buildutil.GetImageStreamForStrategy(config.Spec.Strategy) |
|
63 |
- if from == nil || from.Kind != "ImageStreamTag" { |
|
64 |
- continue |
|
65 |
- } |
|
66 |
- |
|
67 |
- shouldBuild := false |
|
68 |
- triggeredImage := "" |
|
69 |
- // For every ImageChange trigger find the latest tagged image from the image repository and replace that value |
|
70 |
- // throughout the build strategies. A new build is triggered only if the latest tagged image id or pull spec |
|
71 |
- // differs from the last triggered build recorded on the build config. |
|
62 |
+ var ( |
|
63 |
+ from *kapi.ObjectReference |
|
64 |
+ shouldBuild = false |
|
65 |
+ triggeredImage = "" |
|
66 |
+ ) |
|
67 |
+ // For every ImageChange trigger find the latest tagged image from the image repository and |
|
68 |
+ // invoke a build using that image id. A new build is triggered only if the latest tagged image id or pull spec |
|
69 |
+ // differs from the last triggered build recorded on the build config for that trigger |
|
72 | 70 |
for _, trigger := range config.Spec.Triggers { |
73 | 71 |
if trigger.Type != buildapi.ImageChangeBuildTriggerType { |
74 | 72 |
continue |
75 | 73 |
} |
76 |
- fromStreamName := getImageStreamNameFromReference(from) |
|
74 |
+ if trigger.ImageChange.From != nil { |
|
75 |
+ from = trigger.ImageChange.From |
|
76 |
+ } else { |
|
77 |
+ from = buildutil.GetImageStreamForStrategy(config.Spec.Strategy) |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ if from == nil || from.Kind != "ImageStreamTag" { |
|
81 |
+ continue |
|
82 |
+ } |
|
83 |
+ fromStreamName, tag, ok := imageapi.SplitImageStreamTag(from.Name) |
|
84 |
+ if !ok { |
|
85 |
+ glog.Errorf("Invalid image stream tag: %s in build config %s/%s", from.Name, config.Name, config.Namespace) |
|
86 |
+ continue |
|
87 |
+ } |
|
77 | 88 |
|
78 | 89 |
fromNamespace := from.Namespace |
79 | 90 |
if len(fromNamespace) == 0 { |
... | ... |
@@ -89,7 +100,6 @@ func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) erro |
89 | 89 |
|
90 | 90 |
// This split is safe because ImageStreamTag names always have the form |
91 | 91 |
// name:tag. |
92 |
- tag := strings.Split(from.Name, ":")[1] |
|
93 | 92 |
latest := imageapi.LatestTaggedImage(repo, tag) |
94 | 93 |
if latest == nil { |
95 | 94 |
glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", repo.Namespace, repo.Name, tag) |
... | ... |
@@ -122,6 +132,7 @@ func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) erro |
122 | 122 |
Kind: "DockerImage", |
123 | 123 |
Name: triggeredImage, |
124 | 124 |
}, |
125 |
+ From: from, |
|
125 | 126 |
} |
126 | 127 |
if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil { |
127 | 128 |
if kerrors.IsConflict(err) { |
... | ... |
@@ -13,6 +13,7 @@ import ( |
13 | 13 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/credentialprovider" |
14 | 14 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
15 | 15 |
buildapi "github.com/openshift/origin/pkg/build/api" |
16 |
+ buildutil "github.com/openshift/origin/pkg/build/util" |
|
16 | 17 |
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" |
17 | 18 |
imageapi "github.com/openshift/origin/pkg/image/api" |
18 | 19 |
) |
... | ... |
@@ -98,7 +99,7 @@ func (g *BuildGenerator) FetchServiceAccountSecrets(namespace, serviceAccount st |
98 | 98 |
var result []kapi.Secret |
99 | 99 |
sa, err := g.ServiceAccounts.ServiceAccounts(namespace).Get(serviceAccount) |
100 | 100 |
if err != nil { |
101 |
- return result, fmt.Errorf("Error getting push/pull secrets for service account %q: %v", namespace, serviceAccount, err) |
|
101 |
+ return result, fmt.Errorf("Error getting push/pull secrets for service account %s/%s: %v", namespace, serviceAccount, err) |
|
102 | 102 |
} |
103 | 103 |
for _, ref := range sa.Secrets { |
104 | 104 |
secret, err := g.Secrets.Secrets(namespace).Get(ref.Name) |
... | ... |
@@ -110,25 +111,62 @@ func (g *BuildGenerator) FetchServiceAccountSecrets(namespace, serviceAccount st |
110 | 110 |
return result, nil |
111 | 111 |
} |
112 | 112 |
|
113 |
+// findImageChangeTrigger finds an image change trigger that has a from that matches the passed in ref |
|
114 |
+// if no match is found but there is an image change trigger with a null from, that trigger is returned |
|
115 |
+func findImageChangeTrigger(bc *buildapi.BuildConfig, ref *kapi.ObjectReference) *buildapi.ImageChangeTrigger { |
|
116 |
+ if ref == nil { |
|
117 |
+ return nil |
|
118 |
+ } |
|
119 |
+ for _, trigger := range bc.Spec.Triggers { |
|
120 |
+ if trigger.Type != buildapi.ImageChangeBuildTriggerType { |
|
121 |
+ continue |
|
122 |
+ } |
|
123 |
+ imageChange := trigger.ImageChange |
|
124 |
+ triggerRef := imageChange.From |
|
125 |
+ if triggerRef == nil { |
|
126 |
+ triggerRef = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) |
|
127 |
+ if triggerRef == nil || triggerRef.Kind != "ImageStreamTag" { |
|
128 |
+ continue |
|
129 |
+ } |
|
130 |
+ } |
|
131 |
+ triggerNs := triggerRef.Namespace |
|
132 |
+ if triggerNs == "" { |
|
133 |
+ triggerNs = bc.Namespace |
|
134 |
+ } |
|
135 |
+ refNs := ref.Namespace |
|
136 |
+ if refNs == "" { |
|
137 |
+ refNs = bc.Namespace |
|
138 |
+ } |
|
139 |
+ if triggerRef.Name == ref.Name && triggerNs == refNs { |
|
140 |
+ return imageChange |
|
141 |
+ } |
|
142 |
+ } |
|
143 |
+ return nil |
|
144 |
+} |
|
145 |
+ |
|
146 |
+func describeBuildRequest(request *buildapi.BuildRequest) string { |
|
147 |
+ desc := fmt.Sprintf("BuildConfig: %s/%s", request.Namespace, request.Name) |
|
148 |
+ if request.Revision != nil { |
|
149 |
+ desc += fmt.Sprintf(", Revision: %#v", request.Revision.Git) |
|
150 |
+ } |
|
151 |
+ if request.TriggeredByImage != nil { |
|
152 |
+ desc += fmt.Sprintf(", TriggeredBy: %s/%s with stream: %s/%s", |
|
153 |
+ request.TriggeredByImage.Kind, request.TriggeredByImage.Name, |
|
154 |
+ request.From.Kind, request.From.Name) |
|
155 |
+ } |
|
156 |
+ return desc |
|
157 |
+} |
|
158 |
+ |
|
113 | 159 |
// Instantiate returns new Build object based on a BuildRequest object |
114 | 160 |
func (g *BuildGenerator) Instantiate(ctx kapi.Context, request *buildapi.BuildRequest) (*buildapi.Build, error) { |
115 |
- glog.V(4).Infof("Generating Build from BuildConfig %s/%s", request.Namespace, request.Name) |
|
161 |
+ glog.V(4).Infof("Generating Build from %s", describeBuildRequest(request)) |
|
116 | 162 |
bc, err := g.Client.GetBuildConfig(ctx, request.Name) |
117 | 163 |
if err != nil { |
118 | 164 |
return nil, err |
119 | 165 |
} |
120 | 166 |
|
121 |
- if request.TriggeredByImage != nil { |
|
122 |
- for _, trigger := range bc.Spec.Triggers { |
|
123 |
- if trigger.Type != buildapi.ImageChangeBuildTriggerType { |
|
124 |
- continue |
|
125 |
- } |
|
126 |
- if trigger.ImageChange.LastTriggeredImageID == request.TriggeredByImage.Name { |
|
127 |
- glog.V(2).Infof("Aborting imageid triggered build for BuildConfig %s/%s with imageid %s because the BuildConfig already matches this imageid", bc.Namespace, bc.Name, request.TriggeredByImage) |
|
128 |
- return nil, fmt.Errorf("Build config %s/%s has already instantiated a build for imageid %s", bc.Namespace, bc.Name, request.TriggeredByImage.Name) |
|
129 |
- } |
|
130 |
- trigger.ImageChange.LastTriggeredImageID = request.TriggeredByImage.Name |
|
131 |
- } |
|
167 |
+ if err := g.updateImageTriggers(ctx, bc, request.From, request.TriggeredByImage); err != nil { |
|
168 |
+ return nil, err |
|
132 | 169 |
} |
133 | 170 |
|
134 | 171 |
newBuild, err := g.generateBuildFromConfig(ctx, bc, request.Revision) |
... | ... |
@@ -150,6 +188,46 @@ func (g *BuildGenerator) Instantiate(ctx kapi.Context, request *buildapi.BuildRe |
150 | 150 |
return g.createBuild(ctx, newBuild) |
151 | 151 |
} |
152 | 152 |
|
153 |
+// updateImageTriggers sets the LastTriggeredImageID on all the ImageChangeTriggers on the BuildConfig and |
|
154 |
+// updates the From reference of the strategy if the strategy uses an ImageStream or ImageStreamTag reference |
|
155 |
+func (g *BuildGenerator) updateImageTriggers(ctx kapi.Context, bc *buildapi.BuildConfig, from, triggeredBy *kapi.ObjectReference) error { |
|
156 |
+ var requestTrigger *buildapi.ImageChangeTrigger |
|
157 |
+ if from != nil { |
|
158 |
+ requestTrigger = findImageChangeTrigger(bc, from) |
|
159 |
+ } |
|
160 |
+ if requestTrigger != nil && requestTrigger.LastTriggeredImageID == triggeredBy.Name { |
|
161 |
+ glog.V(2).Infof("Aborting imageid triggered build for BuildConfig %s/%s with imageid %s because the BuildConfig already matches this imageid", bc.Namespace, bc.Name, triggeredBy.Name) |
|
162 |
+ return fmt.Errorf("build config %s/%s has already instantiated a build for imageid %s", bc.Namespace, bc.Name, triggeredBy.Name) |
|
163 |
+ } |
|
164 |
+ // Update last triggered image id for all image change triggers |
|
165 |
+ for _, trigger := range bc.Spec.Triggers { |
|
166 |
+ if trigger.Type != buildapi.ImageChangeBuildTriggerType { |
|
167 |
+ continue |
|
168 |
+ } |
|
169 |
+ // Use the requested image id for the trigger that caused the build, otherwise resolve to the latest |
|
170 |
+ if trigger.ImageChange == requestTrigger { |
|
171 |
+ trigger.ImageChange.LastTriggeredImageID = triggeredBy.Name |
|
172 |
+ continue |
|
173 |
+ } |
|
174 |
+ |
|
175 |
+ triggerImageRef := trigger.ImageChange.From |
|
176 |
+ if triggerImageRef == nil { |
|
177 |
+ triggerImageRef = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) |
|
178 |
+ } |
|
179 |
+ image, err := g.resolveImageStreamReference(ctx, *triggerImageRef, bc.Namespace) |
|
180 |
+ if err != nil { |
|
181 |
+ // If the trigger is for the strategy from ref, return an error |
|
182 |
+ if trigger.ImageChange.From == nil { |
|
183 |
+ return err |
|
184 |
+ } |
|
185 |
+ // Otherwise, warn that an error occurred, but continue |
|
186 |
+ glog.Warningf("Could not resolve trigger reference for build config %s/%s: %#v", bc.Namespace, bc.Name, triggerImageRef) |
|
187 |
+ } |
|
188 |
+ trigger.ImageChange.LastTriggeredImageID = image |
|
189 |
+ } |
|
190 |
+ return nil |
|
191 |
+} |
|
192 |
+ |
|
153 | 193 |
// Clone returns clone of a Build |
154 | 194 |
func (g *BuildGenerator) Clone(ctx kapi.Context, request *buildapi.BuildRequest) (*buildapi.Build, error) { |
155 | 195 |
glog.V(4).Infof("Generating build from build %s/%s", request.Namespace, request.Name) |
... | ... |
@@ -227,15 +305,22 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi. |
227 | 227 |
if build.Spec.Output.PushSecret == nil { |
228 | 228 |
build.Spec.Output.PushSecret = g.resolveImageSecret(ctx, builderSecrets, build.Spec.Output.To, bc.Namespace) |
229 | 229 |
} |
230 |
+ strategyImageChangeTrigger := getStrategyImageChangeTrigger(bc) |
|
230 | 231 |
|
231 | 232 |
// If the Build is using a From reference instead of a resolved image, we need to resolve that From |
232 | 233 |
// reference to a valid image so we can run the build. Builds do not consume ImageStream references, |
233 | 234 |
// only image specs. |
235 |
+ var image string |
|
236 |
+ if strategyImageChangeTrigger != nil { |
|
237 |
+ image = strategyImageChangeTrigger.LastTriggeredImageID |
|
238 |
+ } |
|
234 | 239 |
switch { |
235 | 240 |
case build.Spec.Strategy.Type == buildapi.SourceBuildStrategyType: |
236 |
- image, err := g.resolveImageStreamReference(ctx, build.Spec.Strategy.SourceStrategy.From, build.Status.Config.Namespace) |
|
237 |
- if err != nil { |
|
238 |
- return nil, err |
|
241 |
+ if image == "" { |
|
242 |
+ image, err = g.resolveImageStreamReference(ctx, build.Spec.Strategy.SourceStrategy.From, build.Status.Config.Namespace) |
|
243 |
+ if err != nil { |
|
244 |
+ return nil, err |
|
245 |
+ } |
|
239 | 246 |
} |
240 | 247 |
build.Spec.Strategy.SourceStrategy.From = kapi.ObjectReference{ |
241 | 248 |
Kind: "DockerImage", |
... | ... |
@@ -246,9 +331,11 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi. |
246 | 246 |
} |
247 | 247 |
case build.Spec.Strategy.Type == buildapi.DockerBuildStrategyType && |
248 | 248 |
build.Spec.Strategy.DockerStrategy.From != nil: |
249 |
- image, err := g.resolveImageStreamReference(ctx, *build.Spec.Strategy.DockerStrategy.From, build.Status.Config.Namespace) |
|
250 |
- if err != nil { |
|
251 |
- return nil, err |
|
249 |
+ if image == "" { |
|
250 |
+ image, err = g.resolveImageStreamReference(ctx, *build.Spec.Strategy.DockerStrategy.From, build.Status.Config.Namespace) |
|
251 |
+ if err != nil { |
|
252 |
+ return nil, err |
|
253 |
+ } |
|
252 | 254 |
} |
253 | 255 |
build.Spec.Strategy.DockerStrategy.From = &kapi.ObjectReference{ |
254 | 256 |
Kind: "DockerImage", |
... | ... |
@@ -258,9 +345,11 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi. |
258 | 258 |
build.Spec.Strategy.DockerStrategy.PullSecret = g.resolveImageSecret(ctx, builderSecrets, build.Spec.Strategy.DockerStrategy.From, bc.Namespace) |
259 | 259 |
} |
260 | 260 |
case build.Spec.Strategy.Type == buildapi.CustomBuildStrategyType: |
261 |
- image, err := g.resolveImageStreamReference(ctx, build.Spec.Strategy.CustomStrategy.From, build.Status.Config.Namespace) |
|
262 |
- if err != nil { |
|
263 |
- return nil, err |
|
261 |
+ if image == "" { |
|
262 |
+ image, err = g.resolveImageStreamReference(ctx, build.Spec.Strategy.CustomStrategy.From, build.Status.Config.Namespace) |
|
263 |
+ if err != nil { |
|
264 |
+ return nil, err |
|
265 |
+ } |
|
264 | 266 |
} |
265 | 267 |
build.Spec.Strategy.CustomStrategy.From = kapi.ObjectReference{ |
266 | 268 |
Kind: "DockerImage", |
... | ... |
@@ -445,3 +534,13 @@ func getNextBuildNameFromBuild(build *buildapi.Build) string { |
445 | 445 |
} |
446 | 446 |
return fmt.Sprintf("%s-%d", buildName, int32(util.Now().Unix())) |
447 | 447 |
} |
448 |
+ |
|
449 |
+// getStrategyImageChangeTrigger returns the ImageChangeTrigger that corresponds to the BuildConfig's strategy |
|
450 |
+func getStrategyImageChangeTrigger(bc *buildapi.BuildConfig) *buildapi.ImageChangeTrigger { |
|
451 |
+ for _, trigger := range bc.Spec.Triggers { |
|
452 |
+ if trigger.Type == buildapi.ImageChangeBuildTriggerType && trigger.ImageChange.From == nil { |
|
453 |
+ return trigger.ImageChange |
|
454 |
+ } |
|
455 |
+ } |
|
456 |
+ return nil |
|
457 |
+} |
... | ... |
@@ -15,6 +15,7 @@ import ( |
15 | 15 |
|
16 | 16 |
buildapi "github.com/openshift/origin/pkg/build/api" |
17 | 17 |
mocks "github.com/openshift/origin/pkg/build/generator/test" |
18 |
+ buildutil "github.com/openshift/origin/pkg/build/util" |
|
18 | 19 |
imageapi "github.com/openshift/origin/pkg/image/api" |
19 | 20 |
) |
20 | 21 |
|
... | ... |
@@ -122,6 +123,295 @@ func TestInstantiateGenerateBuildError(t *testing.T) { |
122 | 122 |
} |
123 | 123 |
} |
124 | 124 |
|
125 |
+func TestInstantiateWithImageTrigger(t *testing.T) { |
|
126 |
+ imageID := "the-image-id-12345" |
|
127 |
+ defaultTriggers := func() []buildapi.BuildTriggerPolicy { |
|
128 |
+ return []buildapi.BuildTriggerPolicy{ |
|
129 |
+ { |
|
130 |
+ Type: buildapi.GenericWebHookBuildTriggerType, |
|
131 |
+ }, |
|
132 |
+ { |
|
133 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
134 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
135 |
+ }, |
|
136 |
+ { |
|
137 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
138 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
139 |
+ From: &kapi.ObjectReference{ |
|
140 |
+ Name: "image1:tag1", |
|
141 |
+ Kind: "ImageStreamTag", |
|
142 |
+ }, |
|
143 |
+ }, |
|
144 |
+ }, |
|
145 |
+ { |
|
146 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
147 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
148 |
+ From: &kapi.ObjectReference{ |
|
149 |
+ Name: "image2:tag2", |
|
150 |
+ Namespace: "image2ns", |
|
151 |
+ Kind: "ImageStreamTag", |
|
152 |
+ }, |
|
153 |
+ }, |
|
154 |
+ }, |
|
155 |
+ } |
|
156 |
+ } |
|
157 |
+ triggersWithImageID := func() []buildapi.BuildTriggerPolicy { |
|
158 |
+ triggers := defaultTriggers() |
|
159 |
+ triggers[2].ImageChange.LastTriggeredImageID = imageID |
|
160 |
+ return triggers |
|
161 |
+ } |
|
162 |
+ tests := []struct { |
|
163 |
+ name string |
|
164 |
+ reqFrom *kapi.ObjectReference |
|
165 |
+ triggerIndex int // index of trigger that will be updated with the image id, if -1, no update expected |
|
166 |
+ triggers []buildapi.BuildTriggerPolicy |
|
167 |
+ errorExpected bool |
|
168 |
+ }{ |
|
169 |
+ { |
|
170 |
+ name: "default trigger", |
|
171 |
+ reqFrom: &kapi.ObjectReference{ |
|
172 |
+ Kind: "ImageStreamTag", |
|
173 |
+ Name: "image3:tag3", |
|
174 |
+ }, |
|
175 |
+ triggerIndex: 1, |
|
176 |
+ triggers: defaultTriggers(), |
|
177 |
+ }, |
|
178 |
+ { |
|
179 |
+ name: "trigger with from", |
|
180 |
+ reqFrom: &kapi.ObjectReference{ |
|
181 |
+ Kind: "ImageStreamTag", |
|
182 |
+ Name: "image1:tag1", |
|
183 |
+ }, |
|
184 |
+ triggerIndex: 2, |
|
185 |
+ triggers: defaultTriggers(), |
|
186 |
+ }, |
|
187 |
+ { |
|
188 |
+ name: "trigger with from and namespace", |
|
189 |
+ reqFrom: &kapi.ObjectReference{ |
|
190 |
+ Kind: "ImageStreamTag", |
|
191 |
+ Name: "image2:tag2", |
|
192 |
+ Namespace: "image2ns", |
|
193 |
+ }, |
|
194 |
+ triggerIndex: 3, |
|
195 |
+ triggers: defaultTriggers(), |
|
196 |
+ }, |
|
197 |
+ { |
|
198 |
+ name: "existing image id", |
|
199 |
+ reqFrom: &kapi.ObjectReference{ |
|
200 |
+ Kind: "ImageStreamTag", |
|
201 |
+ Name: "image1:tag1", |
|
202 |
+ }, |
|
203 |
+ triggers: triggersWithImageID(), |
|
204 |
+ errorExpected: true, |
|
205 |
+ }, |
|
206 |
+ } |
|
207 |
+ |
|
208 |
+ for _, tc := range tests { |
|
209 |
+ bc := &buildapi.BuildConfig{ |
|
210 |
+ Spec: buildapi.BuildConfigSpec{ |
|
211 |
+ BuildSpec: buildapi.BuildSpec{ |
|
212 |
+ Strategy: buildapi.BuildStrategy{ |
|
213 |
+ Type: buildapi.SourceBuildStrategyType, |
|
214 |
+ SourceStrategy: &buildapi.SourceBuildStrategy{ |
|
215 |
+ From: kapi.ObjectReference{ |
|
216 |
+ Name: "image3:tag3", |
|
217 |
+ Kind: "ImageStreamTag", |
|
218 |
+ }, |
|
219 |
+ }, |
|
220 |
+ }, |
|
221 |
+ }, |
|
222 |
+ Triggers: tc.triggers, |
|
223 |
+ }, |
|
224 |
+ } |
|
225 |
+ generator := mockBuildGeneratorForInstantiate() |
|
226 |
+ client := generator.Client.(Client) |
|
227 |
+ client.GetBuildConfigFunc = |
|
228 |
+ func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) { |
|
229 |
+ return bc, nil |
|
230 |
+ } |
|
231 |
+ client.UpdateBuildConfigFunc = |
|
232 |
+ func(ctx kapi.Context, buildConfig *buildapi.BuildConfig) error { |
|
233 |
+ bc = buildConfig |
|
234 |
+ return nil |
|
235 |
+ } |
|
236 |
+ generator.Client = client |
|
237 |
+ |
|
238 |
+ req := &buildapi.BuildRequest{ |
|
239 |
+ TriggeredByImage: &kapi.ObjectReference{ |
|
240 |
+ Kind: "DockerImage", |
|
241 |
+ Name: imageID, |
|
242 |
+ }, |
|
243 |
+ From: tc.reqFrom, |
|
244 |
+ } |
|
245 |
+ _, err := generator.Instantiate(kapi.NewDefaultContext(), req) |
|
246 |
+ if err != nil && !tc.errorExpected { |
|
247 |
+ t.Errorf("%s: unexpected error %v", tc.name, err) |
|
248 |
+ continue |
|
249 |
+ } |
|
250 |
+ if err == nil && tc.errorExpected { |
|
251 |
+ t.Errorf("%s: expected error but didn't get one", tc.name) |
|
252 |
+ continue |
|
253 |
+ } |
|
254 |
+ if tc.errorExpected { |
|
255 |
+ continue |
|
256 |
+ } |
|
257 |
+ for i := range bc.Spec.Triggers { |
|
258 |
+ if i == tc.triggerIndex { |
|
259 |
+ // Verify that the trigger got updated |
|
260 |
+ if bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID != imageID { |
|
261 |
+ t.Errorf("%s: expeccted trigger at index %d to contain imageID %s", tc.name, i, imageID) |
|
262 |
+ } |
|
263 |
+ continue |
|
264 |
+ } |
|
265 |
+ // Ensure that other triggers are updated with the latest docker image ref |
|
266 |
+ if bc.Spec.Triggers[i].Type == buildapi.ImageChangeBuildTriggerType { |
|
267 |
+ from := bc.Spec.Triggers[i].ImageChange.From |
|
268 |
+ if from == nil { |
|
269 |
+ from = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) |
|
270 |
+ } |
|
271 |
+ if bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID != ("ref@" + from.Name) { |
|
272 |
+ t.Errorf("%s: expected LastTriggeredImageID for trigger at %d to be %s. Got: %s", tc.name, i, "ref@"+from.Name, bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID) |
|
273 |
+ } |
|
274 |
+ } |
|
275 |
+ } |
|
276 |
+ } |
|
277 |
+} |
|
278 |
+ |
|
279 |
+func TestFindImageTrigger(t *testing.T) { |
|
280 |
+ defaultTrigger := &buildapi.ImageChangeTrigger{} |
|
281 |
+ image1Trigger := &buildapi.ImageChangeTrigger{ |
|
282 |
+ From: &kapi.ObjectReference{ |
|
283 |
+ Name: "image1:tag1", |
|
284 |
+ }, |
|
285 |
+ } |
|
286 |
+ image2Trigger := &buildapi.ImageChangeTrigger{ |
|
287 |
+ From: &kapi.ObjectReference{ |
|
288 |
+ Name: "image2:tag2", |
|
289 |
+ Namespace: "image2ns", |
|
290 |
+ }, |
|
291 |
+ } |
|
292 |
+ image4Trigger := &buildapi.ImageChangeTrigger{ |
|
293 |
+ From: &kapi.ObjectReference{ |
|
294 |
+ Name: "image4:tag4", |
|
295 |
+ }, |
|
296 |
+ } |
|
297 |
+ image5Trigger := &buildapi.ImageChangeTrigger{ |
|
298 |
+ From: &kapi.ObjectReference{ |
|
299 |
+ Name: "image5:tag5", |
|
300 |
+ Namespace: "bcnamespace", |
|
301 |
+ }, |
|
302 |
+ } |
|
303 |
+ bc := &buildapi.BuildConfig{ |
|
304 |
+ ObjectMeta: kapi.ObjectMeta{ |
|
305 |
+ Name: "testbc", |
|
306 |
+ Namespace: "bcnamespace", |
|
307 |
+ }, |
|
308 |
+ Spec: buildapi.BuildConfigSpec{ |
|
309 |
+ BuildSpec: buildapi.BuildSpec{ |
|
310 |
+ Strategy: buildapi.BuildStrategy{ |
|
311 |
+ Type: buildapi.SourceBuildStrategyType, |
|
312 |
+ SourceStrategy: &buildapi.SourceBuildStrategy{ |
|
313 |
+ From: kapi.ObjectReference{ |
|
314 |
+ Name: "image3:tag3", |
|
315 |
+ Kind: "ImageStreamTag", |
|
316 |
+ }, |
|
317 |
+ }, |
|
318 |
+ }, |
|
319 |
+ }, |
|
320 |
+ Triggers: []buildapi.BuildTriggerPolicy{ |
|
321 |
+ { |
|
322 |
+ Type: buildapi.GenericWebHookBuildTriggerType, |
|
323 |
+ }, |
|
324 |
+ { |
|
325 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
326 |
+ ImageChange: defaultTrigger, |
|
327 |
+ }, |
|
328 |
+ { |
|
329 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
330 |
+ ImageChange: image1Trigger, |
|
331 |
+ }, |
|
332 |
+ { |
|
333 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
334 |
+ ImageChange: image2Trigger, |
|
335 |
+ }, |
|
336 |
+ { |
|
337 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
338 |
+ ImageChange: image4Trigger, |
|
339 |
+ }, |
|
340 |
+ { |
|
341 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
342 |
+ ImageChange: image5Trigger, |
|
343 |
+ }, |
|
344 |
+ }, |
|
345 |
+ }, |
|
346 |
+ } |
|
347 |
+ |
|
348 |
+ tests := []struct { |
|
349 |
+ name string |
|
350 |
+ input *kapi.ObjectReference |
|
351 |
+ expect *buildapi.ImageChangeTrigger |
|
352 |
+ }{ |
|
353 |
+ { |
|
354 |
+ name: "nil reference", |
|
355 |
+ input: nil, |
|
356 |
+ expect: nil, |
|
357 |
+ }, |
|
358 |
+ { |
|
359 |
+ name: "match name", |
|
360 |
+ input: &kapi.ObjectReference{ |
|
361 |
+ Name: "image1:tag1", |
|
362 |
+ }, |
|
363 |
+ expect: image1Trigger, |
|
364 |
+ }, |
|
365 |
+ { |
|
366 |
+ name: "mismatched namespace", |
|
367 |
+ input: &kapi.ObjectReference{ |
|
368 |
+ Name: "image1:tag1", |
|
369 |
+ Namespace: "otherns", |
|
370 |
+ }, |
|
371 |
+ expect: nil, |
|
372 |
+ }, |
|
373 |
+ { |
|
374 |
+ name: "match name and namespace", |
|
375 |
+ input: &kapi.ObjectReference{ |
|
376 |
+ Name: "image2:tag2", |
|
377 |
+ Namespace: "image2ns", |
|
378 |
+ }, |
|
379 |
+ expect: image2Trigger, |
|
380 |
+ }, |
|
381 |
+ { |
|
382 |
+ name: "match default trigger", |
|
383 |
+ input: &kapi.ObjectReference{ |
|
384 |
+ Name: "image3:tag3", |
|
385 |
+ }, |
|
386 |
+ expect: defaultTrigger, |
|
387 |
+ }, |
|
388 |
+ { |
|
389 |
+ name: "input includes bc namespace", |
|
390 |
+ input: &kapi.ObjectReference{ |
|
391 |
+ Name: "image4:tag4", |
|
392 |
+ Namespace: "bcnamespace", |
|
393 |
+ }, |
|
394 |
+ expect: image4Trigger, |
|
395 |
+ }, |
|
396 |
+ { |
|
397 |
+ name: "implied namespace in trigger input", |
|
398 |
+ input: &kapi.ObjectReference{ |
|
399 |
+ Name: "image5:tag5", |
|
400 |
+ }, |
|
401 |
+ expect: image5Trigger, |
|
402 |
+ }, |
|
403 |
+ } |
|
404 |
+ |
|
405 |
+ for _, tc := range tests { |
|
406 |
+ result := findImageChangeTrigger(bc, tc.input) |
|
407 |
+ if result != tc.expect { |
|
408 |
+ t.Errorf("%s: unexpected trigger for %#v: %#v", tc.name, tc.input, result) |
|
409 |
+ } |
|
410 |
+ } |
|
411 |
+ |
|
412 |
+} |
|
413 |
+ |
|
125 | 414 |
func TestClone(t *testing.T) { |
126 | 415 |
generator := BuildGenerator{Client: Client{ |
127 | 416 |
CreateBuildFunc: func(ctx kapi.Context, build *buildapi.Build) error { |
... | ... |
@@ -861,6 +1151,21 @@ func mockBuild(source buildapi.BuildSource, strategy buildapi.BuildStrategy, out |
861 | 861 |
} |
862 | 862 |
} |
863 | 863 |
|
864 |
+func mockBuildGeneratorForInstantiate() *BuildGenerator { |
|
865 |
+ g := mockBuildGenerator() |
|
866 |
+ c := g.Client.(Client) |
|
867 |
+ c.GetImageStreamTagFunc = func(ctx kapi.Context, name string) (*imageapi.ImageStreamTag, error) { |
|
868 |
+ return &imageapi.ImageStreamTag{ |
|
869 |
+ Image: imageapi.Image{ |
|
870 |
+ ObjectMeta: kapi.ObjectMeta{Name: imageRepoName + ":" + newTag}, |
|
871 |
+ DockerImageReference: "ref@" + name, |
|
872 |
+ }, |
|
873 |
+ }, nil |
|
874 |
+ } |
|
875 |
+ g.Client = c |
|
876 |
+ return g |
|
877 |
+} |
|
878 |
+ |
|
864 | 879 |
func mockBuildGenerator() *BuildGenerator { |
865 | 880 |
fakeSecrets := []runtime.Object{} |
866 | 881 |
for _, s := range mocks.MockBuilderSecrets() { |
... | ... |
@@ -1,9 +1,11 @@ |
1 | 1 |
package graph |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
4 | 5 |
"github.com/gonum/graph" |
5 | 6 |
|
6 | 7 |
osgraph "github.com/openshift/origin/pkg/api/graph" |
8 |
+ buildapi "github.com/openshift/origin/pkg/build/api" |
|
7 | 9 |
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes" |
8 | 10 |
buildutil "github.com/openshift/origin/pkg/build/util" |
9 | 11 |
imageapi "github.com/openshift/origin/pkg/image/api" |
... | ... |
@@ -11,14 +13,30 @@ import ( |
11 | 11 |
) |
12 | 12 |
|
13 | 13 |
const ( |
14 |
+ // BuildTriggerImageEdgeKind is an edge from an ImageStream to a BuildConfig that |
|
15 |
+ // represents a trigger connection. Changes to the ImageStream will trigger a new build |
|
16 |
+ // from the BuildConfig. |
|
17 |
+ BuildTriggerImageEdgeKind = "BuildTriggerImage" |
|
18 |
+ |
|
19 |
+ // BuildInputImageEdgeKind is an edge from an ImageStream to a BuildConfig, where the |
|
20 |
+ // ImageStream is the source image for the build (builder in S2I builds, FROM in Docker builds, |
|
21 |
+ // custom builder in Custom builds). The same ImageStream can also have a trigger |
|
22 |
+ // relationship with the BuildConfig, but not necessarily. |
|
14 | 23 |
BuildInputImageEdgeKind = "BuildInputImage" |
15 |
- BuildOutputEdgeKind = "BuildOutput" |
|
16 |
- BuildInputEdgeKind = "BuildInput" |
|
24 |
+ |
|
25 |
+ // BuildOutputEdgeKind is an edge from a BuildConfig to an ImageStream. The ImageStream will hold |
|
26 |
+ // the ouptut of the Builds created with that BuildConfig. |
|
27 |
+ BuildOutputEdgeKind = "BuildOutput" |
|
28 |
+ |
|
29 |
+ // BuildInputEdgeKind is an edge from a source repository to a BuildConfig. The source repository is the |
|
30 |
+ // input source for the build. |
|
31 |
+ BuildInputEdgeKind = "BuildInput" |
|
17 | 32 |
|
18 | 33 |
// BuildEdgeKind goes from a BuildConfigNode to a BuildNode and indicates that the buildConfig owns the build |
19 | 34 |
BuildEdgeKind = "Build" |
20 | 35 |
) |
21 | 36 |
|
37 |
+// AddBuildEdges adds edges that connect a BuildConfig to Builds to the given graph |
|
22 | 38 |
func AddBuildEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { |
23 | 39 |
for _, n := range g.(graph.Graph).Nodes() { |
24 | 40 |
if buildNode, ok := n.(*buildgraph.BuildNode); ok { |
... | ... |
@@ -29,6 +47,7 @@ func AddBuildEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNod |
29 | 29 |
} |
30 | 30 |
} |
31 | 31 |
|
32 |
+// AddAllBuildEdges adds build edges to all BuildConfig nodes in the given graph |
|
32 | 33 |
func AddAllBuildEdges(g osgraph.MutableUniqueGraph) { |
33 | 34 |
for _, node := range g.(graph.Graph).Nodes() { |
34 | 35 |
if bcNode, ok := node.(*buildgraph.BuildConfigNode); ok { |
... | ... |
@@ -37,48 +56,68 @@ func AddAllBuildEdges(g osgraph.MutableUniqueGraph) { |
37 | 37 |
} |
38 | 38 |
} |
39 | 39 |
|
40 |
-// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on. |
|
41 |
-func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode { |
|
42 |
- output := node.BuildConfig.Spec.Output |
|
43 |
- to := output.To |
|
44 |
- switch { |
|
45 |
- case to == nil: |
|
46 |
- case to.Kind == "DockerImage": |
|
47 |
- out := imagegraph.EnsureDockerRepositoryNode(g, to.Name, "") |
|
48 |
- g.AddEdge(node, out, BuildOutputEdgeKind) |
|
49 |
- case to.Kind == "ImageStreamTag": |
|
50 |
- out := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(to.Namespace, node.BuildConfig.Namespace), to.Name)) |
|
51 |
- g.AddEdge(node, out, BuildOutputEdgeKind) |
|
40 |
+func imageRefNode(g osgraph.MutableUniqueGraph, ref *kapi.ObjectReference, bc *buildapi.BuildConfig) graph.Node { |
|
41 |
+ if ref == nil { |
|
42 |
+ return nil |
|
52 | 43 |
} |
44 |
+ switch ref.Kind { |
|
45 |
+ case "DockerImage": |
|
46 |
+ if ref, err := imageapi.ParseDockerImageReference(ref.Name); err == nil { |
|
47 |
+ tag := ref.Tag |
|
48 |
+ ref.Tag = "" |
|
49 |
+ return imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag) |
|
50 |
+ } |
|
51 |
+ case "ImageStream": |
|
52 |
+ return imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name, imageapi.DefaultImageTag)) |
|
53 |
+ case "ImageStreamTag": |
|
54 |
+ return imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name)) |
|
55 |
+ case "ImageStreamImage": |
|
56 |
+ return imagegraph.FindOrCreateSyntheticImageStreamImageNode(g, imagegraph.MakeImageStreamImageObjectMeta(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name)) |
|
57 |
+ } |
|
58 |
+ return nil |
|
59 |
+} |
|
60 |
+ |
|
61 |
+// AddOutputEdges links the build config to its output image node. |
|
62 |
+func AddOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { |
|
63 |
+ out := imageRefNode(g, node.BuildConfig.Spec.Output.To, node.BuildConfig) |
|
64 |
+ g.AddEdge(node, out, BuildOutputEdgeKind) |
|
65 |
+} |
|
53 | 66 |
|
67 |
+// AddInputEdges links the build config to its input image and source nodes. |
|
68 |
+func AddInputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { |
|
54 | 69 |
if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Spec.Source); in != nil { |
55 | 70 |
g.AddEdge(in, node, BuildInputEdgeKind) |
56 | 71 |
} |
72 |
+ inputImage := buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) |
|
73 |
+ if input := imageRefNode(g, inputImage, node.BuildConfig); input != nil { |
|
74 |
+ g.AddEdge(input, node, BuildInputImageEdgeKind) |
|
75 |
+ } |
|
76 |
+} |
|
57 | 77 |
|
58 |
- from := buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) |
|
59 |
- if from != nil { |
|
60 |
- switch from.Kind { |
|
61 |
- case "DockerImage": |
|
62 |
- if ref, err := imageapi.ParseDockerImageReference(from.Name); err == nil { |
|
63 |
- tag := ref.Tag |
|
64 |
- ref.Tag = "" |
|
65 |
- in := imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag) |
|
66 |
- g.AddEdge(in, node, BuildInputImageEdgeKind) |
|
67 |
- } |
|
68 |
- case "ImageStream": |
|
69 |
- in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name, imageapi.DefaultImageTag)) |
|
70 |
- g.AddEdge(in, node, BuildInputImageEdgeKind) |
|
71 |
- case "ImageStreamTag": |
|
72 |
- in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name)) |
|
73 |
- g.AddEdge(in, node, BuildInputImageEdgeKind) |
|
74 |
- case "ImageStreamImage": |
|
75 |
- in := imagegraph.FindOrCreateSyntheticImageStreamImageNode(g, imagegraph.MakeImageStreamImageObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name)) |
|
76 |
- g.AddEdge(in, node, BuildInputImageEdgeKind) |
|
78 |
+// AddTriggerEdges links the build config to its trigger input image nodes. |
|
79 |
+func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { |
|
80 |
+ for _, trigger := range node.BuildConfig.Spec.Triggers { |
|
81 |
+ if trigger.Type != buildapi.ImageChangeBuildTriggerType { |
|
82 |
+ continue |
|
83 |
+ } |
|
84 |
+ from := trigger.ImageChange.From |
|
85 |
+ if trigger.ImageChange.From == nil { |
|
86 |
+ from = buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) |
|
77 | 87 |
} |
88 |
+ triggerNode := imageRefNode(g, from, node.BuildConfig) |
|
89 |
+ g.AddEdge(triggerNode, node, BuildTriggerImageEdgeKind) |
|
78 | 90 |
} |
91 |
+} |
|
92 |
+ |
|
93 |
+// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on. |
|
94 |
+func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode { |
|
95 |
+ AddInputEdges(g, node) |
|
96 |
+ AddTriggerEdges(g, node) |
|
97 |
+ AddOutputEdges(g, node) |
|
79 | 98 |
return node |
80 | 99 |
} |
81 | 100 |
|
101 |
+// AddAllInputOutputEdges adds input and output edges for all BuildConfigs in the given graph |
|
82 | 102 |
func AddAllInputOutputEdges(g osgraph.MutableUniqueGraph) { |
83 | 103 |
for _, node := range g.(graph.Graph).Nodes() { |
84 | 104 |
if bcNode, ok := node.(*buildgraph.BuildConfigNode); ok { |
... | ... |
@@ -2,6 +2,7 @@ package describe |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
+ "sort" |
|
5 | 6 |
"strings" |
6 | 7 |
|
7 | 8 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
... | ... |
@@ -10,8 +11,8 @@ import ( |
10 | 10 |
"github.com/golang/glog" |
11 | 11 |
"github.com/gonum/graph" |
12 | 12 |
"github.com/gonum/graph/encoding/dot" |
13 |
+ "github.com/gonum/graph/internal" |
|
13 | 14 |
"github.com/gonum/graph/path" |
14 |
- "github.com/gonum/graph/traverse" |
|
15 | 15 |
|
16 | 16 |
osgraph "github.com/openshift/origin/pkg/api/graph" |
17 | 17 |
buildedges "github.com/openshift/origin/pkg/build/graph" |
... | ... |
@@ -78,7 +79,7 @@ func (d *ChainDescriber) MakeGraph() (osgraph.Graph, error) { |
78 | 78 |
// image stream tag (name:tag) in namespace. Namespace is needed here |
79 | 79 |
// because image stream tags with the same name can be found across |
80 | 80 |
// different namespaces. |
81 |
-func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag) (string, error) { |
|
81 |
+func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImages bool) (string, error) { |
|
82 | 82 |
g, err := d.MakeGraph() |
83 | 83 |
if err != nil { |
84 | 84 |
return "", err |
... | ... |
@@ -90,8 +91,13 @@ func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag) (string, error) |
90 | 90 |
return "", NotFoundErr(fmt.Sprintf("%q", ist.Name)) |
91 | 91 |
} |
92 | 92 |
|
93 |
+ buildInputEdgeKinds := []string{buildedges.BuildTriggerImageEdgeKind} |
|
94 |
+ if includeInputImages { |
|
95 |
+ buildInputEdgeKinds = append(buildInputEdgeKinds, buildedges.BuildInputImageEdgeKind) |
|
96 |
+ } |
|
97 |
+ |
|
93 | 98 |
// Partition down to the subgraph containing the ist of interest |
94 |
- partitioned := partition(g, istNode) |
|
99 |
+ partitioned := partition(g, istNode, buildInputEdgeKinds) |
|
95 | 100 |
|
96 | 101 |
switch strings.ToLower(d.outputFormat) { |
97 | 102 |
case "dot": |
... | ... |
@@ -108,11 +114,14 @@ func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag) (string, error) |
108 | 108 |
} |
109 | 109 |
|
110 | 110 |
// partition the graph down to a subgraph starting from the given root |
111 |
-func partition(g osgraph.Graph, root graph.Node) osgraph.Graph { |
|
111 |
+func partition(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) osgraph.Graph { |
|
112 | 112 |
// Filter out all but BuildConfig and ImageStreamTag nodes |
113 | 113 |
nodeFn := osgraph.NodesOfKind(buildgraph.BuildConfigNodeKind, imagegraph.ImageStreamTagNodeKind) |
114 | 114 |
// Filter out all but BuildInputImage and BuildOutput edges |
115 |
- edgeFn := osgraph.EdgesOfKind(buildedges.BuildInputImageEdgeKind, buildedges.BuildOutputEdgeKind) |
|
115 |
+ edgeKinds := []string{} |
|
116 |
+ edgeKinds = append(edgeKinds, buildInputEdgeKinds...) |
|
117 |
+ edgeKinds = append(edgeKinds, buildedges.BuildOutputEdgeKind) |
|
118 |
+ edgeFn := osgraph.EdgesOfKind(edgeKinds...) |
|
116 | 119 |
sub := g.Subgraph(nodeFn, edgeFn) |
117 | 120 |
|
118 | 121 |
// Filter out inbound edges to the ist of interest |
... | ... |
@@ -149,7 +158,7 @@ func (d *ChainDescriber) humanReadableOutput(g osgraph.Graph, root graph.Node) s |
149 | 149 |
} |
150 | 150 |
out := "" |
151 | 151 |
|
152 |
- dfs := &traverse.DepthFirst{ |
|
152 |
+ dfs := &DepthFirst{ |
|
153 | 153 |
Visit: func(u, v graph.Node) { |
154 | 154 |
depth[v] = depth[u] + 1 |
155 | 155 |
}, |
... | ... |
@@ -189,3 +198,52 @@ func outputHelper(info, namespace string, singleNamespace bool) string { |
189 | 189 |
} |
190 | 190 |
return fmt.Sprintf("<%s %s>", namespace, info) |
191 | 191 |
} |
192 |
+ |
|
193 |
+// DepthFirst implements stateful depth-first graph traversal. |
|
194 |
+// Modifies behavior of visitor.DepthFirst to allow nodes to be visited multiple |
|
195 |
+// times as long as they're not in the current stack |
|
196 |
+type DepthFirst struct { |
|
197 |
+ EdgeFilter func(graph.Edge) bool |
|
198 |
+ Visit func(u, v graph.Node) |
|
199 |
+ stack internal.NodeStack |
|
200 |
+} |
|
201 |
+ |
|
202 |
+// Walk performs a depth-first traversal of the graph g starting from the given node |
|
203 |
+func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) bool) graph.Node { |
|
204 |
+ return d.visit(g, from, until) |
|
205 |
+} |
|
206 |
+ |
|
207 |
+func (d *DepthFirst) visit(g graph.Graph, t graph.Node, until func(graph.Node) bool) graph.Node { |
|
208 |
+ if until != nil && until(t) { |
|
209 |
+ return t |
|
210 |
+ } |
|
211 |
+ d.stack.Push(t) |
|
212 |
+ children := osgraph.ByID(g.From(t)) |
|
213 |
+ sort.Sort(children) |
|
214 |
+ for _, n := range children { |
|
215 |
+ if d.EdgeFilter != nil && !d.EdgeFilter(g.Edge(t, n)) { |
|
216 |
+ continue |
|
217 |
+ } |
|
218 |
+ if d.visited(n.ID()) { |
|
219 |
+ continue |
|
220 |
+ } |
|
221 |
+ if d.Visit != nil { |
|
222 |
+ d.Visit(t, n) |
|
223 |
+ } |
|
224 |
+ result := d.visit(g, n, until) |
|
225 |
+ if result != nil { |
|
226 |
+ return result |
|
227 |
+ } |
|
228 |
+ } |
|
229 |
+ d.stack.Pop() |
|
230 |
+ return nil |
|
231 |
+} |
|
232 |
+ |
|
233 |
+func (d *DepthFirst) visited(id int) bool { |
|
234 |
+ for _, n := range d.stack { |
|
235 |
+ if n.ID() == id { |
|
236 |
+ return true |
|
237 |
+ } |
|
238 |
+ } |
|
239 |
+ return false |
|
240 |
+} |
... | ... |
@@ -7,6 +7,8 @@ import ( |
7 | 7 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
8 | 8 |
ktestclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" |
9 | 9 |
kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
10 |
+ "github.com/gonum/graph" |
|
11 |
+ "github.com/gonum/graph/concrete" |
|
10 | 12 |
|
11 | 13 |
"github.com/openshift/origin/pkg/client/testclient" |
12 | 14 |
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes" |
... | ... |
@@ -21,9 +23,10 @@ func TestChainDescriber(t *testing.T) { |
21 | 21 |
name string |
22 | 22 |
tag string |
23 | 23 |
path string |
24 |
- humanReadable map[string]struct{} |
|
24 |
+ humanReadable map[string]int |
|
25 | 25 |
dot []string |
26 | 26 |
expectedErr error |
27 |
+ includeInputImg bool |
|
27 | 28 |
}{ |
28 | 29 |
{ |
29 | 30 |
testName: "human readable test - single namespace", |
... | ... |
@@ -33,12 +36,12 @@ func TestChainDescriber(t *testing.T) { |
33 | 33 |
name: "ruby-20-centos7", |
34 | 34 |
tag: "latest", |
35 | 35 |
path: "../../../../pkg/cmd/experimental/buildchain/test/single-namespace-bcs.yaml", |
36 |
- humanReadable: map[string]struct{}{ |
|
37 |
- "imagestreamtag/ruby-20-centos7:latest": {}, |
|
38 |
- "\tbc/ruby-hello-world": {}, |
|
39 |
- "\t\timagestreamtag/ruby-hello-world:latest": {}, |
|
40 |
- "\tbc/ruby-sample-build": {}, |
|
41 |
- "\t\timagestreamtag/origin-ruby-sample:latest": {}, |
|
36 |
+ humanReadable: map[string]int{ |
|
37 |
+ "imagestreamtag/ruby-20-centos7:latest": 1, |
|
38 |
+ "\tbc/ruby-hello-world": 1, |
|
39 |
+ "\t\timagestreamtag/ruby-hello-world:latest": 1, |
|
40 |
+ "\tbc/ruby-sample-build": 1, |
|
41 |
+ "\t\timagestreamtag/origin-ruby-sample:latest": 1, |
|
42 | 42 |
}, |
43 | 43 |
expectedErr: nil, |
44 | 44 |
}, |
... | ... |
@@ -62,8 +65,8 @@ func TestChainDescriber(t *testing.T) { |
62 | 62 |
"// Edge definitions.", |
63 | 63 |
"[label=\"BuildOutput\"];", |
64 | 64 |
"[label=\"BuildOutput\"];", |
65 |
- "[label=\"BuildInputImage\"];", |
|
66 |
- "[label=\"BuildInputImage\"];", |
|
65 |
+ "[label=\"BuildInputImage,BuildTriggerImage\"];", |
|
66 |
+ "[label=\"BuildInputImage,BuildTriggerImage\"];", |
|
67 | 67 |
"}", |
68 | 68 |
}, |
69 | 69 |
expectedErr: nil, |
... | ... |
@@ -76,12 +79,12 @@ func TestChainDescriber(t *testing.T) { |
76 | 76 |
name: "ruby-20-centos7", |
77 | 77 |
tag: "latest", |
78 | 78 |
path: "../../../../pkg/cmd/experimental/buildchain/test/multiple-namespaces-bcs.yaml", |
79 |
- humanReadable: map[string]struct{}{ |
|
80 |
- "<master imagestreamtag/ruby-20-centos7:latest>": {}, |
|
81 |
- "\t<default bc/ruby-hello-world>": {}, |
|
82 |
- "\t\t<test imagestreamtag/ruby-hello-world:latest>": {}, |
|
83 |
- "\t<test bc/ruby-sample-build>": {}, |
|
84 |
- "\t\t<another imagestreamtag/origin-ruby-sample:latest>": {}, |
|
79 |
+ humanReadable: map[string]int{ |
|
80 |
+ "<master imagestreamtag/ruby-20-centos7:latest>": 1, |
|
81 |
+ "\t<default bc/ruby-hello-world>": 1, |
|
82 |
+ "\t\t<test imagestreamtag/ruby-hello-world:latest>": 1, |
|
83 |
+ "\t<test bc/ruby-sample-build>": 1, |
|
84 |
+ "\t\t<another imagestreamtag/origin-ruby-sample:latest>": 1, |
|
85 | 85 |
}, |
86 | 86 |
expectedErr: nil, |
87 | 87 |
}, |
... | ... |
@@ -105,12 +108,59 @@ func TestChainDescriber(t *testing.T) { |
105 | 105 |
"// Edge definitions.", |
106 | 106 |
"[label=\"BuildOutput\"];", |
107 | 107 |
"[label=\"BuildOutput\"];", |
108 |
- "[label=\"BuildInputImage\"];", |
|
109 |
- "[label=\"BuildInputImage\"];", |
|
108 |
+ "[label=\"BuildInputImage,BuildTriggerImage\"];", |
|
109 |
+ "[label=\"BuildInputImage,BuildTriggerImage\"];", |
|
110 | 110 |
"}", |
111 | 111 |
}, |
112 | 112 |
expectedErr: nil, |
113 | 113 |
}, |
114 |
+ { |
|
115 |
+ testName: "human readable - multiple triggers - triggeronly", |
|
116 |
+ name: "ruby-20-centos7", |
|
117 |
+ defaultNamespace: "test", |
|
118 |
+ tag: "latest", |
|
119 |
+ path: "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml", |
|
120 |
+ namespaces: kutil.NewStringSet("test"), |
|
121 |
+ humanReadable: map[string]int{ |
|
122 |
+ "imagestreamtag/ruby-20-centos7:latest": 1, |
|
123 |
+ "\tbc/parent1": 1, |
|
124 |
+ "\t\timagestreamtag/parent1img:latest": 1, |
|
125 |
+ "\t\t\tbc/child2": 2, |
|
126 |
+ "\t\t\t\timagestreamtag/child2img:latest": 2, |
|
127 |
+ "\tbc/parent2": 1, |
|
128 |
+ "\t\timagestreamtag/parent2img:latest": 1, |
|
129 |
+ "\t\t\tbc/child3": 2, |
|
130 |
+ "\t\t\t\timagestreamtag/child3img:latest": 2, |
|
131 |
+ "\t\t\tbc/child1": 1, |
|
132 |
+ "\t\t\t\timagestreamtag/child1img:latest": 1, |
|
133 |
+ "\tbc/parent3": 1, |
|
134 |
+ "\t\timagestreamtag/parent3img:latest": 1, |
|
135 |
+ }, |
|
136 |
+ }, |
|
137 |
+ { |
|
138 |
+ testName: "human readable - multiple triggers - trigger+input", |
|
139 |
+ name: "ruby-20-centos7", |
|
140 |
+ defaultNamespace: "test", |
|
141 |
+ tag: "latest", |
|
142 |
+ path: "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml", |
|
143 |
+ namespaces: kutil.NewStringSet("test"), |
|
144 |
+ includeInputImg: true, |
|
145 |
+ humanReadable: map[string]int{ |
|
146 |
+ "imagestreamtag/ruby-20-centos7:latest": 1, |
|
147 |
+ "\tbc/parent1": 1, |
|
148 |
+ "\t\timagestreamtag/parent1img:latest": 1, |
|
149 |
+ "\t\t\tbc/child1": 2, |
|
150 |
+ "\t\t\t\timagestreamtag/child1img:latest": 2, |
|
151 |
+ "\t\t\tbc/child2": 2, |
|
152 |
+ "\t\t\t\timagestreamtag/child2img:latest": 2, |
|
153 |
+ "\t\t\tbc/child3": 3, |
|
154 |
+ "\t\t\t\timagestreamtag/child3img:latest": 3, |
|
155 |
+ "\tbc/parent2": 1, |
|
156 |
+ "\t\timagestreamtag/parent2img:latest": 1, |
|
157 |
+ "\tbc/parent3": 1, |
|
158 |
+ "\t\timagestreamtag/parent3img:latest": 1, |
|
159 |
+ }, |
|
160 |
+ }, |
|
114 | 161 |
} |
115 | 162 |
|
116 | 163 |
for _, test := range tests { |
... | ... |
@@ -124,7 +174,8 @@ func TestChainDescriber(t *testing.T) { |
124 | 124 |
oc, _ := testclient.NewFixtureClients(o) |
125 | 125 |
ist := imagegraph.MakeImageStreamTagObjectMeta(test.defaultNamespace, test.name, test.tag) |
126 | 126 |
|
127 |
- desc, err := NewChainDescriber(oc, test.namespaces, test.output).Describe(ist) |
|
127 |
+ desc, err := NewChainDescriber(oc, test.namespaces, test.output).Describe(ist, test.includeInputImg) |
|
128 |
+ t.Logf("%s: output:\n%s\n\n", test.testName, desc) |
|
128 | 129 |
if err != test.expectedErr { |
129 | 130 |
t.Fatalf("%s: error mismatch: expected %v, got %v", test.testName, test.expectedErr, err) |
130 | 131 |
} |
... | ... |
@@ -134,7 +185,7 @@ func TestChainDescriber(t *testing.T) { |
134 | 134 |
switch test.output { |
135 | 135 |
case "dot": |
136 | 136 |
if len(test.dot) != len(got) { |
137 |
- t.Fatalf("%s: expected %d lines, got %d", test.testName, len(test.dot), len(got)) |
|
137 |
+ t.Fatalf("%s: expected %d lines, got %d:\n%s", test.testName, len(test.dot), len(got), desc) |
|
138 | 138 |
} |
139 | 139 |
for _, expected := range test.dot { |
140 | 140 |
if !strings.Contains(desc, expected) { |
... | ... |
@@ -142,14 +193,60 @@ func TestChainDescriber(t *testing.T) { |
142 | 142 |
} |
143 | 143 |
} |
144 | 144 |
case "": |
145 |
- if len(test.humanReadable) != len(got) { |
|
146 |
- t.Fatalf("%s: expected %d lines, got %d", test.testName, len(test.humanReadable), len(got)) |
|
145 |
+ if lenReadable(test.humanReadable) != len(got) { |
|
146 |
+ t.Fatalf("%s: expected %d lines, got %d:\n%s", test.testName, lenReadable(test.humanReadable), len(got), desc) |
|
147 | 147 |
} |
148 | 148 |
for _, line := range got { |
149 | 149 |
if _, ok := test.humanReadable[line]; !ok { |
150 | 150 |
t.Errorf("%s: unexpected line: %s", test.testName, line) |
151 | 151 |
} |
152 |
+ test.humanReadable[line]-- |
|
153 |
+ } |
|
154 |
+ for line, cnt := range test.humanReadable { |
|
155 |
+ if cnt != 0 { |
|
156 |
+ t.Errorf("%s: unexpected number of lines for [%s]: %d", test.testName, line, cnt) |
|
157 |
+ } |
|
152 | 158 |
} |
153 | 159 |
} |
154 | 160 |
} |
155 | 161 |
} |
162 |
+ |
|
163 |
+func lenReadable(value map[string]int) int { |
|
164 |
+ length := 0 |
|
165 |
+ for _, cnt := range value { |
|
166 |
+ length += cnt |
|
167 |
+ } |
|
168 |
+ return length |
|
169 |
+} |
|
170 |
+ |
|
171 |
+func TestDepthFirst(t *testing.T) { |
|
172 |
+ g := concrete.NewDirectedGraph() |
|
173 |
+ |
|
174 |
+ a := concrete.Node(g.NewNodeID()) |
|
175 |
+ b := concrete.Node(g.NewNodeID()) |
|
176 |
+ |
|
177 |
+ g.AddNode(a) |
|
178 |
+ g.AddNode(b) |
|
179 |
+ g.SetEdge(concrete.Edge{F: a, T: b}, 1) |
|
180 |
+ g.SetEdge(concrete.Edge{F: b, T: a}, 1) |
|
181 |
+ |
|
182 |
+ count := 0 |
|
183 |
+ |
|
184 |
+ df := &DepthFirst{ |
|
185 |
+ EdgeFilter: func(graph.Edge) bool { |
|
186 |
+ return true |
|
187 |
+ }, |
|
188 |
+ Visit: func(u, v graph.Node) { |
|
189 |
+ count++ |
|
190 |
+ t.Logf("%d -> %d\n", u.ID(), v.ID()) |
|
191 |
+ }, |
|
192 |
+ } |
|
193 |
+ |
|
194 |
+ df.Walk(g, a, func(n graph.Node) bool { |
|
195 |
+ if count > 100 { |
|
196 |
+ t.Fatalf("looped") |
|
197 |
+ return true |
|
198 |
+ } |
|
199 |
+ return false |
|
200 |
+ }) |
|
201 |
+} |
... | ... |
@@ -24,7 +24,7 @@ const ( |
24 | 24 |
Output the inputs and dependencies of your builds |
25 | 25 |
|
26 | 26 |
Supported formats for the generated graph are dot and a human-readable output. |
27 |
-Tag and namespace are optional and if they are not specified, 'latest' and the |
|
27 |
+Tag and namespace are optional and if they are not specified, 'latest' and the |
|
28 | 28 |
default namespace will be used respectively.` |
29 | 29 |
|
30 | 30 |
buildChainExample = ` // Build the dependency tree for the 'latest' tag in centos7 |
... | ... |
@@ -37,6 +37,7 @@ default namespace will be used respectively.` |
37 | 37 |
$ %[1]s centos7 -n test --all` |
38 | 38 |
) |
39 | 39 |
|
40 |
+// BuildChainRecommendedCommandName is the recommended command name |
|
40 | 41 |
const BuildChainRecommendedCommandName = "build-chain" |
41 | 42 |
|
42 | 43 |
// BuildChainOptions contains all the options needed for build-chain |
... | ... |
@@ -47,6 +48,7 @@ type BuildChainOptions struct { |
47 | 47 |
defaultNamespace string |
48 | 48 |
namespaces kutil.StringSet |
49 | 49 |
allNamespaces bool |
50 |
+ triggerOnly bool |
|
50 | 51 |
|
51 | 52 |
output string |
52 | 53 |
|
... | ... |
@@ -74,6 +76,7 @@ func NewCmdBuildChain(name, fullName string, f *clientcmd.Factory, out io.Writer |
74 | 74 |
} |
75 | 75 |
|
76 | 76 |
cmd.Flags().BoolVar(&options.allNamespaces, "all", false, "Build dependency tree for the specified image stream tag across all namespaces") |
77 |
+ cmd.Flags().BoolVar(&options.triggerOnly, "trigger-only", true, "If true, only include dependencies based on build triggers. If false, include all dependencies.") |
|
77 | 78 |
cmd.Flags().StringVarP(&options.output, "output", "o", "", "Output format of dependency tree") |
78 | 79 |
return cmd |
79 | 80 |
} |
... | ... |
@@ -151,7 +154,7 @@ func (o *BuildChainOptions) Validate() error { |
151 | 151 |
// experimental build-chain command |
152 | 152 |
func (o *BuildChainOptions) RunBuildChain() error { |
153 | 153 |
ist := imagegraph.MakeImageStreamTagObjectMeta(o.defaultNamespace, o.name, o.tag) |
154 |
- desc, err := describe.NewChainDescriber(o.c, o.namespaces, o.output).Describe(ist) |
|
154 |
+ desc, err := describe.NewChainDescriber(o.c, o.namespaces, o.output).Describe(ist, !o.triggerOnly) |
|
155 | 155 |
if err != nil { |
156 | 156 |
if _, isNotFoundErr := err.(describe.NotFoundErr); isNotFoundErr { |
157 | 157 |
// Try to get the imageStreamTag via a direct GET |
158 | 158 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,220 @@ |
0 |
+# Sets up a multi-parent build config tree |
|
1 |
+# openshift/ruby-20-centos7:latest |
|
2 |
+# -> bc - parent1 (input, trigger) |
|
3 |
+# -> parent1img:latest |
|
4 |
+# -> bc - child1 (input) |
|
5 |
+# -> bc - child2 (input, trigger) |
|
6 |
+# -> bc - child3 (input) |
|
7 |
+# -> bc - parent2 (input, trigger) |
|
8 |
+# -> parent2img:latest |
|
9 |
+# -> bc - child1 (trigger) |
|
10 |
+# -> bc - child3 (trigger) |
|
11 |
+# -> bc - parent3 (input, trigger) |
|
12 |
+# -> parent3img:latest |
|
13 |
+# -> bc - child2 (trigger) |
|
14 |
+# -> bc - child3 (trigger) |
|
15 |
+# |
|
16 |
+# bc child1 has [input] parent1img, and [trigger] parent2img |
|
17 |
+# bc child2 has [input, trigger] parent1img, and [trigger] parent3img |
|
18 |
+# bc child3 has [input] parent1img, [trigger] parent2img, [trigger] parent3img |
|
19 |
+# |
|
20 |
+apiVersion: v1 |
|
21 |
+items: |
|
22 |
+- apiVersion: v1 |
|
23 |
+ kind: BuildConfig |
|
24 |
+ metadata: |
|
25 |
+ name: parent1 |
|
26 |
+ namespace: test |
|
27 |
+ spec: |
|
28 |
+ output: |
|
29 |
+ to: |
|
30 |
+ kind: ImageStreamTag |
|
31 |
+ name: parent1img:latest |
|
32 |
+ resources: {} |
|
33 |
+ source: |
|
34 |
+ git: |
|
35 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
36 |
+ type: Git |
|
37 |
+ strategy: |
|
38 |
+ dockerStrategy: |
|
39 |
+ from: |
|
40 |
+ kind: ImageStreamTag |
|
41 |
+ name: ruby-20-centos7:latest |
|
42 |
+ type: Docker |
|
43 |
+ triggers: |
|
44 |
+ - github: |
|
45 |
+ secret: q_ZtlnBcu7ca48ie8dNi |
|
46 |
+ type: GitHub |
|
47 |
+ - generic: |
|
48 |
+ secret: 3kYKtANjVRCOPoM0uLNp |
|
49 |
+ type: Generic |
|
50 |
+ - imageChange: {} |
|
51 |
+ type: ImageChange |
|
52 |
+- apiVersion: v1 |
|
53 |
+ kind: BuildConfig |
|
54 |
+ metadata: |
|
55 |
+ name: parent2 |
|
56 |
+ namespace: test |
|
57 |
+ spec: |
|
58 |
+ output: |
|
59 |
+ to: |
|
60 |
+ kind: ImageStreamTag |
|
61 |
+ name: parent2img:latest |
|
62 |
+ resources: {} |
|
63 |
+ source: |
|
64 |
+ git: |
|
65 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
66 |
+ type: Git |
|
67 |
+ strategy: |
|
68 |
+ dockerStrategy: |
|
69 |
+ from: |
|
70 |
+ kind: ImageStreamTag |
|
71 |
+ name: ruby-20-centos7:latest |
|
72 |
+ type: Docker |
|
73 |
+ triggers: |
|
74 |
+ - github: |
|
75 |
+ secret: q_ZtlnBcu7ca48ie8dNi |
|
76 |
+ type: GitHub |
|
77 |
+ - generic: |
|
78 |
+ secret: 3kYKtANjVRCOPoM0uLNp |
|
79 |
+ type: Generic |
|
80 |
+ - imageChange: {} |
|
81 |
+ type: ImageChange |
|
82 |
+- apiVersion: v1 |
|
83 |
+ kind: BuildConfig |
|
84 |
+ metadata: |
|
85 |
+ name: parent3 |
|
86 |
+ namespace: test |
|
87 |
+ spec: |
|
88 |
+ output: |
|
89 |
+ to: |
|
90 |
+ kind: ImageStreamTag |
|
91 |
+ name: parent3img:latest |
|
92 |
+ resources: {} |
|
93 |
+ source: |
|
94 |
+ git: |
|
95 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
96 |
+ type: Git |
|
97 |
+ strategy: |
|
98 |
+ dockerStrategy: |
|
99 |
+ from: |
|
100 |
+ kind: ImageStreamTag |
|
101 |
+ name: ruby-20-centos7:latest |
|
102 |
+ type: Docker |
|
103 |
+ triggers: |
|
104 |
+ - github: |
|
105 |
+ secret: q_ZtlnBcu7ca48ie8dNi |
|
106 |
+ type: GitHub |
|
107 |
+ - generic: |
|
108 |
+ secret: 3kYKtANjVRCOPoM0uLNp |
|
109 |
+ type: Generic |
|
110 |
+ - imageChange: {} |
|
111 |
+ type: ImageChange |
|
112 |
+- apiVersion: v1 |
|
113 |
+ kind: BuildConfig |
|
114 |
+ metadata: |
|
115 |
+ name: child1 |
|
116 |
+ namespace: test |
|
117 |
+ spec: |
|
118 |
+ output: |
|
119 |
+ to: |
|
120 |
+ kind: ImageStreamTag |
|
121 |
+ name: child1img:latest |
|
122 |
+ source: |
|
123 |
+ git: |
|
124 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
125 |
+ type: Git |
|
126 |
+ strategy: |
|
127 |
+ sourceStrategy: |
|
128 |
+ from: |
|
129 |
+ kind: ImageStreamTag |
|
130 |
+ name: parent1img:latest |
|
131 |
+ type: Source |
|
132 |
+ triggers: |
|
133 |
+ - github: |
|
134 |
+ secret: secret101 |
|
135 |
+ type: GitHub |
|
136 |
+ - generic: |
|
137 |
+ secret: secret101 |
|
138 |
+ type: Generic |
|
139 |
+ - imageChange: |
|
140 |
+ from: |
|
141 |
+ name: parent2img:latest |
|
142 |
+ kind: ImageStreamTag |
|
143 |
+ type: ImageChange |
|
144 |
+- apiVersion: v1 |
|
145 |
+ kind: BuildConfig |
|
146 |
+ metadata: |
|
147 |
+ name: child2 |
|
148 |
+ namespace: test |
|
149 |
+ spec: |
|
150 |
+ output: |
|
151 |
+ to: |
|
152 |
+ kind: ImageStreamTag |
|
153 |
+ name: child2img:latest |
|
154 |
+ source: |
|
155 |
+ git: |
|
156 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
157 |
+ type: Git |
|
158 |
+ strategy: |
|
159 |
+ sourceStrategy: |
|
160 |
+ from: |
|
161 |
+ kind: DockerImage |
|
162 |
+ name: openshift/ruby-20-centos7:latest |
|
163 |
+ type: Source |
|
164 |
+ triggers: |
|
165 |
+ - github: |
|
166 |
+ secret: secret101 |
|
167 |
+ type: GitHub |
|
168 |
+ - generic: |
|
169 |
+ secret: secret101 |
|
170 |
+ type: Generic |
|
171 |
+ - imageChange: |
|
172 |
+ from: |
|
173 |
+ name: parent1img:latest |
|
174 |
+ kind: ImageStreamTag |
|
175 |
+ type: ImageChange |
|
176 |
+ - imageChange: |
|
177 |
+ from: |
|
178 |
+ name: parent3img:latest |
|
179 |
+ kind: ImageStreamTag |
|
180 |
+ type: ImageChange |
|
181 |
+- apiVersion: v1 |
|
182 |
+ kind: BuildConfig |
|
183 |
+ metadata: |
|
184 |
+ name: child3 |
|
185 |
+ namespace: test |
|
186 |
+ spec: |
|
187 |
+ output: |
|
188 |
+ to: |
|
189 |
+ kind: ImageStreamTag |
|
190 |
+ name: child3img:latest |
|
191 |
+ source: |
|
192 |
+ git: |
|
193 |
+ uri: https://github.com/openshift/ruby-hello-world.git |
|
194 |
+ type: Git |
|
195 |
+ strategy: |
|
196 |
+ sourceStrategy: |
|
197 |
+ from: |
|
198 |
+ kind: ImageStreamTag |
|
199 |
+ name: parent1img:latest |
|
200 |
+ type: Source |
|
201 |
+ triggers: |
|
202 |
+ - github: |
|
203 |
+ secret: secret101 |
|
204 |
+ type: GitHub |
|
205 |
+ - generic: |
|
206 |
+ secret: secret101 |
|
207 |
+ type: Generic |
|
208 |
+ - imageChange: |
|
209 |
+ from: |
|
210 |
+ name: parent2img:latest |
|
211 |
+ kind: ImageStreamTag |
|
212 |
+ type: ImageChange |
|
213 |
+ - imageChange: |
|
214 |
+ from: |
|
215 |
+ name: parent3img:latest |
|
216 |
+ kind: ImageStreamTag |
|
217 |
+ type: ImageChange |
|
218 |
+kind: List |
|
219 |
+metadata: {} |
... | ... |
@@ -934,6 +934,7 @@ _openshift_admin_build-chain() |
934 | 934 |
flags+=("-h") |
935 | 935 |
flags+=("--output=") |
936 | 936 |
two_word_flags+=("-o") |
937 |
+ flags+=("--trigger-only") |
|
937 | 938 |
|
938 | 939 |
must_have_one_flag=() |
939 | 940 |
must_have_one_noun=() |
... | ... |
@@ -4045,6 +4046,7 @@ _openshift_ex_build-chain() |
4045 | 4045 |
flags+=("-h") |
4046 | 4046 |
flags+=("--output=") |
4047 | 4047 |
two_word_flags+=("-o") |
4048 |
+ flags+=("--trigger-only") |
|
4048 | 4049 |
|
4049 | 4050 |
must_have_one_flag=() |
4050 | 4051 |
must_have_one_noun=() |
... | ... |
@@ -316,3 +316,169 @@ func runTest(t *testing.T, testname string, clusterAdminClient *client.Client, i |
316 | 316 |
t.Errorf("unexpected trigger id: expected %v, got %v", e, a) |
317 | 317 |
} |
318 | 318 |
} |
319 |
+ |
|
320 |
+func TestMultipleImageChangeBuildTriggers(t *testing.T) { |
|
321 |
+ mockImageStream := func(name, tag string) *imageapi.ImageStream { |
|
322 |
+ return &imageapi.ImageStream{ |
|
323 |
+ ObjectMeta: kapi.ObjectMeta{Name: name}, |
|
324 |
+ Spec: imageapi.ImageStreamSpec{ |
|
325 |
+ DockerImageRepository: "registry:5000/openshift/" + name, |
|
326 |
+ Tags: map[string]imageapi.TagReference{ |
|
327 |
+ tag: { |
|
328 |
+ From: &kapi.ObjectReference{ |
|
329 |
+ Kind: "DockerImage", |
|
330 |
+ Name: "registry:5000/openshift/" + name + ":" + tag, |
|
331 |
+ }, |
|
332 |
+ }, |
|
333 |
+ }, |
|
334 |
+ }, |
|
335 |
+ } |
|
336 |
+ |
|
337 |
+ } |
|
338 |
+ mockStreamMapping := func(name, tag string) *imageapi.ImageStreamMapping { |
|
339 |
+ return &imageapi.ImageStreamMapping{ |
|
340 |
+ ObjectMeta: kapi.ObjectMeta{Name: name}, |
|
341 |
+ Tag: tag, |
|
342 |
+ Image: imageapi.Image{ |
|
343 |
+ ObjectMeta: kapi.ObjectMeta{ |
|
344 |
+ Name: name, |
|
345 |
+ }, |
|
346 |
+ DockerImageReference: "registry:5000/openshift/" + name + ":" + tag, |
|
347 |
+ }, |
|
348 |
+ } |
|
349 |
+ |
|
350 |
+ } |
|
351 |
+ multipleImageChangeBuildConfig := func() *buildapi.BuildConfig { |
|
352 |
+ strategy := stiStrategy("ImageStreamTag", "image1:tag1") |
|
353 |
+ bc := imageChangeBuildConfig("multi-image-trigger", strategy) |
|
354 |
+ bc.Spec.BuildSpec.Output.To.Name = "image1:outputtag" |
|
355 |
+ bc.Spec.Triggers = []buildapi.BuildTriggerPolicy{ |
|
356 |
+ { |
|
357 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
358 |
+ ImageChange: &buildapi.ImageChangeTrigger{}, |
|
359 |
+ }, |
|
360 |
+ { |
|
361 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
362 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
363 |
+ From: &kapi.ObjectReference{ |
|
364 |
+ Name: "image2:tag2", |
|
365 |
+ Kind: "ImageStreamTag", |
|
366 |
+ }, |
|
367 |
+ }, |
|
368 |
+ }, |
|
369 |
+ { |
|
370 |
+ Type: buildapi.ImageChangeBuildTriggerType, |
|
371 |
+ ImageChange: &buildapi.ImageChangeTrigger{ |
|
372 |
+ From: &kapi.ObjectReference{ |
|
373 |
+ Name: "image3:tag3", |
|
374 |
+ Kind: "ImageStreamTag", |
|
375 |
+ }, |
|
376 |
+ }, |
|
377 |
+ }, |
|
378 |
+ } |
|
379 |
+ return bc |
|
380 |
+ } |
|
381 |
+ clusterAdminClient := setup(t) |
|
382 |
+ config := multipleImageChangeBuildConfig() |
|
383 |
+ triggersToTest := []struct { |
|
384 |
+ triggerIndex int |
|
385 |
+ name string |
|
386 |
+ tag string |
|
387 |
+ }{ |
|
388 |
+ { |
|
389 |
+ triggerIndex: 0, |
|
390 |
+ name: "image1", |
|
391 |
+ tag: "tag1", |
|
392 |
+ }, |
|
393 |
+ { |
|
394 |
+ triggerIndex: 1, |
|
395 |
+ name: "image2", |
|
396 |
+ tag: "tag2", |
|
397 |
+ }, |
|
398 |
+ { |
|
399 |
+ triggerIndex: 2, |
|
400 |
+ name: "image3", |
|
401 |
+ tag: "tag3", |
|
402 |
+ }, |
|
403 |
+ } |
|
404 |
+ |
|
405 |
+ created, err := clusterAdminClient.BuildConfigs(testutil.Namespace()).Create(config) |
|
406 |
+ if err != nil { |
|
407 |
+ t.Fatalf("Couldn't create BuildConfig: %v", err) |
|
408 |
+ } |
|
409 |
+ watch, err := clusterAdminClient.Builds(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), created.ResourceVersion) |
|
410 |
+ if err != nil { |
|
411 |
+ t.Fatalf("Couldn't subscribe to Builds %v", err) |
|
412 |
+ } |
|
413 |
+ defer watch.Stop() |
|
414 |
+ |
|
415 |
+ watch2, err := clusterAdminClient.BuildConfigs(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), created.ResourceVersion) |
|
416 |
+ if err != nil { |
|
417 |
+ t.Fatalf("Couldn't subscribe to BuildConfigs %v", err) |
|
418 |
+ } |
|
419 |
+ defer watch2.Stop() |
|
420 |
+ |
|
421 |
+ for _, tc := range triggersToTest { |
|
422 |
+ imageStream := mockImageStream(tc.name, tc.tag) |
|
423 |
+ imageStreamMapping := mockStreamMapping(tc.name, tc.tag) |
|
424 |
+ imageStream, err = clusterAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream) |
|
425 |
+ if err != nil { |
|
426 |
+ t.Fatalf("Couldn't create ImageStream: %v", err) |
|
427 |
+ } |
|
428 |
+ |
|
429 |
+ err = clusterAdminClient.ImageStreamMappings(testutil.Namespace()).Create(imageStreamMapping) |
|
430 |
+ if err != nil { |
|
431 |
+ t.Fatalf("Couldn't create Image: %v", err) |
|
432 |
+ } |
|
433 |
+ // wait for initial build event from the creation of the imagerepo |
|
434 |
+ event := <-watch.ResultChan() |
|
435 |
+ if e, a := watchapi.Added, event.Type; e != a { |
|
436 |
+ t.Fatalf("expected watch event type %s, got %s", e, a) |
|
437 |
+ } |
|
438 |
+ newBuild := event.Object.(*buildapi.Build) |
|
439 |
+ trigger := config.Spec.Triggers[tc.triggerIndex] |
|
440 |
+ if trigger.ImageChange.From == nil { |
|
441 |
+ switch newBuild.Spec.Strategy.Type { |
|
442 |
+ case buildapi.SourceBuildStrategyType: |
|
443 |
+ if newBuild.Spec.Strategy.SourceStrategy.From.Name != "registry:5000/openshift/"+tc.name+":"+tc.tag { |
|
444 |
+ i, _ := clusterAdminClient.ImageStreams(testutil.Namespace()).Get(imageStream.Name) |
|
445 |
+ bc, _ := clusterAdminClient.BuildConfigs(testutil.Namespace()).Get(config.Name) |
|
446 |
+ t.Fatalf("Expected build with base image %s, got %s\n, imagerepo is %v\ntrigger is %#v", "registry:5000/openshift/"+tc.name+":"+tc.tag, newBuild.Spec.Strategy.DockerStrategy.From.Name, i, bc.Spec.Triggers[tc.triggerIndex].ImageChange) |
|
447 |
+ } |
|
448 |
+ case buildapi.DockerBuildStrategyType: |
|
449 |
+ if newBuild.Spec.Strategy.DockerStrategy.From.Name != "registry:8080/openshift/"+tc.name+":"+tc.tag { |
|
450 |
+ i, _ := clusterAdminClient.ImageStreams(testutil.Namespace()).Get(imageStream.Name) |
|
451 |
+ bc, _ := clusterAdminClient.BuildConfigs(testutil.Namespace()).Get(config.Name) |
|
452 |
+ t.Fatalf("Expected build with base image %s, got %s\n, imagerepo is %v\ntrigger is %#v", "registry:5000/openshift/"+tc.name+":"+tag, newBuild.Spec.Strategy.DockerStrategy.From.Name, i, bc.Spec.Triggers[tc.triggerIndex].ImageChange) |
|
453 |
+ } |
|
454 |
+ case buildapi.CustomBuildStrategyType: |
|
455 |
+ if newBuild.Spec.Strategy.CustomStrategy.From.Name != "registry:8080/openshift/"+tc.name+":"+tag { |
|
456 |
+ i, _ := clusterAdminClient.ImageStreams(testutil.Namespace()).Get(imageStream.Name) |
|
457 |
+ bc, _ := clusterAdminClient.BuildConfigs(testutil.Namespace()).Get(config.Name) |
|
458 |
+ t.Fatalf("Expected build with base image %s, got %s\n, imagerepo is %v\ntrigger is %#v", "registry:5000/openshift/"+tc.name+":"+tag, newBuild.Spec.Strategy.DockerStrategy.From.Name, i, bc.Spec.Triggers[tc.triggerIndex].ImageChange) |
|
459 |
+ } |
|
460 |
+ |
|
461 |
+ } |
|
462 |
+ } |
|
463 |
+ event = <-watch.ResultChan() |
|
464 |
+ if e, a := watchapi.Modified, event.Type; e != a { |
|
465 |
+ t.Fatalf("expected watch event type %s, got %s", e, a) |
|
466 |
+ } |
|
467 |
+ newBuild = event.Object.(*buildapi.Build) |
|
468 |
+ // Make sure the resolution of the build's docker image pushspec didn't mutate the persisted API object |
|
469 |
+ if newBuild.Spec.Output.To.Name != "image1:outputtag" { |
|
470 |
+ t.Fatalf("unexpected build output: %#v %#v", newBuild.Spec.Output.To, newBuild.Spec.Output) |
|
471 |
+ } |
|
472 |
+ |
|
473 |
+ // wait for build config to be updated |
|
474 |
+ <-watch2.ResultChan() |
|
475 |
+ updatedConfig, err := clusterAdminClient.BuildConfigs(testutil.Namespace()).Get(config.Name) |
|
476 |
+ if err != nil { |
|
477 |
+ t.Fatalf("Couldn't get BuildConfig: %v", err) |
|
478 |
+ } |
|
479 |
+ // the first tag did not have an image id, so the last trigger field is the pull spec |
|
480 |
+ if updatedConfig.Spec.Triggers[tc.triggerIndex].ImageChange.LastTriggeredImageID != "registry:5000/openshift/"+tc.name+":"+tc.tag { |
|
481 |
+ t.Fatalf("Expected imageID equal to pull spec, got %#v", updatedConfig.Spec.Triggers[0].ImageChange) |
|
482 |
+ } |
|
483 |
+ } |
|
484 |
+} |