github.com/openshift/installer@v1.4.17/pkg/terraform/stages/split.go (about)

     1  package stages
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/hashicorp/terraform-exec/tfexec"
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/openshift/installer/pkg/asset"
    13  	"github.com/openshift/installer/pkg/terraform"
    14  	"github.com/openshift/installer/pkg/terraform/providers"
    15  	"github.com/openshift/installer/pkg/types"
    16  )
    17  
    18  // StageOption is an option for configuring a split stage.
    19  type StageOption func(*SplitStage)
    20  
    21  // NewStage creates a new split stage.
    22  // The default behavior is the following. The behavior can be changed by providing StageOptions.
    23  //   - The resources of the stage will not be deleted as part of destroying the bootstrap.
    24  //   - The IP addresses for the bootstrap and control plane VMs will be output from the stage as bootstrap_ip and
    25  //     control_plane_ips, respectively. Only one stage for the platform should output a particular variable. This will
    26  //     likely be the same stage that creates the VM.
    27  func NewStage(platform, name string, providers []providers.Provider, opts ...StageOption) SplitStage {
    28  	s := SplitStage{
    29  		platform:  platform,
    30  		name:      name,
    31  		providers: providers,
    32  	}
    33  	for _, opt := range opts {
    34  		opt(&s)
    35  	}
    36  	return s
    37  }
    38  
    39  // WithNormalBootstrapDestroy returns an option for specifying that a split stage should use the normal bootstrap
    40  // destroy process. The normal process is to fully delete all of the resources created in the stage.
    41  func WithNormalBootstrapDestroy() StageOption {
    42  	return WithCustomBootstrapDestroy(normalDestroy)
    43  }
    44  
    45  // WithCustomBootstrapDestroy returns an option for specifying that a split stage should use a custom bootstrap
    46  // destroy process.
    47  func WithCustomBootstrapDestroy(destroy DestroyFunc) StageOption {
    48  	return func(s *SplitStage) {
    49  		s.destroyWithBootstrap = true
    50  		s.destroy = destroy
    51  	}
    52  }
    53  
    54  // WithCustomExtractHostAddresses returns an option for specifying that a split stage should use a custom extract host addresses process.
    55  func WithCustomExtractHostAddresses(extractHostAddresses ExtractFunc) StageOption {
    56  	return func(s *SplitStage) {
    57  		s.extractHostAddresses = extractHostAddresses
    58  	}
    59  }
    60  
    61  // WithCustomExtractLBConfig returns an option for specifying that a split stage
    62  // should use a custom method to extract load balancer DNS names.
    63  func WithCustomExtractLBConfig(extractLBConfig ExtractLBConfigFunc) StageOption {
    64  	return func(s *SplitStage) {
    65  		s.extractLBConfig = extractLBConfig
    66  	}
    67  }
    68  
    69  // SplitStage is a split stage.
    70  type SplitStage struct {
    71  	platform             string
    72  	name                 string
    73  	providers            []providers.Provider
    74  	destroyWithBootstrap bool
    75  	destroy              DestroyFunc
    76  	extractHostAddresses ExtractFunc
    77  	extractLBConfig      ExtractLBConfigFunc
    78  }
    79  
    80  // DestroyFunc is a function for destroying the stage.
    81  type DestroyFunc func(s SplitStage, directory string, terraformDir string, varFiles []string) error
    82  
    83  // ExtractFunc is a function for extracting host addresses.
    84  type ExtractFunc func(s SplitStage, directory string, ic *types.InstallConfig) (string, int, []string, error)
    85  
    86  // ExtractLBConfigFunc is a function for extracting LB DNS Names.
    87  type ExtractLBConfigFunc func(s SplitStage, directory string, terraformDir string, file *asset.File, tfvarsFile *asset.File) (string, error)
    88  
    89  // Name implements pkg/terraform/Stage.Name
    90  func (s SplitStage) Name() string {
    91  	return s.name
    92  }
    93  
    94  // Providers is the list of providers that are used for the stage.
    95  func (s SplitStage) Providers() []providers.Provider {
    96  	return s.providers
    97  }
    98  
    99  // StateFilename implements pkg/terraform/Stage.StateFilename
   100  func (s SplitStage) StateFilename() string {
   101  	return fmt.Sprintf("terraform.%s.tfstate", s.name)
   102  }
   103  
   104  // OutputsFilename implements pkg/terraform/Stage.OutputsFilename
   105  func (s SplitStage) OutputsFilename() string {
   106  	return fmt.Sprintf("%s.tfvars.json", s.name)
   107  }
   108  
   109  // DestroyWithBootstrap implements pkg/terraform/Stage.DestroyWithBootstrap
   110  func (s SplitStage) DestroyWithBootstrap() bool {
   111  	return s.destroyWithBootstrap
   112  }
   113  
   114  // Destroy implements pkg/terraform/Stage.Destroy
   115  func (s SplitStage) Destroy(directory string, terraformDir string, varFiles []string) error {
   116  	return s.destroy(s, directory, terraformDir, varFiles)
   117  }
   118  
   119  // Platform implements pkg/terraform/Stage.Platform.
   120  func (s SplitStage) Platform() string {
   121  	return s.platform
   122  }
   123  
   124  // ExtractHostAddresses implements pkg/terraform/Stage.ExtractHostAddresses
   125  func (s SplitStage) ExtractHostAddresses(directory string, ic *types.InstallConfig) (string, int, []string, error) {
   126  	if s.extractHostAddresses != nil {
   127  		return s.extractHostAddresses(s, directory, ic)
   128  	}
   129  	return normalExtractHostAddresses(s, directory, ic)
   130  }
   131  
   132  // ExtractLBConfig implements pkg/terraform/Stage.ExtractLBConfig.
   133  func (s SplitStage) ExtractLBConfig(directory string, terraformDir string, file *asset.File, tfvarsFile *asset.File) (string, error) {
   134  	if s.extractLBConfig != nil {
   135  		return s.extractLBConfig(s, directory, terraformDir, file, tfvarsFile)
   136  	}
   137  	return normalExtractLBConfig(s, directory, terraformDir, file, tfvarsFile)
   138  }
   139  
   140  // GetTerraformOutputs reads the terraform outputs file for the stage and parses it into a map of outputs.
   141  func GetTerraformOutputs(s SplitStage, directory string) (map[string]interface{}, error) {
   142  	outputsFilePath := filepath.Join(directory, s.OutputsFilename())
   143  	if _, err := os.Stat(outputsFilePath); err != nil {
   144  		return nil, errors.Wrapf(err, "could not find outputs file %q", outputsFilePath)
   145  	}
   146  
   147  	outputsFile, err := os.ReadFile(outputsFilePath)
   148  	if err != nil {
   149  		return nil, errors.Wrapf(err, "failed to read outputs file %q", outputsFilePath)
   150  	}
   151  
   152  	outputs := map[string]interface{}{}
   153  	if err := json.Unmarshal(outputsFile, &outputs); err != nil {
   154  		return nil, errors.Wrapf(err, "could not unmarshal outputs file %q", outputsFilePath)
   155  	}
   156  
   157  	return outputs, nil
   158  }
   159  
   160  func normalExtractHostAddresses(s SplitStage, directory string, _ *types.InstallConfig) (string, int, []string, error) {
   161  	outputs, err := GetTerraformOutputs(s, directory)
   162  	if err != nil {
   163  		return "", 0, nil, err
   164  	}
   165  
   166  	var bootstrap string
   167  	if bootstrapRaw, ok := outputs["bootstrap_ip"]; ok {
   168  		bootstrap, ok = bootstrapRaw.(string)
   169  		if !ok {
   170  			return "", 0, nil, errors.New("could not read bootstrap IP from terraform outputs")
   171  		}
   172  	}
   173  
   174  	var masters []string
   175  	if mastersRaw, ok := outputs["control_plane_ips"]; ok {
   176  		mastersSlice, ok := mastersRaw.([]interface{})
   177  		if !ok {
   178  			return "", 0, nil, errors.New("could not read control plane IPs from terraform outputs")
   179  		}
   180  		masters = make([]string, len(mastersSlice))
   181  		for i, ipRaw := range mastersSlice {
   182  			ip, ok := ipRaw.(string)
   183  			if !ok {
   184  				return "", 0, nil, errors.New("could not read control plane IPs from terraform outputs")
   185  			}
   186  			masters[i] = ip
   187  		}
   188  	}
   189  
   190  	return bootstrap, 0, masters, nil
   191  }
   192  
   193  func normalDestroy(s SplitStage, directory string, terraformDir string, varFiles []string) error {
   194  	opts := make([]tfexec.DestroyOption, len(varFiles))
   195  	for i, varFile := range varFiles {
   196  		opts[i] = tfexec.VarFile(varFile)
   197  	}
   198  	return errors.Wrap(terraform.Destroy(directory, s.platform, s, terraformDir, opts...), "terraform destroy")
   199  }
   200  
   201  func normalExtractLBConfig(s SplitStage, directory string, terraformDir string, file *asset.File, tfvarsFile *asset.File) (string, error) {
   202  	return "", nil
   203  }