-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds cloudmeta package. For now it's only a basic interface for working with different cloud metadata providers, like aws, gcp, azure.
- Loading branch information
1 parent
c12903d
commit ed85113
Showing
2 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright (C) 2024 ScyllaDB | ||
|
||
package cloudmeta | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
) | ||
|
||
// InstanceMetadata represents metadata returned by cloud provider. | ||
type InstanceMetadata struct { | ||
InstanceType string | ||
CloudProvider CloudProvider | ||
} | ||
|
||
// CloudProvider is enum of supported cloud providers. | ||
type CloudProvider string | ||
|
||
// CloudProviderAWS represents aws provider. | ||
var CloudProviderAWS CloudProvider = "aws" | ||
|
||
// CloudMetadataProvider interface that each metadata provider should implement. | ||
type CloudMetadataProvider interface { | ||
Metadata(ctx context.Context) (InstanceMetadata, error) | ||
} | ||
|
||
// CLoudMeta is a wrapper around various cloud metadata providers. | ||
type CloudMeta struct { | ||
providers []CloudMetadataProvider | ||
} | ||
|
||
// NewCloudMeta creates new CloudMeta provider. | ||
func NewCloudMeta() (*CloudMeta, error) { | ||
// providers will initialized here and added to CloudMeta.providers. | ||
return &CloudMeta{}, nil | ||
} | ||
|
||
// GetInstanceMetadata tries to fetch instance metadata from AWS, GCP, Azure providers in order. | ||
func (cloud *CloudMeta) GetInstanceMetadata(ctx context.Context) (InstanceMetadata, error) { | ||
var mErr error | ||
for _, provider := range cloud.providers { | ||
meta, err := provider.Metadata(ctx) | ||
if err != nil { | ||
mErr = errors.Join(mErr, err) | ||
continue | ||
} | ||
return meta, nil | ||
} | ||
|
||
return InstanceMetadata{}, mErr | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Copyright (C) 2017 ScyllaDB | ||
|
||
package cloudmeta | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestGetInstanceMetadata(t *testing.T) { | ||
t.Run("when there is no active providers", func(t *testing.T) { | ||
cloudmeta := &CloudMeta{} | ||
|
||
meta, err := cloudmeta.GetInstanceMetadata(context.Background()) | ||
if err != nil { | ||
t.Fatalf("unexpected err: %v", err) | ||
} | ||
if meta.InstanceType != "" { | ||
t.Fatalf("meta.InstanceType should be empty, got %v", meta.InstanceType) | ||
} | ||
|
||
if meta.CloudProvider != "" { | ||
t.Fatalf("meta.CloudProvider should be empty, got %v", meta.CloudProvider) | ||
} | ||
}) | ||
|
||
t.Run("when there is only one active provider", func(t *testing.T) { | ||
cloudmeta := &CloudMeta{ | ||
providers: []CloudMetadataProvider{newTestProvider(t, "test_provider_1", "x-test-1", nil)}, | ||
} | ||
|
||
meta, err := cloudmeta.GetInstanceMetadata(context.Background()) | ||
if err != nil { | ||
t.Fatalf("unexpected err: %v", err) | ||
} | ||
|
||
if meta.InstanceType != "x-test-1" { | ||
t.Fatalf("meta.InstanceType should be 'x-test-1', got %v", meta.InstanceType) | ||
} | ||
|
||
if meta.CloudProvider != "test_provider_1" { | ||
t.Fatalf("meta.CloudProvider should be 'test_provider_1', got %v", meta.CloudProvider) | ||
} | ||
}) | ||
|
||
t.Run("when there is more than one active provider", func(t *testing.T) { | ||
cloudmeta := &CloudMeta{ | ||
providers: []CloudMetadataProvider{ | ||
newTestProvider(t, "test_provider_1", "x-test-1", nil), | ||
newTestProvider(t, "test_provider_2", "x-test-2", nil), | ||
}, | ||
} | ||
|
||
// Only first one should be returned. | ||
meta, err := cloudmeta.GetInstanceMetadata(context.Background()) | ||
if err != nil { | ||
t.Fatalf("unexpected err: %v", err) | ||
} | ||
|
||
if meta.InstanceType != "x-test-1" { | ||
t.Fatalf("meta.InstanceType should be 'x-test-1', got %v", meta.InstanceType) | ||
} | ||
|
||
if meta.CloudProvider != "test_provider_1" { | ||
t.Fatalf("meta.CloudProvider should be 'test_provider_1', got %v", meta.CloudProvider) | ||
} | ||
}) | ||
t.Run("when there is more than one active provider, but first returns err", func(t *testing.T) { | ||
cloudmeta := &CloudMeta{ | ||
providers: []CloudMetadataProvider{ | ||
newTestProvider(t, "test_provider_1", "x-test-1", fmt.Errorf("'test_provider_1' err")), | ||
newTestProvider(t, "test_provider_2", "x-test-2", nil), | ||
}, | ||
} | ||
|
||
// Only first succesfull one should be returned. | ||
meta, err := cloudmeta.GetInstanceMetadata(context.Background()) | ||
if err != nil { | ||
t.Fatalf("unexpected err: %v", err) | ||
} | ||
|
||
if meta.InstanceType != "x-test-2" { | ||
t.Fatalf("meta.InstanceType should be 'x-test-2', got %v", meta.InstanceType) | ||
} | ||
|
||
if meta.CloudProvider != "test_provider_2" { | ||
t.Fatalf("meta.CloudProvider should be 'test_provider_2', got %v", meta.CloudProvider) | ||
} | ||
}) | ||
|
||
t.Run("when there is more than one active provider, but all returns err", func(t *testing.T) { | ||
cloudmeta := &CloudMeta{ | ||
providers: []CloudMetadataProvider{ | ||
newTestProvider(t, "test_provider_1", "x-test-1", fmt.Errorf("'test_provider_1' err")), | ||
newTestProvider(t, "test_provider_2", "x-test-2", fmt.Errorf("'test_provider_2' err")), | ||
}, | ||
} | ||
|
||
// Only first succesfull one should be returned. | ||
meta, err := cloudmeta.GetInstanceMetadata(context.Background()) | ||
if err == nil { | ||
t.Fatalf("expected err, but got: %v", err) | ||
} | ||
|
||
if meta.InstanceType != "" { | ||
t.Fatalf("meta.InstanceType should be empty, got %v", meta.InstanceType) | ||
} | ||
|
||
if meta.CloudProvider != "" { | ||
t.Fatalf("meta.CloudProvider should be empty, got %v", meta.CloudProvider) | ||
} | ||
}) | ||
|
||
} | ||
|
||
func newTestProvider(t *testing.T, providerName, instanceType string, err error) *testProvider { | ||
t.Helper() | ||
|
||
return &testProvider{ | ||
name: CloudProvider(providerName), | ||
instanceType: instanceType, | ||
err: err, | ||
} | ||
} | ||
|
||
type testProvider struct { | ||
name CloudProvider | ||
instanceType string | ||
err error | ||
} | ||
|
||
func (tp testProvider) Metadata(ctx context.Context) (InstanceMetadata, error) { | ||
if tp.err != nil { | ||
return InstanceMetadata{}, tp.err | ||
} | ||
return InstanceMetadata{ | ||
CloudProvider: tp.name, | ||
InstanceType: tp.instanceType, | ||
}, nil | ||
} |