77 "encoding/base64"
88 "fmt"
99 "os"
10+ "regexp"
11+ "strings"
1012 "time"
1113
1214 "github.com/k0sproject/dig"
4143 Version = "v0.0.0"
4244 ImageOverride = ""
4345 MigrationsImageOverride = ""
46+ CounterRegex = regexp .MustCompile (`(\d+)/(\d+)` )
4447)
4548
4649// protectedFields are helm values that are not overwritten when upgrading the addon.
@@ -207,6 +210,99 @@ func (a *AdminConsole) GetAdditionalImages() []string {
207210 return nil
208211}
209212
213+ // MaskKotsOutputForOnline masks the kots cli output during online installations. For
214+ // online installations we only want to print "Finalizing" until it is done and then
215+ // print "Finished!".
216+ func (a * AdminConsole ) MaskKotsOutputForOnline () spinner.MaskFn {
217+ return func (message string ) string {
218+ if strings .Contains (message , "Finished" ) {
219+ return message
220+ }
221+ return "Finalizing"
222+ }
223+ }
224+
225+ // MaskKotsOutputForAirgap masks the kots cli output during airgap installations. This
226+ // function replaces some of the messages being printed to the user so the output looks
227+ // nicer.
228+ func (a * AdminConsole ) MaskKotsOutputForAirgap () spinner.MaskFn {
229+ current := "Uploading air gap bundle"
230+ return func (message string ) string {
231+ switch {
232+ case strings .Contains (message , "Pushing application images" ):
233+ current = message
234+ case strings .Contains (message , "Pushing embedded cluster artifacts" ):
235+ current = message
236+ case strings .Contains (message , "Waiting for Admin Console" ):
237+ current = "Finalizing"
238+ case strings .Contains (message , "Finished!" ):
239+ current = message
240+ }
241+ return current
242+ }
243+ }
244+
245+ // KostsOutputLineBreaker creates a line break (new spinner) when some of the messages
246+ // are printed to the user. For example: after finishing all image uploads we want to
247+ // have a new spinner for the artifacts upload.
248+ func (a * AdminConsole ) KostsOutputLineBreaker () spinner.LineBreakerFn {
249+ // finished is an auxiliary function that evaluates if a message refers to a
250+ // step that has been finished. We determine that by inspected if the message
251+ // contains %d/%d and both integers are equal.
252+ finished := func (message string ) bool {
253+ matches := CounterRegex .FindStringSubmatch (message )
254+ if len (matches ) != 3 {
255+ return false
256+ }
257+ var counter int
258+ if _ , err := fmt .Sscanf (matches [1 ], "%d" , & counter ); err != nil {
259+ return false
260+ }
261+ var total int
262+ if _ , err := fmt .Sscanf (matches [2 ], "%d" , & total ); err != nil {
263+ return false
264+ }
265+ return counter == total
266+ }
267+
268+ var previous string
269+ var seen = map [string ]bool {}
270+ return func (current string ) (bool , string ) {
271+ defer func () {
272+ previous = current
273+ }()
274+
275+ // if we have already seen this message we certainly have already assessed
276+ // if a break line as necessary or not, on this case we return false so we
277+ // do not keep breaking lines indefinitely.
278+ if _ , ok := seen [current ]; ok {
279+ return false , ""
280+ }
281+ seen [current ] = true
282+
283+ // if the previous message evaluated does not relate to an end of a process
284+ // we don't want to break the line. i.e. we only want to break the line when
285+ // the previous evaluated message contains %d/%d and both integers are equal.
286+ if ! finished (previous ) {
287+ return false , ""
288+ }
289+
290+ // if we are printing a message about pushing the embedded cluster artifacts
291+ // it means that we have finished with the images and we want to break the line.
292+ if strings .Contains (current , "Pushing embedded cluster artifacts" ) {
293+ return true , "Application images are ready!"
294+ }
295+
296+ // if we are printing a message about the finalization of the installation it
297+ // means that the embedded cluster artifacts are ready and we want to break the
298+ // line.
299+ if current == "Finalizing" {
300+ return true , "Embedded cluster artifacts are ready!"
301+ }
302+ return false , ""
303+ }
304+ }
305+
210306// Outro waits for the adminconsole to be ready.
211307func (a * AdminConsole ) Outro (ctx context.Context , cli client.Client ) error {
212308 loading := spinner .Start ()
@@ -244,28 +340,27 @@ func (a *AdminConsole) Outro(ctx context.Context, cli client.Client) error {
244340 return fmt .Errorf ("error waiting for admin console: %v" , lasterr )
245341 }
246342
343+ loading .Closef ("Admin Console is ready!" )
247344 if a .licenseFile == "" {
248- loading .Closef ("Admin Console is ready!" )
249345 return nil
250346 }
251347
252- loading .Infof ("Finalizing" )
253-
254348 kotsBinPath , err := goods .MaterializeInternalBinary ("kubectl-kots" )
255349 if err != nil {
256- loading .Close ()
257350 return fmt .Errorf ("unable to materialize kubectl-kots binary: %w" , err )
258351 }
259352 defer os .Remove (kotsBinPath )
260353
261354 license , err := helpers .ParseLicense (a .licenseFile )
262355 if err != nil {
356+ loading .CloseWithError ()
263357 return fmt .Errorf ("unable to parse license: %w" , err )
264358 }
265359
266360 var appVersionLabel string
267361 var channelSlug string
268362 if channelRelease , err := release .GetChannelRelease (); err != nil {
363+ loading .CloseWithError ()
269364 return fmt .Errorf ("unable to get channel release: %w" , err )
270365 } else if channelRelease != nil {
271366 appVersionLabel = channelRelease .VersionLabel
@@ -277,6 +372,8 @@ func (a *AdminConsole) Outro(ctx context.Context, cli client.Client) error {
277372 upstreamURI = fmt .Sprintf ("%s/%s" , upstreamURI , channelSlug )
278373 }
279374
375+ var lbreakfn spinner.LineBreakerFn
376+ maskfn := a .MaskKotsOutputForOnline ()
280377 installArgs := []string {
281378 "install" ,
282379 upstreamURI ,
@@ -290,14 +387,17 @@ func (a *AdminConsole) Outro(ctx context.Context, cli client.Client) error {
290387 }
291388 if a .airgapBundle != "" {
292389 installArgs = append (installArgs , "--airgap-bundle" , a .airgapBundle )
390+ maskfn = a .MaskKotsOutputForAirgap ()
391+ lbreakfn = a .KostsOutputLineBreaker ()
293392 }
294393
295- if _ , err := helpers .RunCommand (kotsBinPath , installArgs ... ); err != nil {
296- loading .Close ()
394+ loading = spinner .Start (spinner .WithMask (maskfn ), spinner .WithLineBreaker (lbreakfn ))
395+ if err := helpers .RunCommandWithWriter (loading , kotsBinPath , installArgs ... ); err != nil {
396+ loading .CloseWithError ()
297397 return fmt .Errorf ("unable to install the application: %w" , err )
298398 }
299399
300- loading .Closef ("Admin Console is ready !" )
400+ loading .Closef ("Finished !" )
301401 a .printSuccessMessage (license .Spec .AppSlug )
302402 return nil
303403}
0 commit comments