Browse code

Respect tag referencePolicy in builds and deployments

Use the latest resolved value, rather than just the exact value from the
image stream tag.

Clayton Coleman authored on 2016/12/11 08:45:59
Showing 5 changed files
... ...
@@ -49,10 +49,10 @@ func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream)
49 49
 	}
50 50
 	for _, config := range bcs {
51 51
 		var (
52
-			from           *kapi.ObjectReference
53
-			shouldBuild    = false
54
-			triggeredImage = ""
55
-			latest         *imageapi.TagEvent
52
+			from            *kapi.ObjectReference
53
+			shouldBuild     = false
54
+			triggeredImage  = ""
55
+			latestReference string
56 56
 		)
57 57
 		// For every ImageChange trigger find the latest tagged image from the image stream and
58 58
 		// invoke a build using that image id. A new build is triggered only if the latest tagged image id or pull spec
... ...
@@ -90,8 +90,8 @@ func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream)
90 90
 
91 91
 			// This split is safe because ImageStreamTag names always have the form
92 92
 			// name:tag.
93
-			latest = imageapi.LatestTaggedImage(stream, tag)
94
-			if latest == nil {
93
+			latestReference, ok = imageapi.ResolveLatestTaggedImage(stream, tag)
94
+			if !ok {
95 95
 				glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", stream.Namespace, stream.Name, tag)
96 96
 				continue
97 97
 			}
... ...
@@ -99,7 +99,7 @@ func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream)
99 99
 
100 100
 			// (must be different) to trigger a build
101 101
 			last := trigger.ImageChange.LastTriggeredImageID
102
-			next := latest.DockerImageReference
102
+			next := latestReference
103 103
 
104 104
 			if len(last) == 0 || (len(next) > 0 && next != last) {
105 105
 				triggeredImage = next
... ...
@@ -123,7 +123,7 @@ func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream)
123 123
 					buildapi.BuildTriggerCause{
124 124
 						Message: buildapi.BuildTriggerCauseImageMsg,
125 125
 						ImageChangeBuild: &buildapi.ImageChangeCause{
126
-							ImageID: latest.DockerImageReference,
126
+							ImageID: latestReference,
127 127
 							FromRef: from,
128 128
 						},
129 129
 					},
... ...
@@ -56,8 +56,8 @@ func (g *DeploymentConfigGenerator) Generate(ctx kapi.Context, name string) (*de
56 56
 		}
57 57
 
58 58
 		// Find the latest tag event for the trigger tag
