diff --git a/util/pkg/vfs/BUILD.bazel b/util/pkg/vfs/BUILD.bazel index fa74325fce143..0573fa6a9a6d7 100644 --- a/util/pkg/vfs/BUILD.bazel +++ b/util/pkg/vfs/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/awserr:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/credentials:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/aws/ec2metadata:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/endpoints:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", diff --git a/util/pkg/vfs/s3context.go b/util/pkg/vfs/s3context.go index 7cfa08bdfd552..9b99793339452 100644 --- a/util/pkg/vfs/s3context.go +++ b/util/pkg/vfs/s3context.go @@ -18,13 +18,18 @@ package vfs import ( "fmt" + "io/ioutil" + "net/http" "os" "regexp" + "runtime" + "strings" "sync" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" @@ -150,8 +155,19 @@ func (s *S3Context) getDetailsForBucket(bucket string) (*S3BucketDetails, error) } awsRegion := os.Getenv("AWS_REGION") + if awsRegion == "" && isRunningOnEC2() { + region, err := getRegionFromMetadata() + if err != nil { + klog.V(2).Infof("unable to get region from metadata:%v", err) + } else { + awsRegion = region + klog.V(2).Infof("got region from metadata: %q", awsRegion) + } + } + if awsRegion == "" { awsRegion = "us-east-1" + klog.V(2).Infof("defaulting region to %q", awsRegion) } if err := validateRegion(awsRegion); err != nil { @@ -298,6 +314,57 @@ func bruteforceBucketLocation(region *string, request *s3.GetBucketLocationInput } } +// isRunningOnEC2 determines if we could be running on EC2. +// It is used to avoid a call to the metadata service to get the current region, +// because that call is slow if not running on EC2 +func isRunningOnEC2() bool { + if runtime.GOOS == "linux" { + // Approach based on https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html + productUUID, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_uuid") + if err != nil { + klog.V(2).Infof("unable to read /sys/devices/virtual/dmi/id/product_uuid, assuming not running on EC2: %v", err) + return false + } + + s := strings.ToLower(strings.TrimSpace(string(productUUID))) + if strings.HasPrefix(s, "ec2") { + klog.V(2).Infof("product_uuid is %q, assuming running on EC2", s) + return true + } else { + klog.V(2).Infof("product_uuid is %q, assuming not running on EC2", s) + return false + } + } else { + klog.V(2).Infof("GOOS=%q, assuming not running on EC2", runtime.GOOS) + return false + } +} + +// getRegionFromMetadata queries the metadata service for the current region, if running in EC2 +func getRegionFromMetadata() (string, error) { + // Use an even shorter timeout, to minimize impact when not running on EC2 + // Note that we still retry a few times, this works out a little under a 1s delay + shortTimeout := &aws.Config{ + HTTPClient: &http.Client{ + Timeout: 100 * time.Millisecond, + }, + } + + metadataSession, err := session.NewSession(shortTimeout) + if err != nil { + return "", fmt.Errorf("unable to build session: %v", err) + } + + metadata := ec2metadata.New(metadataSession) + metadataRegion, err := metadata.Region() + + if err != nil { + return "", fmt.Errorf("unable to get region from metadata: %v", err) + } + + return metadataRegion, nil +} + func validateRegion(region string) error { resolver := endpoints.DefaultResolver() partitions := resolver.(endpoints.EnumPartitions).Partitions() @@ -308,7 +375,7 @@ func validateRegion(region string) error { } } } - return fmt.Errorf("%s is not a valid region\nPlease check that your region is formatted correctly (i.e. us-east-1)", region) + return fmt.Errorf("%s is not a valid region\nPlease check that your region is formatted correctly (e.g. us-east-1)", region) } func VFSPath(url string) (string, error) {