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  }