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 }