Skip to content

Commit c6dd9b0

Browse files
Create a generic trigger controller for image streams
1 parent df749cd commit c6dd9b0

File tree

8 files changed

+1244
-0
lines changed

8 files changed

+1244
-0
lines changed

pkg/image/api/helper.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,10 @@ func ResolveLatestTaggedImage(stream *ImageStream, tag string) (string, bool) {
600600
if latest == nil {
601601
return "", false
602602
}
603+
return ResolveTagReference(stream, tag, latest)
604+
}
603605

606+
func ResolveTagReference(stream *ImageStream, tag string, latest *TagEvent) (string, bool) {
604607
// retrieve spec policy - if not found, we use the latest spec
605608
ref, ok := stream.Spec.Tags[tag]
606609
if !ok {

pkg/image/api/v1/trigger/trigger.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package trigger
2+
3+
// ObjectFieldTrigger links a field on the current object to another object for mutation.
4+
type ObjectFieldTrigger struct {
5+
// from is the object this should trigger from. The kind and name fields must be set.
6+
From ObjectReference `json:"from"`
7+
// fieldPath is a JSONPath string to the field to edit on the object. Required.
8+
FieldPath string `json:"fieldPath"`
9+
// paused is true if this trigger is temporarily disabled. Optional.
10+
Paused bool `json:"paused,omitempty"`
11+
}
12+
13+
// ObjectReference identifies an object by its name and kind.
14+
type ObjectReference struct {
15+
// kind is the referenced object's schema.
16+
Kind string `json:"kind"`
17+
// name is the name of the object.
18+
Name string `json:"name"`
19+
// namespace is the namespace the object is located in. Optional if the object is not
20+
// namespaced, or if left empty on a namespaced object, means the current namespace.
21+
Namespace string `json:"namespace,omitempty"`
22+
// apiVersion is the group and version the type exists in. Optional.
23+
APIVersion string `json:"apiVersion,omitempty"`
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package trigger
2+
3+
import (
4+
"encoding/json"
5+
6+
"k8s.io/client-go/pkg/api/meta"
7+
)
8+
9+
// TODO: WIP not yet updated
10+
func AnnotationTriggerIndex(prefix string, obj interface{}) (string, interface{}, bool, error) {
11+
m, err := meta.Accessor(obj)
12+
if err != nil {
13+
return "", nil, false, err
14+
}
15+
t, ok := m.GetAnnotations()["image.openshift.io/triggers"]
16+
if !ok {
17+
return "", nil, false, nil
18+
}
19+
r := &TriggerCacheEntry{}
20+
if err := json.Unmarshal([]byte(t), &r.Triggers); err != nil {
21+
return "", nil, false, err
22+
}
23+
if len(r.Triggers) == 0 {
24+
return "", nil, false, nil
25+
}
26+
r.Namespace = m.GetNamespace()
27+
key := r.Namespace + "/" + m.GetName()
28+
return key, r, true, nil
29+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package trigger
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
7+
kapi "k8s.io/kubernetes/pkg/api"
8+
kerrors "k8s.io/kubernetes/pkg/api/errors"
9+
"k8s.io/kubernetes/pkg/client/cache"
10+
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
11+
12+
"github.com/golang/glog"
13+
buildapi "github.com/openshift/origin/pkg/build/api"
14+
buildutil "github.com/openshift/origin/pkg/build/util"
15+
imageapi "github.com/openshift/origin/pkg/image/api"
16+
"github.com/openshift/origin/pkg/image/api/v1/trigger"
17+
)
18+
19+
func calculateBuildConfigTriggers(bc *buildapi.BuildConfig, retrieveOperations bool) ([]trigger.ObjectFieldTrigger, []TriggerOperation) {
20+
var triggers []trigger.ObjectFieldTrigger
21+
var operations []TriggerOperation
22+
for _, t := range bc.Spec.Triggers {
23+
if t.ImageChange == nil {
24+
continue
25+
}
26+
var (
27+
fieldPath string
28+
from *kapi.ObjectReference
29+
)
30+
if t.ImageChange.From != nil {
31+
from = t.ImageChange.From
32+
fieldPath = "spec.triggers"
33+
} else {
34+
from = buildutil.GetInputReference(bc.Spec.Strategy)
35+
fieldPath = "spec.strategy.*.from"
36+
}
37+
if from == nil || from.Kind != "ImageStreamTag" || len(from.Name) == 0 {
38+
continue
39+
}
40+
41+
// add a trigger
42+
triggers = append(triggers, trigger.ObjectFieldTrigger{
43+
From: trigger.ObjectReference{
44+
Name: from.Name,
45+
Namespace: from.Namespace,
46+
Kind: from.Kind,
47+
APIVersion: from.APIVersion,
48+
},
49+
FieldPath: fieldPath,
50+
})
51+
52+
// find the current trigger value as an operation if requested
53+
if !retrieveOperations {
54+
continue
55+
}
56+
namespace := from.Namespace
57+
if len(namespace) == 0 {
58+
namespace = bc.Namespace
59+
}
60+
_, tag, ok := imageapi.SplitImageStreamTag(from.Name)
61+
if !ok {
62+
utilruntime.HandleError(fmt.Errorf("unable to trigger image change - tag reference %s on build config %s/%s not valid", from.Name, bc.Namespace, bc.Name))
63+
continue
64+
}
65+
operations = append(operations, TriggerOperation{
66+
Name: from.Name,
67+
Tag: tag,
68+
Ref: t.ImageChange.LastTriggeredImageID,
69+
Namespace: namespace,
70+
RV: 0,
71+
Trigger: &triggers[len(triggers)-1],
72+
})
73+
}
74+
return triggers, operations
75+
}
76+
77+
type buildConfigTriggerIndexer struct {
78+
prefix string
79+
}
80+
81+
func (i buildConfigTriggerIndexer) Index(obj, old interface{}) (string, *TriggerCacheEntry, []TriggerOperation, cache.DeltaType, error) {
82+
var (
83+
triggers []trigger.ObjectFieldTrigger
84+
operations []TriggerOperation
85+
bc *buildapi.BuildConfig
86+
change cache.DeltaType
87+
)
88+
switch {
89+
case obj != nil && old == nil:
90+
// added
91+
bc = obj.(*buildapi.BuildConfig)
92+
triggers, operations = calculateBuildConfigTriggers(bc, true)
93+
change = cache.Added
94+
case old != nil && obj == nil:
95+
// deleted
96+
bc = old.(*buildapi.BuildConfig)
97+
triggers, _ = calculateBuildConfigTriggers(bc, false)
98+
change = cache.Deleted
99+
default:
100+
// updated
101+
bc = obj.(*buildapi.BuildConfig)
102+
triggers, operations = calculateBuildConfigTriggers(bc, true)
103+
oldTriggers, _ := calculateBuildConfigTriggers(old.(*buildapi.BuildConfig), false)
104+
switch {
105+
case len(oldTriggers) == 0:
106+
change = cache.Added
107+
case !reflect.DeepEqual(oldTriggers, triggers):
108+
change = cache.Updated
109+
}
110+
}
111+
112+
if len(triggers) > 0 {
113+
key := i.prefix + bc.Namespace + "/" + bc.Name
114+
return key, &TriggerCacheEntry{
115+
Key: key,
116+
Namespace: bc.Namespace,
117+
Triggers: triggers,
118+
}, operations, change, nil
119+
}
120+
return "", nil, nil, change, nil
121+
}
122+
123+
type BuildConfigInstantiator interface {
124+
Instantiate(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error)
125+
}
126+
127+
type BuildConfigReaction struct {
128+
Instantiator BuildConfigInstantiator
129+
}
130+
131+
func (r *BuildConfigReaction) ImageChanged(obj interface{}, operations []TriggerOperation) error {
132+
bc := obj.(*buildapi.BuildConfig)
133+
var (
134+
changed bool
135+
id string
136+
from *kapi.ObjectReference
137+
ref string
138+
)
139+
for _, t := range bc.Spec.Triggers {
140+
if t.ImageChange == nil {
141+
continue
142+
}
143+
for _, operation := range operations {
144+
switch operation.Trigger.FieldPath {
145+
case "spec.strategy.*.from":
146+
if t.ImageChange.From != nil {
147+
continue
148+
}
149+
case "spec.triggers":
150+
if t.ImageChange.From == nil || t.ImageChange.From.Name != operation.Name || t.ImageChange.From.Namespace != operation.Namespace {
151+
continue
152+
}
153+
}
154+
if t.ImageChange.LastTriggeredImageID != operation.Ref {
155+
changed = true
156+
//id = operation.ID
157+
ref = operation.Ref
158+
from = t.ImageChange.From
159+
if from == nil {
160+
from = buildutil.GetInputReference(bc.Spec.Strategy)
161+
}
162+
copied := *from
163+
from = &copied
164+
}
165+
break
166+
}
167+
}
168+
169+
if !changed {
170+
return nil
171+
}
172+
173+
// instantiate new build
174+
glog.V(4).Infof("Running build for BuildConfig %s/%s", bc.Namespace, bc.Name)
175+
request := &buildapi.BuildRequest{
176+
ObjectMeta: kapi.ObjectMeta{
177+
Name: bc.Name,
178+
Namespace: bc.Namespace,
179+
},
180+
TriggeredBy: []buildapi.BuildTriggerCause{
181+
{
182+
Message: buildapi.BuildTriggerCauseImageMsg,
183+
ImageChangeBuild: &buildapi.ImageChangeCause{
184+
ImageID: id,
185+
FromRef: from,
186+
},
187+
},
188+
},
189+
TriggeredByImage: &kapi.ObjectReference{
190+
Kind: "DockerImage",
191+
Name: ref,
192+
},
193+
From: from,
194+
}
195+
if _, err := r.Instantiator.Instantiate(bc.Namespace, request); err != nil {
196+
if kerrors.IsConflict(err) {
197+
utilruntime.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", bc.Namespace, bc.Name, err))
198+
} else {
199+
utilruntime.HandleError(fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", bc.Namespace, bc.Name, err))
200+
}
201+
return err
202+
}
203+
204+
return nil
205+
}

0 commit comments

Comments
 (0)