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  }