forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
image_change_controller.go
122 lines (105 loc) · 4.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
package controller
import (
"fmt"
"github.com/golang/glog"
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
buildapi "github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
imageapi "github.com/openshift/origin/pkg/image/api"
)
type ImageChangeControllerFatalError struct {
Reason string
Err error
}
func (e ImageChangeControllerFatalError) Error() string {
return "fatal error handling ImageStream change: " + e.Reason
}
// 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 {
BuildConfigStore cache.Store
BuildConfigInstantiator buildclient.BuildConfigInstantiator
BuildConfigUpdater buildclient.BuildConfigUpdater
// Stop is an optional channel that controls when the controller exits
Stop <-chan struct{}
}
// HandleImageRepo processes the next ImageStream event.
func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) error {
glog.V(4).Infof("Build image change controller detected imagerepo change %s", repo.Status.DockerImageRepository)
// TODO: this is inefficient
for _, bc := range c.BuildConfigStore.List() {
config := bc.(*buildapi.BuildConfig)
shouldBuild := false
// For every ImageChange trigger find the latest tagged image from the image repository and replace that value
// throughout the build strategies. 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 _, trigger := range config.Triggers {
if trigger.Type != buildapi.ImageChangeBuildTriggerType {
continue
}
change := trigger.ImageChange
changeNamespace := change.From.Namespace
if len(changeNamespace) == 0 {
changeNamespace = config.Namespace
}
// only trigger a build if this image repo matches the name and namespace of the 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(repo.Status.DockerImageRepository) == 0 || change.From.Name != repo.Name || changeNamespace != repo.Namespace {
continue
}
latest, err := imageapi.LatestTaggedImage(repo, change.Tag)
if err != nil {
util.HandleError(fmt.Errorf("unable to find tagged image: %v", err))
continue
}
// (must be different) to trigger a build
last := change.LastTriggeredImageID
next := latest.DockerImageReference
if len(last) == 0 || (len(next) > 0 && next != last) {
change.LastTriggeredImageID = next
shouldBuild = true
}
}
if shouldBuild {
glog.V(4).Infof("Running build for buildConfig %s in namespace %s", config.Name, config.Namespace)
// instantiate new build
request := &buildapi.BuildRequest{ObjectMeta: kapi.ObjectMeta{Name: config.Name}}
if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil {
return fmt.Errorf("Error instantiating build from config %s: %v", config.Name, err)
}
// and update the config
if err := c.updateConfig(config); err != nil {
// This is not a retryable error. The worst case outcome of not updating the buildconfig
// is that we might rerun a build for the same "new" imageid change in the future,
// which is better than guaranteeing we run the build 2+ times by retrying it here.
return ImageChangeControllerFatalError{
Reason: fmt.Sprintf("Error updating buildConfig %s with new LastTriggeredImageID", config.Name),
Err: err,
}
}
}
}
return nil
}
// updateConfig is responsible for updating current BuildConfig object which was changed
// during instantiate call, it basically copies LastTriggeredImageID to fresh copy
// of the BuildConfig object
func (c *ImageChangeController) updateConfig(config *buildapi.BuildConfig) error {
item, _, err := c.BuildConfigStore.Get(config)
newConfig := item.(*buildapi.BuildConfig)
if err != nil {
return err
}
for i, trigger := range newConfig.Triggers {
if trigger.Type != buildapi.ImageChangeBuildTriggerType {
continue
}
change := trigger.ImageChange
change.LastTriggeredImageID = config.Triggers[i].ImageChange.LastTriggeredImageID
}
return c.BuildConfigUpdater.Update(newConfig)
}