@@ -32,12 +32,14 @@ type RunCommand struct {
32
32
logTail * tail.Tail // follow container logs
33
33
tailLogsStarted map [string ]bool // controls tail instance per container
34
34
35
- buildName string // build name
35
+ logLock sync.Mutex
36
+
37
+ buildName string
36
38
buildRunName string
39
+ namespace string
37
40
buildRunSpec * buildv1alpha1.BuildRunSpec // stores command-line flags
38
41
shpClientset buildclientset.Interface
39
42
follow bool // flag to tail pod logs
40
- watchLock sync.Mutex
41
43
}
42
44
43
45
const buildRunLongDesc = `
@@ -53,7 +55,7 @@ func (r *RunCommand) Cmd() *cobra.Command {
53
55
}
54
56
55
57
// Complete picks the build resource name from arguments, and instantiate additional components.
56
- func (r * RunCommand ) Complete (params * params.Params , args []string ) error {
58
+ func (r * RunCommand ) Complete (params * params.Params , io * genericclioptions. IOStreams , args []string ) error {
57
59
switch len (args ) {
58
60
case 1 :
59
61
r .buildName = args [0 ]
@@ -66,6 +68,31 @@ func (r *RunCommand) Complete(params *params.Params, args []string) error {
66
68
return err
67
69
}
68
70
r .logTail = tail .NewTail (r .Cmd ().Context (), clientset )
71
+ r .ioStreams = io
72
+ r .namespace = params .Namespace ()
73
+ if r .follow {
74
+ if r .shpClientset , err = params .ShipwrightClientSet (); err != nil {
75
+ return err
76
+ }
77
+
78
+ kclientset , err := params .ClientSet ()
79
+ if err != nil {
80
+ return err
81
+ }
82
+ to , err := params .RequestTimeout ()
83
+ if err != nil {
84
+ return err
85
+ }
86
+ r .pw , err = reactor .NewPodWatcher (r .Cmd ().Context (), to , kclientset , params .Namespace ())
87
+ if err != nil {
88
+ return err
89
+ }
90
+
91
+ r .pw .WithOnPodModifiedFn (r .onEvent )
92
+ r .pw .WithTimeoutPodFn (r .onTimeout )
93
+ r .pw .WithNoPodEventsYetFn (r .onNoPodEventsYet )
94
+
95
+ }
69
96
70
97
// overwriting build-ref name to use what's on arguments
71
98
return r .Cmd ().Flags ().Set (flags .BuildrefNameFlag , r .buildName )
@@ -92,11 +119,49 @@ func (r *RunCommand) tailLogs(pod *corev1.Pod) {
92
119
}
93
120
}
94
121
122
+ // onNoPodEventsYet reacts to the pod watcher telling us it has not received any pod events for our build run
123
+ func (r * RunCommand ) onNoPodEventsYet () {
124
+ r .Log (fmt .Sprintf ("BuildRun %q log following has not observed any pod events yet." , r .buildRunName ))
125
+ br , err := r .shpClientset .ShipwrightV1alpha1 ().BuildRuns (r .namespace ).Get (r .cmd .Context (), r .buildRunName , metav1.GetOptions {})
126
+ if err != nil {
127
+ r .Log (fmt .Sprintf ("error accessing BuildRun %q: %s" , r .buildRunName , err .Error ()))
128
+ return
129
+ }
130
+
131
+ c := br .Status .GetCondition (buildv1alpha1 .Succeeded )
132
+ giveUp := false
133
+ msg := ""
134
+ switch {
135
+ case c != nil && c .Status == corev1 .ConditionTrue :
136
+ giveUp = true
137
+ msg = fmt .Sprintf ("BuildRun '%s' has been marked as successful.\n " , br .Name )
138
+ case c != nil && c .Status == corev1 .ConditionFalse :
139
+ giveUp = true
140
+ msg = fmt .Sprintf ("BuildRun '%s' has been marked as failed.\n " , br .Name )
141
+ case br .IsCanceled ():
142
+ giveUp = true
143
+ msg = fmt .Sprintf ("BuildRun '%s' has been canceled.\n " , br .Name )
144
+ case br .DeletionTimestamp != nil :
145
+ giveUp = true
146
+ msg = fmt .Sprintf ("BuildRun '%s' has been deleted.\n " , br .Name )
147
+ case ! br .HasStarted ():
148
+ r .Log (fmt .Sprintf ("BuildRun '%s' has been marked as failed.\n " , br .Name ))
149
+ }
150
+ if giveUp {
151
+ r .Log (msg )
152
+ r .Log (fmt .Sprintf ("exiting 'ship build run --follow' for BuildRun %q" , br .Name ))
153
+ r .stop ()
154
+ }
155
+
156
+ }
157
+
158
+ // onTimeout reacts to either the context or request timeout causing the pod watcher to exit
159
+ func (r * RunCommand ) onTimeout (msg string ) {
160
+ r .Log (fmt .Sprintf ("BuildRun %q log following has stopped because: %q\n " , r .buildRunName , msg ))
161
+ }
162
+
95
163
// onEvent reacts on pod state changes, to start and stop tailing container logs.
96
164
func (r * RunCommand ) onEvent (pod * corev1.Pod ) error {
97
- // found more data races during unit testing with concurrent events coming in
98
- r .watchLock .Lock ()
99
- defer r .watchLock .Unlock ()
100
165
switch pod .Status .Phase {
101
166
case corev1 .PodRunning :
102
167
// graceful time to wait for container start
@@ -118,14 +183,14 @@ func (r *RunCommand) onEvent(pod *corev1.Pod) error {
118
183
err = fmt .Errorf ("build pod '%s' has failed" , pod .GetName ())
119
184
}
120
185
// see if because of deletion or cancelation
121
- fmt . Fprintf ( r . ioStreams . Out , msg )
186
+ r . Log ( msg )
122
187
r .stop ()
123
188
return err
124
189
case corev1 .PodSucceeded :
125
- fmt . Fprintf ( r . ioStreams . Out , "Pod '%s' has succeeded!\n " , pod .GetName ())
190
+ r . Log ( fmt . Sprintf ( "Pod '%s' has succeeded!\n " , pod .GetName () ))
126
191
r .stop ()
127
192
default :
128
- fmt . Fprintf ( r . ioStreams . Out , "Pod '%s' is in state %q...\n " , pod .GetName (), string (pod .Status .Phase ))
193
+ r . Log ( fmt . Sprintf ( "Pod '%s' is in state %q...\n " , pod .GetName (), string (pod .Status .Phase ) ))
129
194
// handle any issues with pulling images that may fail
130
195
for _ , c := range pod .Status .Conditions {
131
196
if c .Type == corev1 .PodInitialized || c .Type == corev1 .ContainersReady {
@@ -146,10 +211,7 @@ func (r *RunCommand) stop() {
146
211
147
212
// Run creates a BuildRun resource based on Build's name informed on arguments.
148
213
func (r * RunCommand ) Run (params * params.Params , ioStreams * genericclioptions.IOStreams ) error {
149
- // ran into some data race conditions during unit test with this starting up, but pod events
150
- // coming in before we completed initialization below
151
- r .watchLock .Lock ()
152
- // resource using GenerateName, which will provice a unique instance
214
+ // resource using GenerateName, which will provide a unique instance
153
215
br := & buildv1alpha1.BuildRun {
154
216
ObjectMeta : metav1.ObjectMeta {
155
217
GenerateName : fmt .Sprintf ("%s-" , r .buildName ),
@@ -162,7 +224,7 @@ func (r *RunCommand) Run(params *params.Params, ioStreams *genericclioptions.IOS
162
224
if err != nil {
163
225
return err
164
226
}
165
- br , err = clientset .ShipwrightV1alpha1 ().BuildRuns (params . Namespace () ).Create (r .cmd .Context (), br , metav1.CreateOptions {})
227
+ br , err = clientset .ShipwrightV1alpha1 ().BuildRuns (r . namespace ).Create (r .cmd .Context (), br , metav1.CreateOptions {})
166
228
if err != nil {
167
229
return err
168
230
}
@@ -172,15 +234,7 @@ func (r *RunCommand) Run(params *params.Params, ioStreams *genericclioptions.IOS
172
234
return nil
173
235
}
174
236
175
- r .ioStreams = ioStreams
176
- kclientset , err := params .ClientSet ()
177
- if err != nil {
178
- return err
179
- }
180
237
r .buildRunName = br .Name
181
- if r .shpClientset , err = params .ShipwrightClientSet (); err != nil {
182
- return err
183
- }
184
238
185
239
// instantiating a pod watcher with a specific label-selector to find the indented pod where the
186
240
// actual build started by this subcommand is being executed, including the randomized buildrun
@@ -190,19 +244,17 @@ func (r *RunCommand) Run(params *params.Params, ioStreams *genericclioptions.IOS
190
244
r .buildName ,
191
245
br .GetName (),
192
246
)}
193
- r .pw , err = reactor .NewPodWatcher (r .Cmd ().Context (), kclientset , listOpts , params .Namespace ())
194
- if err != nil {
195
- return err
196
- }
197
-
198
- r .pw .WithOnPodModifiedFn (r .onEvent )
199
- // cannot defer with unlock up top because r.pw.Start() blocks; but the erroring out above kills the
200
- // cli invocation, so it does not matter
201
- r .watchLock .Unlock ()
202
- _ , err = r .pw .Start ()
247
+ _ , err = r .pw .Start (listOpts )
203
248
return err
204
249
}
205
250
251
+ func (r * RunCommand ) Log (msg string ) {
252
+ // concurrent fmt.Fprintf(r.ioStream.Out...) calls need locking to avoid data races, as we 'write' to the stream
253
+ r .logLock .Lock ()
254
+ defer r .logLock .Unlock ()
255
+ fmt .Fprintf (r .ioStreams .Out , msg )
256
+ }
257
+
206
258
// runCmd instantiate the "build run" sub-command using common BuildRun flags.
207
259
func runCmd () runner.SubCommand {
208
260
cmd := & cobra.Command {
@@ -214,7 +266,7 @@ func runCmd() runner.SubCommand {
214
266
cmd : cmd ,
215
267
buildRunSpec : flags .BuildRunSpecFromFlags (cmd .Flags ()),
216
268
tailLogsStarted : make (map [string ]bool ),
217
- watchLock : sync.Mutex {},
269
+ logLock : sync.Mutex {},
218
270
}
219
271
cmd .Flags ().BoolVarP (& runCommand .follow , "follow" , "F" , runCommand .follow , "Start a build and watch its log until it completes or fails." )
220
272
return runCommand
0 commit comments