forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
image_change_controller.go
152 lines (136 loc) · 5.4 KB
/
image_change_controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package controller
import (
"fmt"
"strings"
"github.com/golang/glog"
kerrors "k8s.io/kubernetes/pkg/api/errors"
kapi "k8s.io/kubernetes/pkg/api"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
buildapi "github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
buildutil "github.com/openshift/origin/pkg/build/util"
oscache "github.com/openshift/origin/pkg/client/cache"
imageapi "github.com/openshift/origin/pkg/image/api"
)
// ImageChangeController watches for changes to ImageRepositories and triggers
// builds when a new version of a tag referenced by a BuildConfig
// is available.
type ImageChangeController struct {
BuildConfigIndex oscache.StoreToBuildConfigLister
BuildConfigInstantiator buildclient.BuildConfigInstantiator
}
// getImageStreamNameFromReference strips off the :tag or @id suffix
// from an ImageStream[Tag,Image,''].Name
func getImageStreamNameFromReference(ref *kapi.ObjectReference) string {
name := strings.Split(ref.Name, ":")[0]
return strings.Split(name, "@")[0]
}
// HandleImageStream processes the next ImageStream event.
func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream) error {
glog.V(4).Infof("Build image change controller detected ImageStream change %s", stream.Status.DockerImageRepository)
// Loop through all build configurations and record if there was an error
// instead of breaking the loop. The error will be returned in the end, so the
// retry controller can retry. Any BuildConfigs that were processed successfully
// should have had their LastTriggeredImageID updated, so the retry should result
// in a no-op for them.
hasError := false
bcs, err := c.BuildConfigIndex.GetConfigsForImageStreamTrigger(stream.Namespace, stream.Name)
if err != nil {
return err
}
for _, config := range bcs {
var (
from *kapi.ObjectReference
shouldBuild = false
triggeredImage = ""
latest *imageapi.TagEvent
)
// For every ImageChange trigger find the latest tagged image from the image stream and
// invoke a build using that image id. A new build is triggered only if the latest tagged image id or pull spec
// differs from the last triggered build recorded on the build config for that trigger
for _, trigger := range config.Spec.Triggers {
if trigger.Type != buildapi.ImageChangeBuildTriggerType {
continue
}
if trigger.ImageChange.From != nil {
from = trigger.ImageChange.From
} else {
from = buildutil.GetInputReference(config.Spec.Strategy)
}
if from == nil || from.Kind != "ImageStreamTag" {
continue
}
fromStreamName, tag, ok := imageapi.SplitImageStreamTag(from.Name)
if !ok {
glog.Errorf("Invalid image stream tag: %s in build config %s/%s", from.Name, config.Name, config.Namespace)
continue
}
fromNamespace := from.Namespace
if len(fromNamespace) == 0 {
fromNamespace = config.Namespace
}
// only trigger a build if this image stream matches the name and namespace of the stream ref in the build trigger
// also do not trigger if the imagerepo does not have a valid DockerImageRepository value for us to pull
// the image from
if len(stream.Status.DockerImageRepository) == 0 || fromStreamName != stream.Name || fromNamespace != stream.Namespace {
continue
}
// This split is safe because ImageStreamTag names always have the form
// name:tag.
latest = imageapi.LatestTaggedImage(stream, tag)
if latest == nil {
glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", stream.Namespace, stream.Name, tag)
continue
}
glog.V(4).Infof("Found ImageStream %s/%s with tag %s", stream.Namespace, stream.Name, tag)
// (must be different) to trigger a build
last := trigger.ImageChange.LastTriggeredImageID
next := latest.DockerImageReference
if len(last) == 0 || (len(next) > 0 && next != last) {
triggeredImage = next
shouldBuild = true
// it doesn't really make sense to have multiple image change triggers any more,
// so just exit the loop now
break
}
}
if shouldBuild {
glog.V(4).Infof("Running build for BuildConfig %s/%s", config.Namespace, config.Name)
buildCauses := []buildapi.BuildTriggerCause{}
// instantiate new build
request := &buildapi.BuildRequest{
ObjectMeta: kapi.ObjectMeta{
Name: config.Name,
Namespace: config.Namespace,
},
TriggeredBy: append(buildCauses,
buildapi.BuildTriggerCause{
Message: "Image change",
ImageChangeBuild: &buildapi.ImageChangeCause{
ImageID: latest.DockerImageReference,
FromRef: from,
},
},
),
TriggeredByImage: &kapi.ObjectReference{
Kind: "DockerImage",
Name: triggeredImage,
},
From: from,
}
if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil {
if kerrors.IsConflict(err) {
utilruntime.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", config.Namespace, config.Name, err))
} else {
utilruntime.HandleError(fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", config.Namespace, config.Name, err))
}
hasError = true
continue
}
}
}
if hasError {
return fmt.Errorf("an error occurred processing 1 or more build configurations; the image change trigger for image stream %s will be retried", stream.Status.DockerImageRepository)
}
return nil
}