| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package cloud | |
| import ( | |
| "context" | |
| "fmt" | |
| "strconv" | |
| "time" | |
| "github.com/hashicorp/go-tfe" | |
| "github.com/opentofu/opentofu/internal/backend" | |
| "github.com/opentofu/opentofu/internal/command/views" | |
| ) | |
| // IntegrationOutputWriter is an interface used to write output tailored for | |
| // Terraform Cloud integrations | |
| type IntegrationOutputWriter interface { | |
| End() | |
| OutputElapsed(message string, maxMessage int) | |
| Output(str string) | |
| SubOutput(str string) | |
| } | |
| // IntegrationContext is a set of data that is useful when performing Terraform Cloud integration operations | |
| type IntegrationContext struct { | |
| B *Cloud | |
| StopContext context.Context | |
| CancelContext context.Context | |
| Op *backend.Operation | |
| Run *tfe.Run | |
| } | |
| // integrationCLIOutput implements IntegrationOutputWriter | |
| type integrationCLIOutput struct { | |
| View views.BackendRemote | |
| started time.Time | |
| } | |
| var _ IntegrationOutputWriter = (*integrationCLIOutput)(nil) // Compile time check | |
| func (s *IntegrationContext) Poll(backoffMinInterval float64, backoffMaxInterval float64, every func(i int) (bool, error)) error { | |
| for i := 0; ; i++ { | |
| select { | |
| case <-s.StopContext.Done(): | |
| return s.StopContext.Err() | |
| case <-s.CancelContext.Done(): | |
| return s.CancelContext.Err() | |
| case <-time.After(backoff(backoffMinInterval, backoffMaxInterval, i)): | |
| // blocks for a time between min and max | |
| } | |
| cont, err := every(i) | |
| if !cont { | |
| return err | |
| } | |
| } | |
| } | |
| // BeginOutput writes a preamble to the CLI and creates a new IntegrationOutputWriter interface | |
| // to write the remaining CLI output to. Use IntegrationOutputWriter.End() to complete integration | |
| // output | |
| func (s *IntegrationContext) BeginOutput(name string) IntegrationOutputWriter { | |
| var result IntegrationOutputWriter = &integrationCLIOutput{ | |
| View: s.B.View, | |
| started: time.Now(), | |
| } | |
| result.Output("\n[bold]" + name + ":\n") | |
| return result | |
| } | |
| // End writes the termination output for the integration | |
| func (s *integrationCLIOutput) End() { | |
| if s.View == nil { | |
| return | |
| } | |
| s.View.Output("\n------------------------------------------------------------------------\n", false) | |
| } | |
| // Output writes a string after colorizing it using any [colorstrings](https://github.com/mitchellh/colorstring) it contains | |
| func (s *integrationCLIOutput) Output(str string) { | |
| if s.View == nil { | |
| return | |
| } | |
| s.View.Output(str, true) | |
| } | |
| // SubOutput writes a string prefixed by a "│ " after colorizing it using any [colorstrings](https://github.com/mitchellh/colorstring) it contains | |
| func (s *integrationCLIOutput) SubOutput(str string) { | |
| if s.View == nil { | |
| return | |
| } | |
| s.View.Output(fmt.Sprintf("[reset]│ %s", str), true) | |
| } | |
| // OutputElapsed writes a string followed by the amount of time that has elapsed since calling BeginOutput. | |
| // Example pending output; the variable spacing (50 chars) allows up to 99 tasks (two digits) in each category: | |
| // --------------- | |
| // 13 tasks still pending, 0 passed, 0 failed ... | |
| // 13 tasks still pending, 0 passed, 0 failed ... (8s elapsed) | |
| // 13 tasks still pending, 0 passed, 0 failed ... (19s elapsed) | |
| // 13 tasks still pending, 0 passed, 0 failed ... (33s elapsed) | |
| func (s *integrationCLIOutput) OutputElapsed(message string, maxMessage int) { | |
| if s.View == nil { | |
| return | |
| } | |
| elapsed := time.Since(s.started).Truncate(1 * time.Second) | |
| s.View.Output(fmt.Sprintf("%-"+strconv.FormatInt(int64(maxMessage), 10)+"s[dim](%s elapsed)", message, elapsed), true) | |
| } |