-
Notifications
You must be signed in to change notification settings - Fork 6
/
fn.go
137 lines (121 loc) · 4.19 KB
/
fn.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
package main
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/logging"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/response"
"github.com/crossplane/function-sequencer/input/v1beta1"
)
// Function returns whatever response you ask it to.
type Function struct {
fnv1beta1.UnimplementedFunctionRunnerServiceServer
log logging.Logger
}
const (
// START marks the start of a regex pattern.
START = "^"
// END marks the end of a regex pattern.
END = "$"
)
// RunFunction runs the Function.
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) { //nolint:gocyclo // This function is unavoidably complex.
f.log.Info("Running function", "tag", req.GetMeta().GetTag())
rsp := response.To(req, response.DefaultTTL)
in := &v1beta1.Input{}
if err := request.GetInput(req, in); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
return rsp, nil
}
// Get the desired composed resources from the request.
desiredComposed, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composed resources"))
return rsp, nil
}
observedComposed, err := request.GetObservedComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot get observed composed resources"))
return rsp, nil
}
sequences := make([][]resource.Name, 0, len(in.Rules))
for _, rule := range in.Rules {
sequences = append(sequences, rule.Sequence)
}
for _, sequence := range sequences {
for i, r := range sequence {
if i == 0 {
// We don't need to do anything for the first resource in the sequence.
continue
}
if _, created := observedComposed[r]; created {
// We've already created this resource, so we don't need to do anything.
// We only sequence creation of resources that don't exist yet.
continue
}
for _, before := range sequence[:i] {
beforeRegex, err := getStrictRegex(string(before))
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", before))
return rsp, nil
}
keys := []resource.Name{}
for k := range desiredComposed {
if beforeRegex.MatchString(string(k)) {
keys = append(keys, k)
}
}
// We'll treat everything the same way adding all resources to the keys slice
// and then checking if they are ready.
desired := len(keys)
readyResources := 0
for _, k := range keys {
if b, ok := desiredComposed[k]; ok && b.Ready == resource.ReadyTrue {
// resource is ready, add it to the counter
readyResources++
}
}
if desired == 0 || desired != readyResources {
// no resource created
msg := fmt.Sprintf("Delaying creation of resource(s) matching %q because %q does not exist yet", r, before)
if desired > 0 {
// provide a nicer message if there are resources.
msg = fmt.Sprintf(
"Delaying creation of resource(s) matching %q because %q is not fully ready (%d of %d)",
r,
before,
readyResources,
desired,
)
}
response.Normal(rsp, msg)
f.log.Info(msg)
// find all objects that match the regex and delete them from the desiredComposed map
currentRegex, _ := getStrictRegex(string(r))
for k := range desiredComposed {
if currentRegex.MatchString(string(k)) {
delete(desiredComposed, k)
}
}
break
}
}
}
}
rsp.Desired.Resources = nil
return rsp, response.SetDesiredComposedResources(rsp, desiredComposed)
}
func getStrictRegex(pattern string) (*regexp.Regexp, error) {
if !strings.HasPrefix(pattern, START) && !strings.HasSuffix(pattern, END) {
// if the user provides a delimited regex, we'll use it as is
// if not, add the regex with ^ & $ to match the entire string
// possibly avoid using regex for matching literal strings
pattern = fmt.Sprintf("%s%s%s", START, pattern, END)
}
return regexp.Compile(pattern)
}