1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-05 06:46:36 +01:00

CORS-4078: centralize aws client constructions

Following session pattern in v1 sdk, we centralize the construction of
v2 sdk aws.Config and API clients for better reusability.

To remain backwards compatible, the following is in place:
- New config and client constructs are in sessionv2.go  while we
  gradually migrate parts of the installer code.
- EndpointResolver v1 is kept around to resolve partition ID of a
  region.
- Service ID in v1 is converted to v2 equivalent internally. The user
  can continue to use existing values while we migrate the sdk.

Notes:
- Default service endpoints for CN regions are not handled by the v2
  endpoint resolver.
This commit is contained in:
Thuan Vo
2025-08-12 10:35:19 -07:00
parent 4e1ef8345b
commit 96e6c5e8a5
11 changed files with 570 additions and 250 deletions

View File

@@ -4,23 +4,20 @@ package aws
import (
"context"
"fmt"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
configv2 "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/iam"
iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go-v2/service/route53"
r53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/openshift/installer/pkg/asset/installconfig"
awsconfig "github.com/openshift/installer/pkg/asset/installconfig/aws"
"github.com/openshift/installer/pkg/types"
awstypes "github.com/openshift/installer/pkg/types/aws"
)
@@ -87,19 +84,13 @@ func tagSharedVPCResources(ctx context.Context, clusterID string, installConfig
tagKey, tagValue := sharedTag(clusterID)
cfg, err := configv2.LoadDefaultConfig(ctx, configv2.WithRegion(installConfig.Config.Platform.AWS.Region))
if err != nil {
return fmt.Errorf("failed to load AWS config: %w", err)
}
ec2Client := ec2.NewFromConfig(cfg, func(options *ec2.Options) {
options.Region = installConfig.Config.Platform.AWS.Region
for _, endpoint := range installConfig.Config.AWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, "ec2") {
options.BaseEndpoint = aws.String(endpoint.URL)
}
}
ec2Client, err := awsconfig.NewEC2Client(ctx, awsconfig.EndpointOptions{
Region: installConfig.Config.Platform.AWS.Region,
Endpoints: installConfig.Config.Platform.AWS.ServiceEndpoints,
})
if err != nil {
return fmt.Errorf("failed to create EC2 client: %w", err)
}
if _, err = ec2Client.CreateTags(ctx, &ec2.CreateTagsInput{
Resources: ids,
@@ -109,23 +100,15 @@ func tagSharedVPCResources(ctx context.Context, clusterID string, installConfig
}
if zone := installConfig.Config.AWS.HostedZone; zone != "" {
if installConfig.Config.AWS.HostedZoneRole != "" {
stsSvc := sts.NewFromConfig(cfg)
creds := stscreds.NewAssumeRoleProvider(stsSvc, installConfig.Config.AWS.HostedZoneRole)
// The credentials for this config are set after the other uses. In the event that more
// clients use the config, a new config should be created.
cfg.Credentials = aws.NewCredentialsCache(creds)
roleArn := installConfig.Config.AWS.HostedZoneRole
route53Client, err := awsconfig.NewRoute53Client(ctx, awsconfig.EndpointOptions{
Region: installConfig.Config.Platform.AWS.Region,
Endpoints: installConfig.Config.Platform.AWS.ServiceEndpoints,
}, roleArn)
if err != nil {
return fmt.Errorf("failed to create Route 53 client: %w", err)
}
route53Client := route53.NewFromConfig(cfg, func(options *route53.Options) {
options.Region = installConfig.Config.Platform.AWS.Region
for _, endpoint := range installConfig.Config.AWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, "route53") {
options.BaseEndpoint = aws.String(endpoint.URL)
}
}
})
if _, err := route53Client.ChangeTagsForResource(ctx, &route53.ChangeTagsForResourceInput{
ResourceType: r53types.TagResourceTypeHostedzone,
ResourceId: aws.String(zone),
@@ -176,19 +159,13 @@ func tagSharedIAMRoles(ctx context.Context, clusterID string, installConfig *ins
tagKey, tagValue := sharedTag(clusterID)
cfg, err := configv2.LoadDefaultConfig(ctx, configv2.WithRegion(installConfig.Config.Platform.AWS.Region))
if err != nil {
return fmt.Errorf("failed to load AWS config: %w", err)
}
iamClient := iam.NewFromConfig(cfg, func(options *iam.Options) {
options.Region = installConfig.Config.Platform.AWS.Region
for _, endpoint := range installConfig.Config.AWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, "iam") {
options.BaseEndpoint = aws.String(endpoint.URL)
}
}
iamClient, err := awsconfig.NewIAMClient(ctx, awsconfig.EndpointOptions{
Region: installConfig.Config.Platform.AWS.Region,
Endpoints: installConfig.Config.Platform.AWS.ServiceEndpoints,
})
if err != nil {
return fmt.Errorf("failed to create IAM client: %w", err)
}
for role := range iamRoles {
if _, err := iamClient.TagRole(ctx, &iam.TagRoleInput{
@@ -244,19 +221,13 @@ func tagSharedIAMProfiles(ctx context.Context, clusterID string, installConfig *
logrus.Debugf("Tagging shared instance profiles: %v", sets.List(iamProfileNames))
cfg, err := configv2.LoadDefaultConfig(ctx, configv2.WithRegion(installConfig.Config.Platform.AWS.Region))
if err != nil {
return fmt.Errorf("failed to load AWS config: %w", err)
}
iamClient := iam.NewFromConfig(cfg, func(options *iam.Options) {
options.Region = installConfig.Config.Platform.AWS.Region
for _, endpoint := range installConfig.Config.AWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, "iam") {
options.BaseEndpoint = aws.String(endpoint.URL)
}
}
iamClient, err := awsconfig.NewIAMClient(ctx, awsconfig.EndpointOptions{
Region: installConfig.Config.Platform.AWS.Region,
Endpoints: installConfig.Config.Platform.AWS.ServiceEndpoints,
})
if err != nil {
return fmt.Errorf("failed to create IAM client: %w", err)
}
tagKey, tagValue := sharedTag(clusterID)
for name := range iamProfileNames {

View File

@@ -0,0 +1,98 @@
package aws
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/route53"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
// NewEC2Client creates a new EC2 API client.
func NewEC2Client(ctx context.Context, endpointOpts EndpointOptions, optFns ...func(*ec2.Options)) (*ec2.Client, error) {
cfg, err := GetConfigWithOptions(ctx, config.WithRegion(endpointOpts.Region))
if err != nil {
return nil, err
}
ec2Opts := []func(*ec2.Options){
func(o *ec2.Options) {
o.EndpointResolverV2 = &EC2EndpointResolver{
ServiceEndpointResolver: NewServiceEndpointResolver(endpointOpts),
}
},
}
ec2Opts = append(ec2Opts, optFns...)
return ec2.NewFromConfig(cfg, ec2Opts...), nil
}
// NewIAMClient creates a new IAM API client.
func NewIAMClient(ctx context.Context, endpointOpts EndpointOptions, optFns ...func(*iam.Options)) (*iam.Client, error) {
cfg, err := GetConfigWithOptions(ctx, config.WithRegion(endpointOpts.Region))
if err != nil {
return nil, err
}
iamOpts := []func(*iam.Options){
func(o *iam.Options) {
o.EndpointResolverV2 = &IAMEndpointResolver{
ServiceEndpointResolver: NewServiceEndpointResolver(endpointOpts),
}
},
}
iamOpts = append(iamOpts, optFns...)
return iam.NewFromConfig(cfg, iamOpts...), nil
}
// NewRoute53Client creates a new Route 53 API client.
func NewRoute53Client(ctx context.Context, endpointOpts EndpointOptions, roleArn string, optFns ...func(*route53.Options)) (*route53.Client, error) {
cfg, err := GetConfigWithOptions(ctx, config.WithRegion(endpointOpts.Region))
if err != nil {
return nil, err
}
if len(roleArn) > 0 {
stsClient, err := NewSTSClient(ctx, endpointOpts)
if err != nil {
return nil, err
}
creds := stscreds.NewAssumeRoleProvider(stsClient, roleArn)
cfg.Credentials = aws.NewCredentialsCache(creds)
}
route53Opts := []func(*route53.Options){
func(o *route53.Options) {
o.EndpointResolverV2 = &Route53EndpointResolver{
ServiceEndpointResolver: NewServiceEndpointResolver(endpointOpts),
}
},
}
route53Opts = append(route53Opts, optFns...)
return route53.NewFromConfig(cfg, route53Opts...), nil
}
// NewSTSClient creates a new STS API client.
func NewSTSClient(ctx context.Context, endpointOpts EndpointOptions, optFns ...func(*sts.Options)) (*sts.Client, error) {
cfg, err := GetConfigWithOptions(ctx, config.WithRegion(endpointOpts.Region))
if err != nil {
return nil, err
}
stsOpts := []func(*sts.Options){
func(o *sts.Options) {
o.EndpointResolverV2 = &STSEndpointResolver{
ServiceEndpointResolver: NewServiceEndpointResolver(endpointOpts),
}
},
}
stsOpts = append(stsOpts, optFns...)
return sts.NewFromConfig(cfg, stsOpts...), nil
}

View File

@@ -5,46 +5,23 @@ import (
"fmt"
"time"
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
cfgv2 "github.com/aws/aws-sdk-go-v2/config"
ec2v2 "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
)
func setUserCredsInConfig(ctx context.Context) (awsv2.Config, error) {
if err := getUserCredentials(); err != nil {
return awsv2.Config{}, err
}
cfg, err := cfgv2.LoadDefaultConfig(ctx, cfgv2.WithRegion("us-east-1"))
if err != nil {
return awsv2.Config{}, fmt.Errorf("failed to create AWS config: %w", err)
}
return cfg, nil
}
// GetRegions get all regions that are accessible.
func GetRegions(ctx context.Context) ([]string, error) {
// Create a basic/default config. The function is currently called during the survey.
// Pass the default region (used for survey purposes) as the region here. Without a region
// the DescribeRegions call will fail immediately.
cfg, err := cfgv2.LoadDefaultConfig(ctx, cfgv2.WithRegion("us-east-1"))
client, err := NewEC2Client(ctx, EndpointOptions{
// Pass the default region (used for survey purposes) as the region here. Without a region
// the DescribeRegions call will fail immediately.
Region: "us-east-1",
})
if err != nil {
cfg, err = setUserCredsInConfig(ctx)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("failed to create EC2 client: %w", err)
}
if _, err = cfg.Credentials.Retrieve(ctx); err != nil {
cfg, err = setUserCredsInConfig(ctx)
if err != nil {
return nil, err
}
}
client := ec2v2.NewFromConfig(cfg)
output, err := client.DescribeRegions(ctx, &ec2v2.DescribeRegionsInput{AllRegions: aws.Bool(true)})
if err != nil {
return nil, fmt.Errorf("failed to get all regions: %w", err)

View File

@@ -1,87 +0,0 @@
package aws
import (
"fmt"
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
ec2v2 "github.com/aws/aws-sdk-go-v2/service/ec2"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
iamv2 "github.com/aws/aws-sdk-go-v2/service/iam"
tagsv2 "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
route53v2 "github.com/aws/aws-sdk-go-v2/service/route53"
s3v2 "github.com/aws/aws-sdk-go-v2/service/s3"
stsv2 "github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/sirupsen/logrus"
)
type endpointOptionInterface interface {
GetResolvedRegion() string
GetDisableHTTPS() bool
GetUseDualStackEndpoint() awsv2.DualStackEndpointState
GetUseFIPSEndpoint() awsv2.FIPSEndpointState
}
type endpointOptions struct {
ResolvedRegion string
DisableHTTPS bool
UseDualStackEndpoint awsv2.DualStackEndpointState
UseFIPSEndpoint awsv2.FIPSEndpointState
}
func (e endpointOptions) GetResolvedRegion() string {
return e.ResolvedRegion
}
func (e endpointOptions) GetDisableHTTPS() bool { return e.DisableHTTPS }
func (e endpointOptions) GetUseDualStackEndpoint() awsv2.DualStackEndpointState {
return e.UseDualStackEndpoint
}
func (e endpointOptions) GetUseFIPSEndpoint() awsv2.FIPSEndpointState {
return e.UseFIPSEndpoint
}
// getDefaultServiceEndpoint will get the default service endpoint for a service and region.
func getDefaultServiceEndpoint(region, service string, opts endpointOptions) (*awsv2.Endpoint, error) { //nolint: staticcheck
var err error
var endpoint awsv2.Endpoint //nolint: staticcheck
var options endpointOptionInterface = opts
switch service {
case "ec2":
resolver := ec2v2.NewDefaultEndpointResolver()
r, _ := options.(ec2v2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "elasticloadbalancing":
resolver := elbv2.NewDefaultEndpointResolver()
r, _ := options.(elbv2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "iam":
resolver := iamv2.NewDefaultEndpointResolver()
r, _ := options.(iamv2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "route53":
resolver := route53v2.NewDefaultEndpointResolver()
r, _ := options.(route53v2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "s3":
resolver := s3v2.NewDefaultEndpointResolver()
r, _ := options.(s3v2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "sts":
resolver := stsv2.NewDefaultEndpointResolver()
r, _ := options.(stsv2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
case "tagging":
resolver := tagsv2.NewDefaultEndpointResolver()
r, _ := options.(tagsv2.EndpointResolverOptions)
endpoint, err = resolver.ResolveEndpoint(region, r)
default:
logrus.Debugf("Could not determine default endpoint for service: %s", service)
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("failed to resolve aws %s service endpoint: %w", service, err)
}
return &endpoint, nil
}

View File

@@ -0,0 +1,227 @@
package aws
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go-v2/service/route53"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/sts"
smithyendpoints "github.com/aws/smithy-go/endpoints"
"github.com/sirupsen/logrus"
typesaws "github.com/openshift/installer/pkg/types/aws"
)
// Partition identifiers.
const (
AwsPartitionID = "aws" // AWS Standard partition.
AwsCnPartitionID = "aws-cn" // AWS China partition.
AwsUsGovPartitionID = "aws-us-gov" // AWS GovCloud (US) partition.
AwsIsoPartitionID = "aws-iso" // AWS ISO (US) partition.
AwsIsoBPartitionID = "aws-iso-b" // AWS ISOB (US) partition.
)
var (
// v1Tov2ServiceIDMap maps v1 service ID to its v2 equivalent.
v1Tov2ServiceIDMap = map[string]string{
"ec2": ec2.ServiceID,
"elasticloadbalancing": elb.ServiceID,
"elasticloadbalancingv2": elbv2.ServiceID,
"iam": iam.ServiceID,
"route53": route53.ServiceID,
"s3": s3.ServiceID,
"sts": sts.ServiceID,
"resourcegroupstaggingapi": resourcegroupstaggingapi.ServiceID,
}
)
// resolveServiceID converts a service ID in the SDK from v1 to v2.
// If the service ID is not recognized, return as-is.
func resolveServiceID(serviceID string) string {
if v2serviceID, ok := v1Tov2ServiceIDMap[serviceID]; ok {
return v2serviceID
}
return serviceID
}
// EndpointOptions defines options to configure the service endpoint resolver.
type EndpointOptions struct {
Endpoints []typesaws.ServiceEndpoint
Region string
UseDualStack bool
UseFIPS bool
}
// ServiceEndpointResolver implements EndpointResolverV2 interface for services.
type ServiceEndpointResolver struct {
// endpoints is the map of provided service endpoints
// indexed by service ID.
endpoints map[string]typesaws.ServiceEndpoint
endpointOptions EndpointOptions
logger logrus.FieldLogger
}
// NewServiceEndpointResolver returns a new ServiceEndpointResolver.
func NewServiceEndpointResolver(opts EndpointOptions) *ServiceEndpointResolver {
endpointMap := make(map[string]typesaws.ServiceEndpoint, len(opts.Endpoints))
for _, endpoint := range opts.Endpoints {
endpointMap[resolveServiceID(endpoint.Name)] = endpoint
}
return &ServiceEndpointResolver{
endpoints: endpointMap,
endpointOptions: opts,
logger: logrus.StandardLogger(),
}
}
// EC2EndpointResolver implements EndpointResolverV2 interface for EC2.
type EC2EndpointResolver struct {
*ServiceEndpointResolver
}
// ResolveEndpoint for EC2.
func (s *EC2EndpointResolver) ResolveEndpoint(ctx context.Context, params ec2.EndpointParameters) (smithyendpoints.Endpoint, error) {
params.UseDualStack = aws.Bool(s.endpointOptions.UseDualStack)
params.UseFIPS = aws.Bool(s.endpointOptions.UseFIPS)
// If custom endpoint not found, return default endpoint for the service.
endpoint, ok := s.endpoints[ec2.ServiceID]
if !ok {
s.logger.Debug("Custom EC2 endpoint not found, using default endpoint")
return ec2.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
s.logger.Debugf("Custom EC2 endpoint found, using custom endpoint: %s", endpoint.URL)
params.Endpoint = aws.String(endpoint.URL)
params.Region = aws.String(s.endpointOptions.Region)
return ec2.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
// IAMEndpointResolver implements EndpointResolverV2 interface for IAM.
type IAMEndpointResolver struct {
*ServiceEndpointResolver
}
// ResolveEndpoint for IAM.
func (s *IAMEndpointResolver) ResolveEndpoint(ctx context.Context, params iam.EndpointParameters) (smithyendpoints.Endpoint, error) {
params.UseDualStack = aws.Bool(s.endpointOptions.UseDualStack)
params.UseFIPS = aws.Bool(s.endpointOptions.UseFIPS)
// If custom endpoint not found, return default endpoint for the service.
endpoint, ok := s.endpoints[iam.ServiceID]
if !ok {
s.logger.Debug("Custom IAM endpoint not found, using default endpoint")
return iam.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
s.logger.Debugf("Custom IAM endpoint found, using custom endpoint: %s", endpoint.URL)
params.Endpoint = aws.String(endpoint.URL)
params.Region = aws.String(s.endpointOptions.Region)
return iam.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
// STSEndpointResolver implements EndpointResolverV2 interface for STS.
type STSEndpointResolver struct {
*ServiceEndpointResolver
}
// ResolveEndpoint for STS.
func (s *STSEndpointResolver) ResolveEndpoint(ctx context.Context, params sts.EndpointParameters) (smithyendpoints.Endpoint, error) {
params.UseDualStack = aws.Bool(s.endpointOptions.UseDualStack)
params.UseFIPS = aws.Bool(s.endpointOptions.UseFIPS)
// If custom endpoint not found, return default endpoint for the service
endpoint, ok := s.endpoints[sts.ServiceID]
if !ok {
s.logger.Debug("Custom STS endpoint not found, using default endpoint")
return sts.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
s.logger.Debugf("Custom STS endpoint found, using custom endpoint: %s", endpoint.URL)
params.Endpoint = aws.String(endpoint.URL)
params.Region = aws.String(s.endpointOptions.Region)
return sts.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
// Route53EndpointResolver implements EndpointResolverV2 interface for Route 53.
type Route53EndpointResolver struct {
*ServiceEndpointResolver
}
// ResolveEndpoint for Route 53.
func (s *Route53EndpointResolver) ResolveEndpoint(ctx context.Context, params route53.EndpointParameters) (smithyendpoints.Endpoint, error) {
params.UseDualStack = aws.Bool(s.endpointOptions.UseDualStack)
params.UseFIPS = aws.Bool(s.endpointOptions.UseFIPS)
// If custom endpoint not found, return default endpoint for the service.
endpoint, ok := s.endpoints[route53.ServiceID]
if !ok {
s.logger.Debug("Custom Route53 endpoint not found, using default endpoint")
return route53.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
s.logger.Debugf("Custom Route53 endpoint found, using custom endpoint: %s", endpoint.URL)
params.Endpoint = aws.String(endpoint.URL)
params.Region = aws.String(s.endpointOptions.Region)
return route53.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}
// GetDefaultServiceEndpoint will get the default service endpoint for a service and region.
// Note: This uses the v1 EndpointResolver, which exposes the partition ID.
func GetDefaultServiceEndpoint(ctx context.Context, service string, opts EndpointOptions) (aws.Endpoint, error) { //nolint: staticcheck
region := opts.Region
useFIPs := aws.FIPSEndpointStateDisabled
if opts.UseFIPS {
useFIPs = aws.FIPSEndpointStateEnabled
}
useDualstack := aws.DualStackEndpointStateDisabled
if opts.UseDualStack {
useDualstack = aws.DualStackEndpointStateEnabled
}
var endpoint aws.Endpoint //nolint: staticcheck
var err error
switch service {
case ec2.ServiceID:
options := ec2.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = ec2.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case elb.ServiceID:
options := elb.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = elb.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case elbv2.ServiceID:
options := elbv2.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = elbv2.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case iam.ServiceID:
options := iam.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = iam.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case route53.ServiceID:
options := route53.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = route53.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case s3.ServiceID:
options := s3.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = s3.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case sts.ServiceID:
options := sts.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = sts.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
case resourcegroupstaggingapi.ServiceID:
options := resourcegroupstaggingapi.EndpointResolverOptions{UseFIPSEndpoint: useFIPs, UseDualStackEndpoint: useDualstack}
endpoint, err = resourcegroupstaggingapi.NewDefaultEndpointResolver().ResolveEndpoint(region, options)
default:
logrus.Debugf("Could not determine default endpoint for unknown service: %s", service)
return endpoint, nil
}
if err != nil {
return endpoint, fmt.Errorf("failed to resolve aws %s service endpoint: %w", service, err)
}
return endpoint, nil
}

View File

@@ -7,12 +7,9 @@ import (
"sync"
"github.com/IBM/ibm-cos-sdk-go/aws"
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
awssdk "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/sirupsen/logrus"
typesaws "github.com/openshift/installer/pkg/types/aws"
)
@@ -22,7 +19,6 @@ import (
// from external APIs).
type Metadata struct {
session *session.Session
config *awsv2.Config
availabilityZones []string
availableRegions []string
edgeZones []string
@@ -66,38 +62,18 @@ func (m *Metadata) unlockedSession(ctx context.Context) (*session.Session, error
return m.session, nil
}
func (m *Metadata) unlockedConfig(ctx context.Context) (*awsv2.Config, error) {
if m.config == nil {
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(m.Region))
if err != nil {
return nil, fmt.Errorf("creating AWS configuration: %w", err)
}
m.config = &cfg
}
return m.config, nil
}
// EC2Client initiates a new EC2 client when one does not already exist, otherwise the existing client
// is returned.
func (m *Metadata) EC2Client(ctx context.Context) (*ec2.Client, error) {
if m.ec2Client == nil {
cfg, err := m.unlockedConfig(ctx)
ec2Client, err := NewEC2Client(ctx, EndpointOptions{
Region: m.Region,
Endpoints: m.Services,
})
if err != nil {
return nil, fmt.Errorf("metadata failed to create config: %w", err)
return nil, fmt.Errorf("failed to create EC2 client: %w", err)
}
optFns := []func(*ec2.Options){}
for _, service := range m.Services {
if service.Name == "ec2" {
optFns = append(optFns, func(o *ec2.Options) {
o.BaseEndpoint = awssdk.String(service.URL)
})
logrus.Warnf("setting ec2 endpoint URL to %s", service.URL)
break
}
}
m.ec2Client = ec2.NewFromConfig(*cfg, optFns...)
m.ec2Client = ec2Client
}
return m.ec2Client, nil
}

View File

@@ -0,0 +1,183 @@
package aws
import (
"context"
"fmt"
"os"
"path/filepath"
"sync"
survey "github.com/AlecAivazis/survey/v2"
"github.com/aws/aws-sdk-go-v2/aws"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/smithy-go/middleware"
"github.com/sirupsen/logrus"
ini "gopkg.in/ini.v1"
"github.com/openshift/installer/pkg/version"
)
const (
// OpenShiftInstallerUserAgent is the User Agent key to add to the AWS API request header.
OpenShiftInstallerUserAgent = "OpenShift/4.x Installer"
// OpenShiftInstallerGatherUserAgent is the User Agent key to add to the AWS API request header
// when gather command is invoked.
OpenShiftInstallerGatherUserAgent = "OpenShift/4.x Gather"
// RetryMaxAttempts is the total number of times an API request is retried.
RetryMaxAttempts = 25
)
var (
credentialsFromConfigLogger = new(sync.Once)
)
// ConfigOptions is a set of functions that modify the provided config.LoadOptions.
type ConfigOptions []func(*config.LoadOptions) error
// getDefaultConfigOptions returns the default settings for config.LoadOptions.
func getDefaultConfigOptions() ConfigOptions {
return ConfigOptions{
config.WithRetryMaxAttempts(RetryMaxAttempts),
config.WithAPIOptions([]func(*middleware.Stack) error{
awsmiddleware.AddUserAgentKeyValue(OpenShiftInstallerUserAgent, version.Raw),
}),
}
}
// GetConfig returns an AWS config by checking credentials
// and, if no creds are found, asks for them and stores them on disk in a config file.
func GetConfig(ctx context.Context) (aws.Config, error) { return GetConfigWithOptions(ctx) }
// GetConfigWithOptions returns an AWS config by checking credentials
// and, if no creds are found, asks for them and stores them on disk in a config file.
func GetConfigWithOptions(ctx context.Context, options ...func(*config.LoadOptions) error) (aws.Config, error) {
// Set the default options, which are overridden by user-defined ones if any.
options = append(getDefaultConfigOptions(), options...)
// Attempt to retrieve valid credentials.
// If failed, ask the user for the credentials via the survey.
_, err := getCredentialsV2(ctx, options)
if err != nil {
if err := getUserCredentialsV2(); err != nil {
return aws.Config{}, err
}
}
cfg, err := config.LoadDefaultConfig(ctx, options...)
if err != nil {
return aws.Config{}, fmt.Errorf("failed to create AWS config: %w", err)
}
return cfg, err
}
// getCredentials returns the credentials by constructing an AWS Config
// and attempting to retrieve the credentials if possible.
// TODO: Remove suffix V2 when completing migration aws sdk v2 (i.e. removing session.go).
func getCredentialsV2(ctx context.Context, options ConfigOptions) (aws.Credentials, error) {
cfg, err := config.LoadDefaultConfig(ctx, options...)
if err != nil {
return aws.Credentials{}, fmt.Errorf("failed to create AWS config: %w", err)
}
creds, err := cfg.Credentials.Retrieve(ctx)
if err != nil {
return aws.Credentials{}, err
}
credentialsFromConfigLogger.Do(func() {
logrus.Infof("Credentials loaded from the AWS config using %q provider", creds.Source)
})
return creds, nil
}
// IsStaticCredentialsV2 returns whether the credentials value provider are
// static credentials safe for installer to transfer to cluster for use as-is.
// TODO: Remove suffix V2 when completing migration aws sdk v2 (i.e. removing session.go).
func IsStaticCredentialsV2(creds aws.Credentials) bool {
if creds.Source == credentials.StaticCredentialsName {
return creds.SessionToken == ""
}
return false
}
// getUserCredentialsV2 asks for aws access key id and secret in the survey
// and stores them on disk in a config file.
// TODO: Remove suffix V2 when completing migration aws sdk v2 (i.e. removing session.go).
func getUserCredentialsV2() error {
var keyID string
err := survey.Ask([]*survey.Question{
{
Prompt: &survey.Input{
Message: "AWS Access Key ID",
Help: "The AWS access key ID to use for installation (this is not your username).\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
},
},
}, &keyID)
if err != nil {
return err
}
var secretKey string
err = survey.Ask([]*survey.Question{
{
Prompt: &survey.Password{
Message: "AWS Secret Access Key",
Help: "The AWS secret access key corresponding to your access key ID (this is not your password).",
},
},
}, &secretKey)
if err != nil {
return err
}
path := config.DefaultSharedCredentialsFilename()
if env := os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); env != "" {
path = env
}
logrus.Infof("Writing AWS credentials to %q (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)", path)
err = os.MkdirAll(filepath.Dir(path), 0700)
if err != nil {
return err
}
creds, err := ini.Load(path)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("failed to load credentials file %s: %w", path, err)
}
creds = ini.Empty()
creds.Section("").Comment = "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html"
}
profile := os.Getenv("AWS_PROFILE")
if profile == "" {
profile = "default"
}
creds.Section(profile).Key("aws_access_key_id").SetValue(keyID)
creds.Section(profile).Key("aws_secret_access_key").SetValue(secretKey)
tempPath := path + ".tmp"
file, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return err
}
defer file.Close()
_, err = creds.WriteTo(file)
if err != nil {
err2 := os.Remove(tempPath)
if err2 != nil {
logrus.Error(fmt.Errorf("failed to remove partially-written credentials file: %w", err2))
}
return err
}
return os.Rename(tempPath, path)
}

View File

@@ -9,7 +9,7 @@ import (
"net/url"
"sort"
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
ec2v2 "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/ec2"
@@ -156,17 +156,14 @@ func validateAMI(ctx context.Context, meta *Metadata, config *types.InstallConfi
return nil
}
// accept AMI that can be copied from us-east-1 if the region is in the standard AWS partition
regions, err := meta.Regions(ctx)
if err != nil {
return field.ErrorList{field.InternalError(field.NewPath("platform", "aws", "region"), fmt.Errorf("failed to get list of regions: %w", err))}
}
if sets.New(regions...).Has(config.Platform.AWS.Region) {
defaultEndpoint, err := getDefaultServiceEndpoint(config.Platform.AWS.Region, "ec2", endpointOptions{
DisableHTTPS: false,
UseDualStackEndpoint: awsv2.DualStackEndpointStateDisabled,
})
if err != nil || defaultEndpoint == nil {
defaultEndpoint, err := GetDefaultServiceEndpoint(ctx, ec2v2.ServiceID, EndpointOptions{Region: config.Platform.AWS.Region, UseFIPS: false})
if err != nil {
return field.ErrorList{field.InternalError(field.NewPath("platform", "aws", "region"), fmt.Errorf("failed to resolve ec2 endpoint"))}
}
if defaultEndpoint.PartitionID == endpoints.AwsPartitionID {

View File

@@ -3,12 +3,11 @@ package aws
import (
"context"
"fmt"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
awsconfig "github.com/openshift/installer/pkg/asset/installconfig/aws"
typesaws "github.com/openshift/installer/pkg/types/aws"
)
@@ -23,21 +22,14 @@ type InstanceTypeInfo struct {
func InstanceTypes(ctx context.Context, region string, serviceEndpoints []typesaws.ServiceEndpoint) (map[string]InstanceTypeInfo, error) {
ret := map[string]InstanceTypeInfo{}
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
client, err := awsconfig.NewEC2Client(ctx, awsconfig.EndpointOptions{
Region: region,
Endpoints: serviceEndpoints,
})
if err != nil {
return ret, fmt.Errorf("failed to create AWS config: %w", err)
return ret, fmt.Errorf("failed to create EC2 client: %w", err)
}
client := ec2.NewFromConfig(cfg,
func(o *ec2.Options) {
for _, endpoint := range serviceEndpoints {
if strings.EqualFold(endpoint.Name, ec2.ServiceID) {
o.BaseEndpoint = aws.String(endpoint.URL)
}
}
},
)
paginator := ec2.NewDescribeInstanceTypesPaginator(client, &ec2.DescribeInstanceTypesInput{})
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)

View File

@@ -6,7 +6,6 @@ import (
"strings"
"time"
configv2 "github.com/aws/aws-sdk-go-v2/config"
ec2v2 "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
@@ -86,19 +85,13 @@ func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (providers.
return nil, err
}
cfg, err := configv2.LoadDefaultConfig(context.TODO(), configv2.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("failed loading default config: %w", err)
}
ec2Client := ec2v2.NewFromConfig(cfg, func(options *ec2v2.Options) {
options.Region = region
for _, endpoint := range metadata.AWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, "ec2") {
options.BaseEndpoint = aws.String(endpoint.URL)
}
}
ec2Client, err := awssession.NewEC2Client(context.TODO(), awssession.EndpointOptions{
Region: region,
Endpoints: metadata.AWS.ServiceEndpoints,
})
if err != nil {
return nil, fmt.Errorf("failed to create EC2 client: %w", err)
}
return &ClusterUninstaller{
Filters: filters,

View File

@@ -10,8 +10,7 @@ import (
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/config"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/smithy-go"
@@ -19,6 +18,7 @@ import (
"github.com/sirupsen/logrus"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
awssession "github.com/openshift/installer/pkg/asset/installconfig/aws"
"github.com/openshift/installer/pkg/gather"
"github.com/openshift/installer/pkg/gather/providers"
"github.com/openshift/installer/pkg/types"
@@ -51,24 +51,17 @@ func New(logger logrus.FieldLogger, serialLogBundle string, bootstrap string, ma
filters = append(filters, filter)
}
region := metadataAWS.Region
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
ec2Client, err := awssession.NewEC2Client(context.TODO(), awssession.EndpointOptions{
Region: metadataAWS.Region,
Endpoints: metadataAWS.ServiceEndpoints,
}, ec2.WithAPIOptions(awsmiddleware.AddUserAgentKeyValue(awssession.OpenShiftInstallerGatherUserAgent, version.Raw)))
if err != nil {
return nil, fmt.Errorf("failed to create AWS config: %w", err)
return nil, fmt.Errorf("failed to create EC2 client: %w", err)
}
ec2Client := ec2.NewFromConfig(cfg,
ec2.WithAPIOptions(middleware.AddUserAgentKeyValue("OpenShift/4.x Gather", version.Raw)),
func(o *ec2.Options) {
for _, endpoint := range metadataAWS.ServiceEndpoints {
if strings.EqualFold(endpoint.Name, ec2.ServiceID) {
o.BaseEndpoint = aws.String(endpoint.URL)
}
}
})
return &Gather{
logger: logger,
region: region,
region: metadataAWS.Region,
filters: filters,
serialLogBundle: serialLogBundle,
bootstrap: bootstrap,