59
-		latestEvent := imageapi.LatestTaggedImage(imageStream, tag)
60
-		if latestEvent == nil {
59
+		latestReference, ok := imageapi.ResolveLatestTaggedImage(imageStream, tag)
60
+		if !ok {
61 61
 			f := field.NewPath("triggers").Index(i).Child("imageChange", "tag")
62 62
 			errs = append(errs, field.Invalid(f, tag, fmt.Sprintf("no image recorded for %s/%s:%s", imageStream.Namespace, imageStream.Name, tag)))
63 63
 			continue
... ...
@@ -72,12 +72,12 @@ func (g *DeploymentConfigGenerator) Generate(ctx kapi.Context, name string) (*de
72 72
 			if !names.Has(container.Name) {
73 73
 				continue
74 74
 			}
75
-			if len(latestEvent.DockerImageReference) > 0 &&
76
-				container.Image != latestEvent.DockerImageReference {
75
+			if len(latestReference) > 0 &&
76
+				container.Image != latestReference {
77 77
 				// Update the image
78
-				container.Image = latestEvent.DockerImageReference
78
+				container.Image = latestReference
79 79
 				// Log the last triggered image ID
80
-				params.LastTriggeredImage = latestEvent.DockerImageReference
80
+				params.LastTriggeredImage = latestReference
81 81
 				containerChanged = true
82 82
 			}
83 83
 		}
... ...
@@ -136,14 +136,13 @@ func processTriggers(config *deployapi.DeploymentConfig, isn client.ImageStreams
136 136
 		}
137 137
 
138 138
 		// Find the latest tag event for the trigger reference.
139
-		latestEvent := imageapi.LatestTaggedImage(stream, tag)
140
-		if latestEvent == nil {
139
+		latestReference, ok := imageapi.ResolveLatestTaggedImage(stream, tag)
140
+		if !ok {
141 141
 			continue
142 142
 		}
143 143
 
144 144
 		// Ensure a change occurred
145
-		latestRef := latestEvent.DockerImageReference
146
-		if len(latestRef) == 0 || latestRef == params.LastTriggeredImage {
145
+		if len(latestReference) == 0 || latestReference == params.LastTriggeredImage {
147 146
 			continue
148 147
 		}
149 148
 
... ...
@@ -155,11 +154,11 @@ func processTriggers(config *deployapi.DeploymentConfig, isn client.ImageStreams
155 155
 				continue
156 156
 			}
157 157
 
158
-			if container.Image != latestRef {
158
+			if container.Image != latestReference {
159 159
 				// Update the image
160
-				container.Image = latestRef
160
+				container.Image = latestReference
161 161
 				// Log the last triggered image ID
162
-				params.LastTriggeredImage = latestRef
162
+				params.LastTriggeredImage = latestReference
163 163
 			}
164 164
 		}
165 165
 	}
... ...
@@ -565,6 +565,55 @@ func LatestTaggedImage(stream *ImageStream, tag string) *TagEvent {
565 565
 	return nil
566 566
 }
567 567
 
568
+// ResolveLatestTaggedImage returns the appropriate pull spec for a given tag in
569
+// the image stream, handling the tag's reference policy if necessary to return
570
+// a resolved image. Callers that transform an ImageStreamTag into a pull spec
571
+// should use this method instead of LatestTaggedImage.
572
+func ResolveLatestTaggedImage(stream *ImageStream, tag string) (string, bool) {
573
+	if len(tag) == 0 {
574
+		tag = DefaultImageTag
575
+	}
576
+
577
+	// retrieve event
578
+	latest := LatestTaggedImage(stream, tag)
579
+	if latest == nil {
580
+		return "", false
581
+	}
582
+
583
+	// retrieve spec policy - if not found, we use the latest spec
584
+	ref, ok := stream.Spec.Tags[tag]
585
+	if !ok {
586
+		return latest.DockerImageReference, true
587
+	}
588
+
589
+	switch ref.ReferencePolicy.Type {
590
+	// the local reference policy attempts to use image pull through on the integrated
591
+	// registry if possible
592
+	case LocalTagReferencePolicy:
593
+		local := stream.Status.DockerImageRepository
594
+		if len(local) == 0 || len(latest.Image) == 0 {
595
+			// fallback to the originating reference if no local docker registry defined or we
596
+			// lack an image ID
597
+			return latest.DockerImageReference, true
598
+		}
599
+
600
+		ref, err := ParseDockerImageReference(local)
601
+		if err != nil {
602
+			// fallback to the originating reference if the reported local repository spec is not valid
603
+			return latest.DockerImageReference, true
604
+		}
605
+
606
+		// create a local pullthrough URL
607
+		ref.Tag = ""
608
+		ref.ID = latest.Image
609
+		return ref.Exact(), true
610
+
611
+	// the default policy is to use the originating image
612
+	default:
613
+		return latest.DockerImageReference, true
614
+	}
615
+}
616
+
568 617
 // DifferentTagEvent returns true if the supplied tag event matches the current stream tag event.
569 618
 // Generation is not compared.
570 619
 func DifferentTagEvent(stream *ImageStream, tag string, next TagEvent) bool {
... ...
@@ -839,6 +839,158 @@ func TestLatestTaggedImage(t *testing.T) {
839 839
 	}
840 840
 }
841 841
 
842
+func TestResolveLatestTaggedImage(t *testing.T) {
843
+	tests := []struct {
844
+		tag            string
845
+		statusRef      string
846
+		refs           map[string]TagReference
847
+		tags           map[string]TagEventList
848
+		expected       string
849
+		expectNotFound bool
850
+	}{
851
+		{
852
+			tag:            "foo",
853
+			tags:           map[string]TagEventList{},
854
+			expectNotFound: true,
855
+		},
856
+		{
857
+			tag: "foo",
858
+			tags: map[string]TagEventList{
859
+				"latest": {
860
+					Items: []TagEvent{
861
+						{DockerImageReference: "latest-ref"},
862
+						{DockerImageReference: "older"},
863
+					},
864
+				},
865
+			},
866
+			expectNotFound: true,
867
+		},
868
+		{
869
+			tag: "",
870
+			tags: map[string]TagEventList{
871
+				"latest": {
872
+					Items: []TagEvent{
873
+						{DockerImageReference: "latest-ref"},
874
+						{DockerImageReference: "older"},
875
+					},
876
+				},
877
+			},
878
+			expected: "latest-ref",
879
+		},
880
+		{
881
+			tag: "foo",
882
+			tags: map[string]TagEventList{
883
+				"latest": {
884
+					Items: []TagEvent{
885
+						{DockerImageReference: "latest-ref"},
886
+						{DockerImageReference: "older"},
887
+					},
888
+				},
889
+				"foo": {
890
+					Items: []TagEvent{
891
+						{DockerImageReference: "foo-ref"},
892
+						{DockerImageReference: "older"},
893
+					},
894
+				},
895
+			},
896
+			expected: "foo-ref",
897
+		},
898
+
899
+		// the default reference policy does nothing
900
+		{
901
+			refs: map[string]TagReference{
902
+				"latest": {
903
+					ReferencePolicy: TagReferencePolicy{Type: SourceTagReferencePolicy},
904
+				},
905
+			},
906
+			tags: map[string]TagEventList{
907
+				"latest": {
908
+					Items: []TagEvent{
909
+						{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
910
+						{DockerImageReference: "older"},
911
+					},
912
+				},
913
+			},
914
+			expected: "latest-ref",
915
+		},
916
+
917
+		// the local reference policy does nothing unless reference is set
918
+		{
919
+			refs: map[string]TagReference{
920
+				"latest": {
921
+					ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
922
+				},
923
+			},
924
+			tags: map[string]TagEventList{
925
+				"latest": {
926
+					Items: []TagEvent{
927
+						{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
928
+						{DockerImageReference: "older"},
929
+					},
930
+				},
931
+			},
932
+			expected: "latest-ref",
933
+		},
934
+
935
+		// the local reference policy does nothing unless the image id is set
936
+		{
937
+			statusRef: "test.server/a/b",
938
+			refs: map[string]TagReference{
939
+				"latest": {
940
+					ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
941
+				},
942
+			},
943
+			tags: map[string]TagEventList{
944
+				"latest": {
945
+					Items: []TagEvent{
946
+						{DockerImageReference: "latest-ref"},
947
+						{DockerImageReference: "older"},
948
+					},
949
+				},
950
+			},
951
+			expected: "latest-ref",
952
+		},
953
+
954
+		// the local reference policy uses the output status reference and the image id
955
+		// and returns a pullthrough spec
956
+		{
957
+			statusRef: "test.server/a/b",
958
+			refs: map[string]TagReference{
959
+				"latest": {
960
+					ReferencePolicy: TagReferencePolicy{Type: LocalTagReferencePolicy},
961
+				},
962
+			},
963
+			tags: map[string]TagEventList{
964
+				"latest": {
965
+					Items: []TagEvent{
966
+						{DockerImageReference: "latest-ref", Image: "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246"},
967
+						{DockerImageReference: "older"},
968
+					},
969
+				},
970
+			},
971
+			expected: "test.server/a/b@sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
972
+		},
973
+	}
974
+
975
+	for i, test := range tests {
976
+		stream := &ImageStream{}
977
+		stream.Status.DockerImageRepository = test.statusRef
978
+		stream.Status.Tags = test.tags
979
+		stream.Spec.Tags = test.refs
980
+
981
+		actual, ok := ResolveLatestTaggedImage(stream, test.tag)
982
+		if !ok {
983
+			if !test.expectNotFound {
984
+				t.Errorf("%d: unexpected nil result", i)
985
+			}
986
+			continue
987
+		}
988
+		if e, a := test.expected, actual; e != a {
989
+			t.Errorf("%d: expected %q, got %q", i, e, a)
990
+		}
991
+	}
992
+}
993
+
842 994
 func TestAddTagEventToImageStream(t *testing.T) {
843 995
 	tests := map[string]struct {
844 996
 		tags           map[string]TagEventList