github.com/opentofu/opentofu@v1.7.1/internal/cloud/cloud_integration.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package cloud 7 8 import ( 9 "context" 10 "fmt" 11 "strconv" 12 "time" 13 14 "github.com/hashicorp/go-tfe" 15 "github.com/mitchellh/cli" 16 "github.com/opentofu/opentofu/internal/backend" 17 ) 18 19 // IntegrationOutputWriter is an interface used to to write output tailored for 20 // Terraform Cloud integrations 21 type IntegrationOutputWriter interface { 22 End() 23 OutputElapsed(message string, maxMessage int) 24 Output(str string) 25 SubOutput(str string) 26 } 27 28 // IntegrationContext is a set of data that is useful when performing Terraform Cloud integration operations 29 type IntegrationContext struct { 30 B *Cloud 31 StopContext context.Context 32 CancelContext context.Context 33 Op *backend.Operation 34 Run *tfe.Run 35 } 36 37 // integrationCLIOutput implements IntegrationOutputWriter 38 type integrationCLIOutput struct { 39 CLI cli.Ui 40 Colorizer Colorer 41 started time.Time 42 } 43 44 var _ IntegrationOutputWriter = (*integrationCLIOutput)(nil) // Compile time check 45 46 func (s *IntegrationContext) Poll(backoffMinInterval float64, backoffMaxInterval float64, every func(i int) (bool, error)) error { 47 for i := 0; ; i++ { 48 select { 49 case <-s.StopContext.Done(): 50 return s.StopContext.Err() 51 case <-s.CancelContext.Done(): 52 return s.CancelContext.Err() 53 case <-time.After(backoff(backoffMinInterval, backoffMaxInterval, i)): 54 // blocks for a time between min and max 55 } 56 57 cont, err := every(i) 58 if !cont { 59 return err 60 } 61 } 62 } 63 64 // BeginOutput writes a preamble to the CLI and creates a new IntegrationOutputWriter interface 65 // to write the remaining CLI output to. Use IntegrationOutputWriter.End() to complete integration 66 // output 67 func (s *IntegrationContext) BeginOutput(name string) IntegrationOutputWriter { 68 var result IntegrationOutputWriter = &integrationCLIOutput{ 69 CLI: s.B.CLI, 70 Colorizer: s.B.Colorize(), 71 started: time.Now(), 72 } 73 74 result.Output("\n[bold]" + name + ":\n") 75 76 return result 77 } 78 79 // End writes the termination output for the integration 80 func (s *integrationCLIOutput) End() { 81 if s.CLI == nil { 82 return 83 } 84 85 s.CLI.Output("\n------------------------------------------------------------------------\n") 86 } 87 88 // Output writes a string after colorizing it using any [colorstrings](https://github.com/mitchellh/colorstring) it contains 89 func (s *integrationCLIOutput) Output(str string) { 90 if s.CLI == nil { 91 return 92 } 93 s.CLI.Output(s.Colorizer.Color(str)) 94 } 95 96 // SubOutput writes a string prefixed by a "│ " after colorizing it using any [colorstrings](https://github.com/mitchellh/colorstring) it contains 97 func (s *integrationCLIOutput) SubOutput(str string) { 98 if s.CLI == nil { 99 return 100 } 101 s.CLI.Output(s.Colorizer.Color(fmt.Sprintf("[reset]│ %s", str))) 102 } 103 104 // OutputElapsed writes a string followed by the amount of time that has elapsed since calling BeginOutput. 105 // Example pending output; the variable spacing (50 chars) allows up to 99 tasks (two digits) in each category: 106 // --------------- 107 // 13 tasks still pending, 0 passed, 0 failed ... 108 // 13 tasks still pending, 0 passed, 0 failed ... (8s elapsed) 109 // 13 tasks still pending, 0 passed, 0 failed ... (19s elapsed) 110 // 13 tasks still pending, 0 passed, 0 failed ... (33s elapsed) 111 func (s *integrationCLIOutput) OutputElapsed(message string, maxMessage int) { 112 if s.CLI == nil { 113 return 114 } 115 elapsed := time.Since(s.started).Truncate(1 * time.Second) 116 s.CLI.Output(fmt.Sprintf("%-"+strconv.FormatInt(int64(maxMessage), 10)+"s", message) + s.Colorizer.Color(fmt.Sprintf("[dim](%s elapsed)", elapsed))) 117 }