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 }