Fetch cloud environment in parallel (#23472)

This speeds up the amount of time it takes to determine which cloud
environment Teleport is running on, and properly propagates a context
as an argument instead of stashing it in a config struct.
This commit is contained in:
Zac Bergquist 2023-04-15 15:45:32 -06:00 committed by GitHub
parent 5b2998a455
commit fb6e7912fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 30 deletions

View file

@ -44,7 +44,7 @@ func Get(ctx context.Context) (*Metadata, error) {
go func() {
defaultFetcher := &fetchConfig{}
defaultFetcher.setDefaults()
metadata = defaultFetcher.fetch()
metadata = defaultFetcher.fetch(context.Background())
// Signal that the metadata is ready.
close(metadataReady)

View file

@ -59,7 +59,6 @@ type Metadata struct {
// fetchConfig contains the configuration used by the FetchMetadata method.
type fetchConfig struct {
context context.Context
// getenv is the method called to retrieve an environment
// variable.
// It is configurable so that it can be mocked in tests.
@ -79,9 +78,6 @@ type fetchConfig struct {
// commands, performing http requests, etc.
// Having these methods configurable allows us to mock them in tests.
func (c *fetchConfig) setDefaults() {
if c.context == nil {
c.context = context.Background()
}
if c.getenv == nil {
c.getenv = os.Getenv
}
@ -116,7 +112,7 @@ func (c *fetchConfig) setDefaults() {
}
// fetch fetches all metadata.
func (c *fetchConfig) fetch() *Metadata {
func (c *fetchConfig) fetch(ctx context.Context) *Metadata {
return &Metadata{
OS: c.fetchOS(),
OSVersion: c.fetchOSVersion(),
@ -124,8 +120,8 @@ func (c *fetchConfig) fetch() *Metadata {
GlibcVersion: c.fetchGlibcVersion(),
InstallMethods: c.fetchInstallMethods(),
ContainerRuntime: c.fetchContainerRuntime(),
ContainerOrchestrator: c.fetchContainerOrchestrator(),
CloudEnvironment: c.fetchCloudEnvironment(),
ContainerOrchestrator: c.fetchContainerOrchestrator(ctx),
CloudEnvironment: c.fetchCloudEnvironment(ctx),
}
}
@ -200,7 +196,7 @@ func (c *fetchConfig) fetchContainerRuntime() string {
// running on kubernetes.
// This function performs the equivalent of the following:
// curl -k https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/version | jq .gitVersion
func (c *fetchConfig) fetchContainerOrchestrator() string {
func (c *fetchConfig) fetchContainerOrchestrator(ctx context.Context) string {
host := c.getenv("KUBERNETES_SERVICE_HOST")
port := c.getenv("KUBERNETES_SERVICE_PORT")
if host == "" || port == "" {
@ -208,7 +204,7 @@ func (c *fetchConfig) fetchContainerOrchestrator() string {
}
url := fmt.Sprintf("https://%s:%s/version", host, port)
req, err := http.NewRequestWithContext(c.context, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return ""
}
@ -240,15 +236,36 @@ func (c *fetchConfig) fetchContainerOrchestrator() string {
// fetchCloudEnvironment returns aws, gpc or azure if the instance is running on
// such cloud environments.
func (c *fetchConfig) fetchCloudEnvironment() string {
if c.awsHTTPGetSuccess() {
return "aws"
func (c *fetchConfig) fetchCloudEnvironment(ctx context.Context) string {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
// kick off 3 checks in parallel, at most 1 will succeed
checks := []struct {
env string
f func(context.Context) bool
}{
{"aws", c.awsHTTPGetSuccess},
{"gcp", c.gcpHTTPGetSuccess},
{"azure", c.azureHTTPGetSuccess},
}
if c.gcpHTTPGetSuccess() {
return "gcp"
cloudEnv := make(chan string, len(checks))
for _, check := range checks {
check := check
go func() {
if check.f(ctx) {
cloudEnv <- check.env
} else {
cloudEnv <- ""
}
}()
}
if c.azureHTTPGetSuccess() {
return "azure"
for range checks {
if env := <-cloudEnv; env != "" {
return env
}
}
return ""
}
@ -256,9 +273,9 @@ func (c *fetchConfig) fetchCloudEnvironment() string {
// awsHTTPGetSuccess hits the AWS metadata endpoint in order to detect whether
// the instance is running on AWS.
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
func (c *fetchConfig) awsHTTPGetSuccess() bool {
func (c *fetchConfig) awsHTTPGetSuccess(ctx context.Context) bool {
url := "http://169.254.169.254/latest/meta-data/"
req, err := http.NewRequestWithContext(c.context, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false
}
@ -269,9 +286,9 @@ func (c *fetchConfig) awsHTTPGetSuccess() bool {
// gcpHTTPGetSuccess hits the GCP metadata endpoint in order to detect whether
// the instance is running on GCP.
// https://cloud.google.com/compute/docs/metadata/overview#parts-of-a-request
func (c *fetchConfig) gcpHTTPGetSuccess() bool {
func (c *fetchConfig) gcpHTTPGetSuccess(ctx context.Context) bool {
url := "http://metadata.google.internal/computeMetadata/v1"
req, err := http.NewRequestWithContext(c.context, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false
}
@ -283,9 +300,9 @@ func (c *fetchConfig) gcpHTTPGetSuccess() bool {
// azureHTTPGetSuccess hits the Azure metadata endpoint in order to detect whether
// the instance is running on Azure.
// https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
func (c *fetchConfig) azureHTTPGetSuccess() bool {
func (c *fetchConfig) azureHTTPGetSuccess(ctx context.Context) bool {
url := "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
req, err := http.NewRequestWithContext(c.context, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false
}

View file

@ -255,11 +255,10 @@ func TestFetchContainerOrchestrator(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
c := &fetchConfig{
context: context.Background(),
getenv: tc.getenv,
httpDo: tc.httpDo,
getenv: tc.getenv,
httpDo: tc.httpDo,
}
require.Equal(t, tc.expected, c.fetchContainerOrchestrator())
require.Equal(t, tc.expected, c.fetchContainerOrchestrator(context.Background()))
})
}
}
@ -352,10 +351,9 @@ func TestFetchCloudEnvironment(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
c := &fetchConfig{
context: context.Background(),
httpDo: tc.httpDo,
httpDo: tc.httpDo,
}
require.Equal(t, tc.expected, c.fetchCloudEnvironment())
require.Equal(t, tc.expected, c.fetchCloudEnvironment(context.Background()))
})
}
}