github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/aws/metadata.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  
     9  	awssdk "github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/session"
    11  
    12  	typesaws "github.com/openshift/installer/pkg/types/aws"
    13  )
    14  
    15  // Metadata holds additional metadata for InstallConfig resources that
    16  // does not need to be user-supplied (e.g. because it can be retrieved
    17  // from external APIs).
    18  type Metadata struct {
    19  	session           *session.Session
    20  	availabilityZones []string
    21  	edgeZones         []string
    22  	privateSubnets    Subnets
    23  	publicSubnets     Subnets
    24  	edgeSubnets       Subnets
    25  	vpc               string
    26  	instanceTypes     map[string]InstanceType
    27  
    28  	Region   string                     `json:"region,omitempty"`
    29  	Subnets  []string                   `json:"subnets,omitempty"`
    30  	Services []typesaws.ServiceEndpoint `json:"services,omitempty"`
    31  
    32  	mutex sync.Mutex
    33  }
    34  
    35  // NewMetadata initializes a new Metadata object.
    36  func NewMetadata(region string, subnets []string, services []typesaws.ServiceEndpoint) *Metadata {
    37  	return &Metadata{Region: region, Subnets: subnets, Services: services}
    38  }
    39  
    40  // Session holds an AWS session which can be used for AWS API calls
    41  // during asset generation.
    42  func (m *Metadata) Session(ctx context.Context) (*session.Session, error) {
    43  	m.mutex.Lock()
    44  	defer m.mutex.Unlock()
    45  
    46  	return m.unlockedSession(ctx)
    47  }
    48  
    49  func (m *Metadata) unlockedSession(ctx context.Context) (*session.Session, error) {
    50  	if m.session == nil {
    51  		var err error
    52  		m.session, err = GetSessionWithOptions(WithRegion(m.Region), WithServiceEndpoints(m.Region, m.Services))
    53  		if err != nil {
    54  			return nil, fmt.Errorf("creating AWS session: %w", err)
    55  		}
    56  	}
    57  
    58  	return m.session, nil
    59  }
    60  
    61  // AvailabilityZones retrieves a list of availability zones for the configured region.
    62  func (m *Metadata) AvailabilityZones(ctx context.Context) ([]string, error) {
    63  	m.mutex.Lock()
    64  	defer m.mutex.Unlock()
    65  
    66  	if len(m.availabilityZones) == 0 {
    67  		session, err := m.unlockedSession(ctx)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		m.availabilityZones, err = availabilityZones(ctx, session, m.Region)
    72  		if err != nil {
    73  			return nil, fmt.Errorf("error retrieving Availability Zones: %w", err)
    74  		}
    75  	}
    76  
    77  	return m.availabilityZones, nil
    78  }
    79  
    80  // EdgeZones retrieves a list of Local and Wavelength zones for the configured region.
    81  func (m *Metadata) EdgeZones(ctx context.Context) ([]string, error) {
    82  	m.mutex.Lock()
    83  	defer m.mutex.Unlock()
    84  
    85  	if len(m.edgeZones) == 0 {
    86  		session, err := m.unlockedSession(ctx)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  
    91  		m.edgeZones, err = edgeZones(ctx, session, m.Region)
    92  		if err != nil {
    93  			return nil, fmt.Errorf("getting Local Zones: %w", err)
    94  		}
    95  	}
    96  
    97  	return m.edgeZones, nil
    98  }
    99  
   100  // EdgeSubnets retrieves subnet metadata indexed by subnet ID, for
   101  // subnets that the cloud-provider logic considers to be edge
   102  // (i.e. Local Zone).
   103  func (m *Metadata) EdgeSubnets(ctx context.Context) (Subnets, error) {
   104  	err := m.populateSubnets(ctx)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("error retrieving Edge Subnets: %w", err)
   107  	}
   108  	return m.edgeSubnets, nil
   109  }
   110  
   111  // SetZoneAttributes retrieves AWS Zone attributes and update required fields in zones.
   112  func (m *Metadata) SetZoneAttributes(ctx context.Context, zoneNames []string, zones Zones) error {
   113  	sess, err := m.Session(ctx)
   114  	if err != nil {
   115  		return fmt.Errorf("unable to get aws session to populate zone details: %w", err)
   116  	}
   117  	azs, err := describeFilteredZones(ctx, sess, m.Region, zoneNames)
   118  	if err != nil {
   119  		return fmt.Errorf("unable to filter zones: %w", err)
   120  	}
   121  
   122  	for _, az := range azs {
   123  		zoneName := awssdk.StringValue(az.ZoneName)
   124  		if _, ok := zones[zoneName]; !ok {
   125  			zones[zoneName] = &Zone{Name: zoneName}
   126  		}
   127  		if zones[zoneName].GroupName == "" {
   128  			zones[zoneName].GroupName = awssdk.StringValue(az.GroupName)
   129  		}
   130  		if zones[zoneName].Type == "" {
   131  			zones[zoneName].Type = awssdk.StringValue(az.ZoneType)
   132  		}
   133  		if az.ParentZoneName != nil {
   134  			zones[zoneName].ParentZoneName = awssdk.StringValue(az.ParentZoneName)
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  // AllZones return all the zones and it's attributes available on the region.
   141  func (m *Metadata) AllZones(ctx context.Context) (Zones, error) {
   142  	sess, err := m.Session(ctx)
   143  	if err != nil {
   144  		return nil, fmt.Errorf("unable to get aws session to populate zone details: %w", err)
   145  	}
   146  	azs, err := describeAvailabilityZones(ctx, sess, m.Region, []string{})
   147  	if err != nil {
   148  		return nil, fmt.Errorf("unable to gather availability zones: %w", err)
   149  	}
   150  	zoneDesc := make(Zones, len(azs))
   151  	for _, az := range azs {
   152  		zoneName := awssdk.StringValue(az.ZoneName)
   153  		zoneDesc[zoneName] = &Zone{
   154  			Name:      zoneName,
   155  			GroupName: awssdk.StringValue(az.GroupName),
   156  			Type:      awssdk.StringValue(az.ZoneType),
   157  		}
   158  		if az.ParentZoneName != nil {
   159  			zoneDesc[zoneName].ParentZoneName = awssdk.StringValue(az.ParentZoneName)
   160  		}
   161  	}
   162  	return zoneDesc, nil
   163  }
   164  
   165  // PrivateSubnets retrieves subnet metadata indexed by subnet ID, for
   166  // subnets that the cloud-provider logic considers to be private
   167  // (i.e. not public).
   168  func (m *Metadata) PrivateSubnets(ctx context.Context) (Subnets, error) {
   169  	err := m.populateSubnets(ctx)
   170  	if err != nil {
   171  		return nil, fmt.Errorf("error retrieving Private Subnets: %w", err)
   172  	}
   173  	return m.privateSubnets, nil
   174  }
   175  
   176  // PublicSubnets retrieves subnet metadata indexed by subnet ID, for
   177  // subnets that the cloud-provider logic considers to be public
   178  // (e.g. with suitable routing for hosting public load balancers).
   179  func (m *Metadata) PublicSubnets(ctx context.Context) (Subnets, error) {
   180  	err := m.populateSubnets(ctx)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("error retrieving Public Subnets: %w", err)
   183  	}
   184  	return m.publicSubnets, nil
   185  }
   186  
   187  // VPC retrieves the VPC ID containing PublicSubnets and PrivateSubnets.
   188  func (m *Metadata) VPC(ctx context.Context) (string, error) {
   189  	err := m.populateSubnets(ctx)
   190  	if err != nil {
   191  		return "", fmt.Errorf("error retrieving VPC: %w", err)
   192  	}
   193  	return m.vpc, nil
   194  }
   195  
   196  func (m *Metadata) populateSubnets(ctx context.Context) error {
   197  	m.mutex.Lock()
   198  	defer m.mutex.Unlock()
   199  
   200  	if len(m.Subnets) == 0 {
   201  		return errors.New("no subnets configured")
   202  	}
   203  
   204  	if m.vpc != "" || len(m.privateSubnets) > 0 || len(m.publicSubnets) > 0 || len(m.edgeSubnets) > 0 {
   205  		// Call to populate subnets has already happened
   206  		return nil
   207  	}
   208  
   209  	session, err := m.unlockedSession(ctx)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	sb, err := subnets(ctx, session, m.Region, m.Subnets)
   215  	m.vpc = sb.VPC
   216  	m.privateSubnets = sb.Private
   217  	m.publicSubnets = sb.Public
   218  	m.edgeSubnets = sb.Edge
   219  	return err
   220  }
   221  
   222  // InstanceTypes retrieves instance type metadata indexed by InstanceType for the configured region.
   223  func (m *Metadata) InstanceTypes(ctx context.Context) (map[string]InstanceType, error) {
   224  	m.mutex.Lock()
   225  	defer m.mutex.Unlock()
   226  
   227  	if len(m.instanceTypes) == 0 {
   228  		session, err := m.unlockedSession(ctx)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  
   233  		m.instanceTypes, err = instanceTypes(ctx, session, m.Region)
   234  		if err != nil {
   235  			return nil, fmt.Errorf("error listing instance types: %w", err)
   236  		}
   237  	}
   238  
   239  	return m.instanceTypes, nil
   240  }