-
Notifications
You must be signed in to change notification settings - Fork 25
/
wait.go
240 lines (207 loc) · 8.18 KB
/
wait.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package wait
import (
"context"
"strings"
"time"
"github.com/jenkins-x/go-scm/scm"
jxc "github.com/jenkins-x/jx-api/v3/pkg/client/clientset/versioned"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/helper"
"github.com/jenkins-x/jx-helpers/v3/pkg/kube"
"github.com/jenkins-x/jx-helpers/v3/pkg/kube/jxclient"
"github.com/jenkins-x/jx-helpers/v3/pkg/kube/naming"
"github.com/jenkins-x/jx-helpers/v3/pkg/options"
"github.com/jenkins-x/jx-helpers/v3/pkg/termcolor"
"github.com/jenkins-x/jx-pipeline/pkg/constants"
"github.com/jenkins-x/jx-pipeline/pkg/triggers"
"github.com/jenkins-x/lighthouse/pkg/config"
"github.com/pkg/errors"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"github.com/jenkins-x/jx-helpers/v3/pkg/cobras/templates"
"github.com/jenkins-x/jx-logging/v3/pkg/log"
)
// Options contains the command line options
type Options struct {
options.BaseOptions
WaitDuration time.Duration
PollPeriod time.Duration
Owner string
Repository string
LighthouseConfigMap string
Namespace string
KubeClient kubernetes.Interface
JXClient jxc.Interface
}
var (
info = termcolor.ColorInfo
cmdLong = templates.LongDesc(`
Waits for a pipeline to be imported and activated by the boot Job
`)
cmdExample = templates.Examples(`
# Waits for the pipeline to be setup for the given repository
jx pipeline wait --owner myorg --repo myrepo
`)
)
// NewCmdPipelineWait creates the command
func NewCmdPipelineWait() (*cobra.Command, *Options) {
o := &Options{}
cmd := &cobra.Command{
Use: "wait",
Short: "Waits for a pipeline to be imported and activated by the boot Job",
Long: cmdLong,
Example: cmdExample,
Aliases: []string{"build", "run"},
Run: func(cmd *cobra.Command, args []string) {
err := o.Run()
helper.CheckErr(err)
},
}
cmd.Flags().StringVarP(&o.Owner, "owner", "o", "", "The owner name to wait for")
cmd.Flags().StringVarP(&o.Repository, "repo", "r", "", "The repository name o wait for")
cmd.Flags().StringVarP(&o.LighthouseConfigMap, "configmap", "", constants.LighthouseConfigMapName, "The name of the Lighthouse ConfigMap to find the trigger configurations")
cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", "", "The namespace to look for the lighthouse configuration. Defaults to the current namespace")
cmd.Flags().DurationVarP(&o.WaitDuration, "duration", "", time.Minute*20, "Maximum duration to wait for one or more matching triggers to be setup in Lighthouse. Useful for when a new repository is being imported via GitOps")
cmd.Flags().DurationVarP(&o.PollPeriod, "poll-period", "", time.Second*2, "Poll period when waiting for one or more matching triggers to be setup in Lighthouse. Useful for when a new repository is being imported via GitOps")
o.BaseOptions.AddBaseFlags(cmd)
return cmd, o
}
// Validate verifies things are setup correctly
func (o *Options) Validate() error {
var err error
o.KubeClient, o.Namespace, err = kube.LazyCreateKubeClientAndNamespace(o.KubeClient, o.Namespace)
if err != nil {
return errors.Wrapf(err, "failed to create kube client")
}
o.JXClient, err = jxclient.LazyCreateJXClient(o.JXClient)
if err != nil {
return errors.Wrapf(err, "failed to create jx client")
}
if o.Owner == "" {
return options.MissingOption("owner")
}
if o.Repository == "" {
return options.MissingOption("repo")
}
return nil
}
// Run implements this command
func (o *Options) Run() error {
err := o.Validate()
if err != nil {
return errors.Wrapf(err, "failed to validate options")
}
fullName := scm.Join(o.Owner, o.Repository)
ctx := o.GetContext()
exists, err := o.waitForRepositoryToBeSetup(ctx, o.KubeClient, o.Namespace, fullName)
if err != nil {
return errors.Wrapf(err, "failed to wait for repository to be setup in lighthouse")
}
if !exists {
return errors.Errorf("repository %s is not yet setup in lighthouse", fullName)
}
err = o.waitForWebHookToBeSetup(ctx, o.JXClient, o.Namespace, o.Owner, o.Repository)
if err != nil {
return errors.Wrapf(err, "failed to wait for repository to have its webhook enabled")
}
log.Logger().Infof("the repository %s is now setup in lighthouse and has its webhook enabled", info(fullName))
return nil
}
func (o *Options) waitForRepositoryToBeSetup(ctx context.Context, kubeClient kubernetes.Interface, ns, fullName string) (bool, error) {
end := time.Now().Add(o.WaitDuration)
name := o.LighthouseConfigMap
logWaiting := false
for {
cfg, err := triggers.LoadLighthouseConfig(ctx, kubeClient, ns, name, true)
if err != nil {
return false, errors.Wrapf(err, "failed to load lighthouse config")
}
flag := o.containsRepositoryTrigger(cfg, fullName)
if flag {
return flag, nil
}
if time.Now().After(end) {
log.Logger().Info("")
log.Logger().Warn("It looks like the boot job failed to setup this project.")
log.Logger().Infof("You can view the log via: %s", info("jx admin log"))
return false, errors.Errorf("failed to find trigger in the lighthouse configuration in ConfigMap %s in namespace %s for repository: %s within %s", name, ns, fullName, o.WaitDuration.String())
}
if !logWaiting {
logWaiting = true
log.Logger().Info("")
log.Logger().Infof("waiting up to %s for a trigger to be added to the lighthouse configuration in ConfigMap %s in namespace %s for repository: %s", info(o.WaitDuration.String()), info(name), info(ns), info(fullName))
log.Logger().Infof("you can watch the boot job to update the configuration via: %s", info("jx admin log"))
log.Logger().Info("for more information on how this works see: https://jenkins-x.io/docs/v3/about/how-it-works/#importing--creating-quickstarts")
log.Logger().Info("")
}
time.Sleep(o.PollPeriod)
}
}
func (o *Options) waitForWebHookToBeSetup(ctx context.Context, jxClient jxc.Interface, ns, owner, repository string) error {
end := time.Now().Add(o.WaitDuration)
name := naming.ToValidName(o.Owner + "-" + o.Repository)
logWaiting := false
fullName := scm.Join(owner, repository)
lastValue := ""
found := false
lastFailMessage := ""
for {
sr, err := jxClient.JenkinsV1().SourceRepositories(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
if !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "failed to find SourceRepository %s in namespace %s", name, ns)
}
} else {
if !found {
found = true
log.Logger().Infof("found SourceRepository %s for %s", info(sr.Name), info(sr.Spec.URL))
}
if sr.Annotations == nil {
sr.Annotations = map[string]string{}
}
value := sr.Annotations["webhook.jenkins-x.io"]
if value != "" {
if value != lastValue {
lastValue = value
log.Logger().Infof("webhook status annotation is: %s", info(value))
if value == "true" {
return nil
}
if strings.HasPrefix(strings.ToLower(value), "err") {
failure := sr.Annotations["webhook.jenkins-x.io/error"]
if failure != "" && failure != lastFailMessage {
lastFailMessage = failure
log.Logger().Warnf("when creating webhook: %s", lastFailMessage)
}
}
}
}
}
if time.Now().After(end) {
log.Logger().Info("")
log.Logger().Warn("It looks like the boot job failed to setup the webhooks. It could be related to the git token permissions.")
log.Logger().Infof("You can view the log via: %s", info("jx admin log"))
log.Logger().Info("")
return errors.Errorf("failed to find trigger in the lighthouse configuration in ConfigMap %s in namespace %s for repository: %s within %s", name, ns, fullName, o.WaitDuration.String())
}
if !logWaiting {
logWaiting = true
log.Logger().Infof("waiting up to %s the webhook to be registered for the SourceRepository %s in namespace %s for repository: %s", info(o.WaitDuration.String()), info(name), info(ns), info(fullName))
}
time.Sleep(o.PollPeriod)
}
}
// containsRepositoryTrigger returns true if the trigger is setup for the repository
func (o *Options) containsRepositoryTrigger(cfg *config.Config, fullName string) bool {
if cfg.Postsubmits[fullName] != nil {
return true
}
if cfg.InRepoConfig.Enabled != nil {
f := cfg.InRepoConfig.Enabled[fullName]
if f != nil {
return *f
}
}
return false
}