github.com/openshift/installer@v1.4.17/pkg/tfvars/aws/aws.go (about) 1 // Package aws contains AWS-specific Terraform-variable logic. 2 package aws 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "sort" 8 "strings" 9 10 "github.com/pkg/errors" 11 "github.com/sirupsen/logrus" 12 13 machinev1beta1 "github.com/openshift/api/machine/v1beta1" 14 "github.com/openshift/installer/pkg/asset/ignition/bootstrap" 15 icaws "github.com/openshift/installer/pkg/asset/installconfig/aws" 16 "github.com/openshift/installer/pkg/types" 17 typesaws "github.com/openshift/installer/pkg/types/aws" 18 ) 19 20 // Config contains the AWS platform data for terraform. 21 type Config struct { 22 AMI string `json:"aws_ami"` 23 AMIRegion string `json:"aws_ami_region"` 24 CustomEndpoints map[string]string `json:"custom_endpoints,omitempty"` 25 ExtraTags map[string]string `json:"aws_extra_tags,omitempty"` 26 BootstrapInstanceType string `json:"aws_bootstrap_instance_type,omitempty"` 27 MasterInstanceType string `json:"aws_master_instance_type,omitempty"` 28 MasterAvailabilityZones []string `json:"aws_master_availability_zones"` 29 WorkerAvailabilityZones []string `json:"aws_worker_availability_zones"` 30 EdgeLocalZones []string `json:"aws_edge_local_zones,omitempty"` 31 EdgeZonesGatewayIndex map[string]int `json:"aws_edge_parent_zones_index,omitempty"` 32 EdgeZonesType map[string]string `json:"aws_edge_zones_type,omitempty"` 33 IOPS int64 `json:"aws_master_root_volume_iops"` 34 Size int64 `json:"aws_master_root_volume_size,omitempty"` 35 Type string `json:"aws_master_root_volume_type,omitempty"` 36 Encrypted bool `json:"aws_master_root_volume_encrypted"` 37 KMSKeyID string `json:"aws_master_root_volume_kms_key_id,omitempty"` 38 Region string `json:"aws_region,omitempty"` 39 VPC string `json:"aws_vpc,omitempty"` 40 PrivateSubnets []string `json:"aws_private_subnets,omitempty"` 41 PublicSubnets *[]string `json:"aws_public_subnets,omitempty"` 42 InternalZone string `json:"aws_internal_zone,omitempty"` 43 InternalZoneRole string `json:"aws_internal_zone_role,omitempty"` 44 PublishStrategy string `json:"aws_publish_strategy,omitempty"` 45 IgnitionBucket string `json:"aws_ignition_bucket"` 46 BootstrapIgnitionStub string `json:"aws_bootstrap_stub_ignition"` 47 MasterIAMRoleName string `json:"aws_master_iam_role_name,omitempty"` 48 WorkerIAMRoleName string `json:"aws_worker_iam_role_name,omitempty"` 49 MasterMetadataAuthentication string `json:"aws_master_instance_metadata_authentication,omitempty"` 50 BootstrapMetadataAuthentication string `json:"aws_bootstrap_instance_metadata_authentication,omitempty"` 51 PreserveBootstrapIgnition bool `json:"aws_preserve_bootstrap_ignition"` 52 MasterSecurityGroups []string `json:"aws_master_security_groups,omitempty"` 53 PublicIpv4Pool string `json:"aws_public_ipv4_pool"` 54 MasterUseSpotInstance bool `json:"aws_master_use_spot_instance,omitempty"` 55 } 56 57 // TFVarsSources contains the parameters to be converted into Terraform variables 58 type TFVarsSources struct { 59 VPC string 60 PrivateSubnets, PublicSubnets []string 61 InternalZone, InternalZoneRole string 62 Services []typesaws.ServiceEndpoint 63 AvailabilityZones icaws.Zones 64 65 Publish types.PublishingStrategy 66 67 AMIID, AMIRegion string 68 69 MasterConfigs, WorkerConfigs []*machinev1beta1.AWSMachineProviderConfig 70 71 IgnitionBucket, IgnitionPresignedURL string 72 73 AdditionalTrustBundle string 74 75 MasterIAMRoleName, WorkerIAMRoleName string 76 77 MasterMetadataAuthentication string 78 79 Architecture types.Architecture 80 81 Proxy *types.Proxy 82 83 PreserveBootstrapIgnition bool 84 85 MasterSecurityGroups []string 86 87 PublicIpv4Pool string 88 } 89 90 // TFVars generates AWS-specific Terraform variables launching the cluster. 91 func TFVars(sources TFVarsSources) ([]byte, error) { 92 masterConfig := sources.MasterConfigs[0] 93 94 endpoints := make(map[string]string) 95 for _, service := range sources.Services { 96 service := service 97 endpoints[service.Name] = service.URL 98 } 99 100 tags := make(map[string]string, len(masterConfig.Tags)) 101 for _, tag := range masterConfig.Tags { 102 tags[tag.Name] = tag.Value 103 } 104 105 exists := struct{}{} 106 allAvailabilityZonesMap := map[string]struct{}{} 107 masterAvailabilityZones := make([]string, len(sources.MasterConfigs)) 108 for i, c := range sources.MasterConfigs { 109 masterAvailabilityZones[i] = c.Placement.AvailabilityZone 110 allAvailabilityZonesMap[c.Placement.AvailabilityZone] = exists 111 } 112 113 availabilityZoneMap := map[string]struct{}{} 114 edgeLocalZoneMap := map[string]struct{}{} 115 for _, c := range sources.WorkerConfigs { 116 zoneName := c.Placement.AvailabilityZone 117 if _, ok := sources.AvailabilityZones[zoneName]; !ok { 118 return nil, errors.New(fmt.Sprintf("unable to find the zone when generating terraform vars: %s", zoneName)) 119 } 120 if sources.AvailabilityZones[zoneName].Type == typesaws.LocalZoneType || 121 sources.AvailabilityZones[zoneName].Type == typesaws.WavelengthZoneType { 122 edgeLocalZoneMap[zoneName] = exists 123 continue 124 } 125 availabilityZoneMap[zoneName] = exists 126 allAvailabilityZonesMap[zoneName] = exists 127 } 128 129 workerAvailabilityZones := make([]string, 0, len(availabilityZoneMap)) 130 for zone := range availabilityZoneMap { 131 workerAvailabilityZones = append(workerAvailabilityZones, zone) 132 } 133 134 allAvailabilityZones := make([]string, 0, len(allAvailabilityZonesMap)) 135 for zone := range allAvailabilityZonesMap { 136 allAvailabilityZones = append(allAvailabilityZones, zone) 137 } 138 139 // Create map for edge zone and parent's zone index. 140 // AWS Local Zones does not support private Nat Gateways, to egress internet 141 // traffic from the zone, so the parent's zone route table will be 142 // used to associate private subnets created in the edge zones. 143 // The allAvailabilityZones holds all Availability Zone type (in the Region) 144 // for the cluster, where the terraform creates network resources 145 // (NAT Gateway). The index of that list will be used to determine the 146 // parent's zone route table ID, when exists, otherwise the default 147 // private route table will be used. 148 // TODO(when Local Zone supports Nat Gateway): create private route table 149 // by Local Zone location. 150 sort.Strings(allAvailabilityZones) 151 edgeLocalZones := make([]string, 0, len(edgeLocalZoneMap)) 152 edgeZonesGatewayIndexMap := make(map[string]int, len(edgeLocalZoneMap)) 153 edgeZonesType := make(map[string]string, len(edgeLocalZoneMap)) 154 // new VPC 155 if len(sources.PrivateSubnets) == 0 { 156 for zone := range edgeLocalZoneMap { 157 parent := sources.AvailabilityZones[zone].ParentZoneName 158 gwIndex := 0 159 for idx, az := range allAvailabilityZones { 160 if az == parent { 161 gwIndex = idx 162 break 163 } 164 } 165 edgeLocalZones = append(edgeLocalZones, zone) 166 edgeZonesGatewayIndexMap[zone] = gwIndex 167 edgeZonesType[zone] = sources.AvailabilityZones[zone].Type 168 } 169 } 170 171 if len(masterConfig.BlockDevices) == 0 { 172 return nil, errors.New("block device slice cannot be empty") 173 } 174 175 rootVolume := masterConfig.BlockDevices[0] 176 if rootVolume.EBS == nil { 177 return nil, errors.New("EBS information must be configured for the root volume") 178 } 179 180 if rootVolume.EBS.VolumeType == nil { 181 return nil, errors.New("EBS volume type must be configured for the root volume") 182 } 183 184 if rootVolume.EBS.VolumeSize == nil { 185 return nil, errors.New("EBS volume size must be configured for the root volume") 186 } 187 188 if *rootVolume.EBS.VolumeType == "io1" && rootVolume.EBS.Iops == nil { 189 return nil, errors.New("EBS IOPS must be configured for the io1 root volume") 190 } 191 192 useSpotInstances := masterConfig.SpotMarketOptions != nil 193 if useSpotInstances { 194 logrus.Warn("Found Spot instance configuration. Please be warned, this is not advised.") 195 } 196 197 cfg := &Config{ 198 CustomEndpoints: endpoints, 199 Region: masterConfig.Placement.Region, 200 ExtraTags: tags, 201 MasterAvailabilityZones: masterAvailabilityZones, 202 WorkerAvailabilityZones: workerAvailabilityZones, 203 EdgeLocalZones: edgeLocalZones, 204 EdgeZonesGatewayIndex: edgeZonesGatewayIndexMap, 205 EdgeZonesType: edgeZonesType, 206 BootstrapInstanceType: masterConfig.InstanceType, 207 MasterInstanceType: masterConfig.InstanceType, 208 Size: *rootVolume.EBS.VolumeSize, 209 Type: *rootVolume.EBS.VolumeType, 210 VPC: sources.VPC, 211 PrivateSubnets: sources.PrivateSubnets, 212 InternalZone: sources.InternalZone, 213 InternalZoneRole: sources.InternalZoneRole, 214 PublishStrategy: string(sources.Publish), 215 IgnitionBucket: sources.IgnitionBucket, 216 MasterIAMRoleName: sources.MasterIAMRoleName, 217 WorkerIAMRoleName: sources.WorkerIAMRoleName, 218 PreserveBootstrapIgnition: sources.PreserveBootstrapIgnition, 219 MasterSecurityGroups: sources.MasterSecurityGroups, 220 PublicIpv4Pool: sources.PublicIpv4Pool, 221 MasterUseSpotInstance: useSpotInstances, 222 } 223 224 stubIgn, err := bootstrap.GenerateIgnitionShimWithCertBundleAndProxy(sources.IgnitionPresignedURL, sources.AdditionalTrustBundle, sources.Proxy) 225 if err != nil { 226 return nil, errors.Wrap(err, "failed to create stub Ignition config for bootstrap") 227 } 228 229 // Check the size of the raw ignition stub is less than 16KB for aws user-data 230 // see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-add-user-data.html 231 if len(stubIgn) > 16000 { 232 return nil, fmt.Errorf("rendered bootstrap ignition shim exceeds the 16KB limit for AWS user data -- try reducing the size of your CA cert bundle") 233 } 234 cfg.BootstrapIgnitionStub = string(stubIgn) 235 236 if len(sources.PublicSubnets) == 0 { 237 if cfg.VPC != "" { 238 cfg.PublicSubnets = &[]string{} 239 } 240 } else { 241 cfg.PublicSubnets = &sources.PublicSubnets 242 } 243 244 if rootVolume.EBS.Iops != nil { 245 cfg.IOPS = *rootVolume.EBS.Iops 246 } 247 248 cfg.Encrypted = true 249 if rootVolume.EBS.Encrypted != nil { 250 cfg.Encrypted = *rootVolume.EBS.Encrypted 251 } 252 if rootVolume.EBS.KMSKey.ID != nil && *rootVolume.EBS.KMSKey.ID != "" { 253 cfg.KMSKeyID = *rootVolume.EBS.KMSKey.ID 254 } else if rootVolume.EBS.KMSKey.ARN != nil && *rootVolume.EBS.KMSKey.ARN != "" { 255 cfg.KMSKeyID = *rootVolume.EBS.KMSKey.ARN 256 } 257 258 if masterConfig.AMI.ID != nil && *masterConfig.AMI.ID != "" { 259 cfg.AMI = *masterConfig.AMI.ID 260 cfg.AMIRegion = masterConfig.Placement.Region 261 } else { 262 cfg.AMI = sources.AMIID 263 cfg.AMIRegion = sources.AMIRegion 264 } 265 266 if masterConfig.MetadataServiceOptions.Authentication != "" { 267 cfg.MasterMetadataAuthentication = strings.ToLower(string(masterConfig.MetadataServiceOptions.Authentication)) 268 cfg.BootstrapMetadataAuthentication = cfg.MasterMetadataAuthentication 269 } 270 271 return json.MarshalIndent(cfg, "", " ") 272 }