AB#2104 Feat/azure logging (#198)

implementation for azure early boot logging
This commit is contained in:
Fabian Kammel 2022-06-10 13:18:30 +02:00 committed by GitHub
parent 963c6f98e5
commit 84552ca8f7
33 changed files with 526 additions and 212 deletions

View File

@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Early boot logging for Cloud Provider: GCP & Azure
### Changed

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
@ -139,3 +140,8 @@ type virtualMachinesAPI interface {
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, vmName string, parameters armcompute.VirtualMachine,
options *armcompute.VirtualMachinesClientBeginCreateOrUpdateOptions) (virtualMachinesClientCreateOrUpdatePollerResponse, error)
}
type applicationInsightsAPI interface {
CreateOrUpdate(ctx context.Context, resourceGroupName string, resourceName string, insightProperties armapplicationinsights.Component,
options *armapplicationinsights.ComponentsClientCreateOrUpdateOptions) (armapplicationinsights.ComponentsClientCreateOrUpdateResponse, error)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
@ -402,3 +403,12 @@ func (a *stubRoleAssignmentsAPI) Create(ctx context.Context, scope string, roleA
}
return authorization.RoleAssignment{}, a.createErrors[(a.createCounter-1)%len(a.createErrors)]
}
type stubApplicationInsightsAPI struct {
err error
}
func (a *stubApplicationInsightsAPI) CreateOrUpdate(ctx context.Context, resourceGroupName string, resourceName string, insightProperties armapplicationinsights.Component, options *armapplicationinsights.ComponentsClientCreateOrUpdateOptions) (armapplicationinsights.ComponentsClientCreateOrUpdateResponse, error) {
resp := armapplicationinsights.ComponentsClientCreateOrUpdateResponse{}
return resp, a.err
}

View File

@ -0,0 +1,26 @@
package client
import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"golang.org/x/net/context"
)
func (c *Client) CreateApplicationInsight(ctx context.Context) error {
properties := armapplicationinsights.Component{
Kind: to.StringPtr("web"),
Location: to.StringPtr(c.location),
Properties: &armapplicationinsights.ComponentProperties{
ApplicationType: armapplicationinsights.ApplicationTypeWeb.ToPtr(),
},
}
_, err := c.applicationInsightsAPI.CreateOrUpdate(
ctx,
c.resourceGroup,
"constellation-insights-"+c.uid,
properties,
nil,
)
return err
}

View File

@ -0,0 +1,46 @@
package client
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateApplicationInsight(t *testing.T) {
testCases := map[string]struct {
applicationInsightsAPI applicationInsightsAPI
wantErr bool
}{
"successful create": {
applicationInsightsAPI: &stubApplicationInsightsAPI{
err: nil,
},
},
"failed create": {
applicationInsightsAPI: &stubApplicationInsightsAPI{
err: errors.New("some error"),
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
client := Client{
applicationInsightsAPI: tc.applicationInsightsAPI,
}
err := client.CreateApplicationInsight(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
})
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
@ -38,6 +39,7 @@ type Client struct {
applicationsAPI
servicePrincipalsAPI
roleAssignmentsAPI
applicationInsightsAPI
adReplicationLagCheckInterval time.Duration
adReplicationLagCheckMaxRetries int
@ -82,6 +84,7 @@ func NewFromDefault(subscriptionID, tenantID string) (*Client, error) {
networkInterfacesAPI := armnetwork.NewInterfacesClient(subscriptionID, cred, nil)
loadBalancersAPI := armnetwork.NewLoadBalancersClient(subscriptionID, cred, nil)
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
applicationInsightsAPI := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil)
applicationsAPI := graphrbac.NewApplicationsClient(tenantID)
applicationsAPI.Authorizer = graphAuthorizer
servicePrincipalsAPI := graphrbac.NewServicePrincipalsClient(tenantID)
@ -101,6 +104,7 @@ func NewFromDefault(subscriptionID, tenantID string) (*Client, error) {
servicePrincipalsAPI: &servicePrincipalsClient{&servicePrincipalsAPI},
roleAssignmentsAPI: &roleAssignmentsClient{&roleAssignmentsAPI},
virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI},
applicationInsightsAPI: applicationInsightsAPI,
subscriptionID: subscriptionID,
tenantID: tenantID,
nodes: cloudtypes.Instances{},

View File

@ -25,6 +25,7 @@ type gcpclient interface {
type azureclient interface {
GetState() (state.ConstellationState, error)
SetState(state.ConstellationState) error
CreateApplicationInsight(ctx context.Context) error
CreateResourceGroup(ctx context.Context) error
CreateExternalLoadBalancer(ctx context.Context) error
CreateVirtualNetwork(ctx context.Context) error

View File

@ -69,6 +69,10 @@ func (c *fakeAzureClient) SetState(stat state.ConstellationState) error {
return nil
}
func (c *fakeAzureClient) CreateApplicationInsight(ctx context.Context) error {
return nil
}
func (c *fakeAzureClient) CreateResourceGroup(ctx context.Context) error {
c.resourceGroup = "resource-group"
return nil
@ -156,6 +160,7 @@ type stubAzureClient struct {
getStateErr error
setStateErr error
createApplicationInsightErr error
createResourceGroupErr error
createVirtualNetworkErr error
createSecurityGroupErr error
@ -178,6 +183,10 @@ func (c *stubAzureClient) CreateExternalLoadBalancer(ctx context.Context) error
return c.createLoadBalancerErr
}
func (c *stubAzureClient) CreateApplicationInsight(ctx context.Context) error {
return c.createApplicationInsightErr
}
func (c *stubAzureClient) CreateResourceGroup(ctx context.Context) error {
return c.createResourceGroupErr
}

View File

@ -166,6 +166,9 @@ func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *confi
if err := cl.CreateInstances(ctx, createInput); err != nil {
return state.ConstellationState{}, err
}
if err := cl.CreateApplicationInsight(ctx); err != nil {
return state.ConstellationState{}, err
}
return cl.GetState()
}

View File

@ -3,6 +3,7 @@ package azure
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
@ -91,3 +92,7 @@ type tagsAPI interface {
CreateOrUpdateAtScope(ctx context.Context, scope string, parameters armresources.TagsResource, options *armresources.TagsClientCreateOrUpdateAtScopeOptions) (armresources.TagsClientCreateOrUpdateAtScopeResponse, error)
UpdateAtScope(ctx context.Context, scope string, parameters armresources.TagsPatchResource, options *armresources.TagsClientUpdateAtScopeOptions) (armresources.TagsClientUpdateAtScopeResponse, error)
}
type applicationInsightsAPI interface {
Get(ctx context.Context, resourceGroupName string, resourceName string, options *armapplicationinsights.ComponentsClientGetOptions) (armapplicationinsights.ComponentsClientGetResponse, error)
}

View File

@ -18,7 +18,7 @@ func (a *Autoscaler) Name() string {
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
func (a *Autoscaler) Secrets(instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) {
subscriptionID, resourceGroup, err := extractBasicsFromProviderID(instance.ProviderID)
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID)
if err != nil {
return resources.Secrets{}, err
}

View File

@ -65,7 +65,7 @@ func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtype
// Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets
// reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/
subscriptionID, resourceGroup, err := extractBasicsFromProviderID(instance.ProviderID)
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID)
if err != nil {
return resources.Secrets{}, err
}
@ -75,7 +75,7 @@ func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtype
}
vmType := "standard"
if _, _, _, _, err := splitScaleSetProviderID(instance.ProviderID); err == nil {
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(instance.ProviderID); err == nil {
vmType = "vmss"
}

View File

@ -1,6 +1,11 @@
package azure
import (
"context"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/edgelesssys/constellation/internal/azureshared"
"github.com/microsoft/ApplicationInsights-Go/appinsights"
)
@ -10,10 +15,34 @@ type Logger struct {
// NewLogger creates a new client to store information in Azure Application Insights
// https://github.com/Microsoft/ApplicationInsights-go
func NewLogger(instrumentationKey string) *Logger {
return &Logger{
client: appinsights.NewTelemetryClient(instrumentationKey),
func NewLogger(ctx context.Context, metadata *Metadata) (*Logger, error) {
providerID, err := metadata.providerID(ctx)
if err != nil {
return nil, err
}
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return nil, err
}
uid, err := azureshared.UIDFromProviderID(providerID)
if err != nil {
return nil, err
}
resourceName := "constellation-insights-" + uid
resp, err := metadata.applicationInsightsAPI.Get(ctx, resourceGroup, resourceName, &armapplicationinsights.ComponentsClientGetOptions{})
if err != nil {
return nil, err
}
if resp.Properties == nil || resp.Properties.InstrumentationKey == nil {
return nil, errors.New("unable to get instrumentation key")
}
return &Logger{
client: appinsights.NewTelemetryClient(*resp.Properties.InstrumentationKey),
}, nil
}
// Disclose stores log information in Azure Application Insights!

View File

@ -7,12 +7,14 @@ import (
"regexp"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/azureshared"
)
var (
@ -32,6 +34,7 @@ type Metadata struct {
virtualMachinesAPI
virtualMachineScaleSetVMsAPI
tagsAPI
applicationInsightsAPI
}
// NewMetadata creates a new Metadata.
@ -50,7 +53,7 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
if err != nil {
return nil, err
}
subscriptionID, _, err := extractBasicsFromProviderID("azure://" + instanceMetadata.Compute.ResourceID)
subscriptionID, _, err := azureshared.BasicsFromProviderID("azure://" + instanceMetadata.Compute.ResourceID)
if err != nil {
return nil, err
}
@ -63,6 +66,7 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
virtualMachinesAPI := armcompute.NewVirtualMachinesClient(subscriptionID, cred, nil)
virtualMachineScaleSetVMsAPI := armcompute.NewVirtualMachineScaleSetVMsClient(subscriptionID, cred, nil)
tagsAPI := armresources.NewTagsClient(subscriptionID, cred, nil)
applicationInsightsAPI := armapplicationinsights.NewComponentsClient(subscriptionID, cred, nil)
return &Metadata{
imdsAPI: &imdsAPI,
@ -75,6 +79,7 @@ func NewMetadata(ctx context.Context) (*Metadata, error) {
virtualMachinesAPI: &virtualMachinesClient{virtualMachinesAPI},
virtualMachineScaleSetVMsAPI: &virtualMachineScaleSetVMsClient{virtualMachineScaleSetVMsAPI},
tagsAPI: &tagsClient{tagsAPI},
applicationInsightsAPI: &applicationInsightsClient{applicationInsightsAPI},
}, nil
}
@ -84,7 +89,7 @@ func (m *Metadata) List(ctx context.Context) ([]cloudtypes.Instance, error) {
if err != nil {
return nil, err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return nil, err
}
@ -131,7 +136,7 @@ func (m *Metadata) SignalRole(ctx context.Context, role role.Role) error {
if err != nil {
return err
}
if _, _, _, _, err := splitScaleSetProviderID(providerID); err == nil {
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(providerID); err == nil {
// scale set instances cannot store tags and role can be inferred from scale set name.
return nil
}
@ -144,7 +149,7 @@ func (m *Metadata) GetNetworkSecurityGroupName(ctx context.Context) (string, err
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return "", err
}
@ -165,7 +170,7 @@ func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return "", err
}
@ -187,7 +192,7 @@ func (m *Metadata) getLoadBalancer(ctx context.Context) (*armnetwork.LoadBalance
if err != nil {
return nil, err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return nil, err
}
@ -253,7 +258,7 @@ func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
_, resourceGroup, err := extractBasicsFromProviderID(providerID)
_, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
if err != nil {
return "", err
}
@ -286,19 +291,6 @@ func (m *Metadata) providerID(ctx context.Context) (string, error) {
return "azure://" + instanceMetadata.Compute.ResourceID, nil
}
// extractBasicsFromProviderID extracts subscriptionID and resourceGroup from both types of valid azure providerID.
func extractBasicsFromProviderID(providerID string) (subscriptionID, resourceGroup string, err error) {
subscriptionID, resourceGroup, _, err = splitVMProviderID(providerID)
if err == nil {
return subscriptionID, resourceGroup, nil
}
subscriptionID, resourceGroup, _, _, err = splitScaleSetProviderID(providerID)
if err == nil {
return subscriptionID, resourceGroup, nil
}
return "", "", fmt.Errorf("providerID %v is malformatted", providerID)
}
// extractInstanceTags converts azure tags into metadata key-value pairs.
func extractInstanceTags(tags map[string]*string) map[string]string {
metadataMap := map[string]string{}

View File

@ -605,47 +605,6 @@ func TestProviderID(t *testing.T) {
}
}
func TestExtractBasicsFromProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantErr bool
wantSubscriptionID string
wantResourceGroup string
}{
"providerID for individual instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
},
"providerID for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
subscriptionID, resourceGroup, err := extractBasicsFromProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
})
}
}
func TestExtractInstanceTags(t *testing.T) {
testCases := map[string]struct {
in map[string]*string

View File

@ -9,17 +9,17 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/azureshared"
)
var (
azureVMSSProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
coordinatorScaleSetRegexp = regexp.MustCompile(`constellation-scale-set-coordinators-[0-9a-zA-Z]+$`)
nodeScaleSetRegexp = regexp.MustCompile(`constellation-scale-set-nodes-[0-9a-zA-Z]+$`)
)
// getScaleSetVM tries to get an azure vm belonging to a scale set.
func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
_, resourceGroup, scaleSet, instanceID, err := splitScaleSetProviderID(providerID)
_, resourceGroup, scaleSet, instanceID, err := azureshared.ScaleSetInformationFromProviderID(providerID)
if err != nil {
return cloudtypes.Instance{}, err
}
@ -70,17 +70,6 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
return instances, nil
}
// splitScaleSetProviderID splits a provider's id belonging to an azure scaleset into core components.
// A providerID for scale set VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>'
func splitScaleSetProviderID(providerID string) (subscriptionID, resourceGroup, scaleSet, instanceID string, err error) {
matches := azureVMSSProviderIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 5 {
return "", "", "", "", errors.New("error splitting providerID")
}
return matches[1], matches[2], matches[3], matches[4], nil
}
// convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance.
func convertScaleSetVMToCoreInstance(scaleSet string, vm armcompute.VirtualMachineScaleSetVM, networkInterfaces []armnetwork.Interface, publicIPAddresses []string) (cloudtypes.Instance, error) {
if vm.ID == nil {

View File

@ -162,52 +162,6 @@ func TestListScaleSetVMs(t *testing.T) {
}
}
func TestSplitScaleSetProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantErr bool
wantSubscriptionID string
wantResourceGroup string
wantScaleSet string
wantInstanceID string
}{
"providerID for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
wantScaleSet: "scale-set-name",
wantInstanceID: "instance-id",
},
"providerID for individual instance must fail": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
wantErr: true,
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
subscriptionID, resourceGroup, scaleSet, instanceID, err := splitScaleSetProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
assert.Equal(tc.wantScaleSet, scaleSet)
assert.Equal(tc.wantInstanceID, instanceID)
})
}
}
func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
testCases := map[string]struct {
inVM armcompute.VirtualMachineScaleSetVM

View File

@ -2,9 +2,7 @@ package azure
import (
"context"
"errors"
"fmt"
"regexp"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
@ -12,13 +10,11 @@ import (
"github.com/Azure/go-autorest/autorest/to"
"github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/internal/azureshared"
)
var azureVMProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
// getVM tries to get a single azure vm.
func (m *Metadata) getVM(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
_, resourceGroup, instanceName, err := splitVMProviderID(providerID)
_, resourceGroup, instanceName, err := azureshared.VMInformationFromProviderID(providerID)
if err != nil {
return cloudtypes.Instance{}, err
}
@ -73,17 +69,6 @@ func (m *Metadata) setTag(ctx context.Context, key, value string) error {
return err
}
// splitVMProviderID splits a provider's id belonging to a single azure instance into core components.
// A providerID for individual VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<instance-name>'
func splitVMProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) {
matches := azureVMProviderIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", errors.New("error splitting providerID")
}
return matches[1], matches[2], matches[3], nil
}
// convertVMToCoreInstance converts an azure virtual machine with interface configurations into a cloudtypes.Instance.
func convertVMToCoreInstance(vm armcompute.VirtualMachine, networkInterfaces []armnetwork.Interface) (cloudtypes.Instance, error) {
if vm.Name == nil || vm.ID == nil {

View File

@ -189,49 +189,6 @@ func TestSetTag(t *testing.T) {
}
}
func TestSplitVMProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantErr bool
wantSubscriptionID string
wantResourceGroup string
wantInstanceName string
}{
"providerID for individual instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
wantInstanceName: "instance-name",
},
"providerID for scale set instance must fail": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantErr: true,
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
subscriptionID, resourceGroup, instanceName, err := splitVMProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
assert.Equal(tc.wantInstanceName, instanceName)
})
}
}
func TestConvertVMToCoreInstance(t *testing.T) {
testCases := map[string]struct {
inVM armcompute.VirtualMachine

View File

@ -3,6 +3,7 @@ package azure
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
@ -110,3 +111,11 @@ type scaleSetsClient struct {
func (c *scaleSetsClient) List(resourceGroupName string, options *armcompute.VirtualMachineScaleSetsClientListOptions) virtualMachineScaleSetsClientListPager {
return c.VirtualMachineScaleSetsClient.List(resourceGroupName, options)
}
type applicationInsightsClient struct {
*armapplicationinsights.ComponentsClient
}
func (c *applicationInsightsClient) Get(ctx context.Context, resourceGroupName string, resourceName string, options *armapplicationinsights.ComponentsClientGetOptions) (armapplicationinsights.ComponentsClientGetResponse, error) {
return c.ComponentsClient.Get(ctx, resourceGroupName, resourceName, options)
}

View File

@ -51,7 +51,7 @@ func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resou
// reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892
var config strings.Builder
config.WriteString("[global]\n")
projectID, _, _, err := splitProviderID(instance.ProviderID)
projectID, _, _, err := gcpshared.SplitProviderID(instance.ProviderID)
if err != nil {
return resources.ConfigMaps{}, err
}

View File

@ -3,13 +3,13 @@ package gcp
import (
"context"
"fmt"
"regexp"
"strings"
compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/coordinator/cloudprovider"
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/internal/gcpshared"
"google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
@ -17,8 +17,6 @@ import (
const gcpSSHMetadataKey = "ssh-keys"
var providerIDRegex = regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
// Client implements the gcp.API interface.
type Client struct {
instanceAPI
@ -325,7 +323,7 @@ func convertToCoreInstance(in *computepb.Instance, project string, zone string)
metadata := extractInstanceMetadata(in.Metadata, "", false)
return cloudtypes.Instance{
Name: *in.Name,
ProviderID: joinProviderID(project, zone, *in.Name),
ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name),
Role: cloudprovider.ExtractRole(metadata),
PrivateIPs: extractPrivateIPs(in.NetworkInterfaces),
PublicIPs: extractPublicIPs(in.NetworkInterfaces),
@ -334,22 +332,6 @@ func convertToCoreInstance(in *computepb.Instance, project string, zone string)
}, nil
}
// joinProviderID builds a k8s provider ID for GCP instances.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func joinProviderID(project, zone, instanceName string) string {
return fmt.Sprintf("gce://%v/%v/%v", project, zone, instanceName)
}
// splitProviderID splits a provider's id into core components.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func splitProviderID(providerID string) (project, zone, instance string, err error) {
matches := providerIDRegex.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", fmt.Errorf("error splitting providerID: %v", providerID)
}
return matches[1], matches[2], matches[3], nil
}
// extractInstanceMetadata will extract the list of instance metadata key-value pairs into a map.
// If "skipKey" is true, "key" will be skipped.
func extractInstanceMetadata(in *computepb.Metadata, key string, skipKey bool) map[string]string {

View File

@ -5,6 +5,7 @@ import (
"log"
"cloud.google.com/go/logging"
"github.com/edgelesssys/constellation/internal/gcpshared"
)
type Logger struct {
@ -15,7 +16,7 @@ type Logger struct {
// NewLogger creates a new Cloud Logger for GCP.
// https://cloud.google.com/logging/docs/setup/go
func NewLogger(ctx context.Context, providerID string, logName string) (*Logger, error) {
projectID, _, _, err := splitProviderID(providerID)
projectID, _, _, err := gcpshared.SplitProviderID(providerID)
if err != nil {
return nil, err
}

View File

@ -7,6 +7,7 @@ import (
"github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes"
"github.com/edgelesssys/constellation/coordinator/core"
"github.com/edgelesssys/constellation/coordinator/role"
"github.com/edgelesssys/constellation/internal/gcpshared"
)
// API handles all GCP API requests.
@ -79,7 +80,7 @@ func (m *Metadata) Self(ctx context.Context) (cloudtypes.Instance, error) {
// GetInstance retrieves an instance using its providerID.
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (cloudtypes.Instance, error) {
project, zone, instanceName, err := splitProviderID(providerID)
project, zone, instanceName, err := gcpshared.SplitProviderID(providerID)
if err != nil {
return cloudtypes.Instance{}, fmt.Errorf("invalid providerID: %w", err)
}

View File

@ -122,8 +122,10 @@ func main() {
if err != nil {
log.Fatal(err)
}
// TODO: Implement cloud logging for Azure
cloudLogger = &logging.NopLogger{}
cloudLogger, err = azurecloud.NewLogger(context.Background(), metadata)
if err != nil {
log.Fatal(err)
}
coreMetadata = metadata
kube = kubernetes.New("azure", k8sapi.NewKubernetesUtil(), &k8sapi.CoreOSConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata), &azurecloud.CloudNodeManager{}, &azurecloud.Autoscaler{}, metadata)

3
go.mod
View File

@ -50,6 +50,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.3.0
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.3.1
@ -65,7 +66,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/ec2 v1.32.0
github.com/aws/aws-sdk-go-v2/service/kms v1.16.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2
github.com/aws/smithy-go v1.11.2
github.com/coreos/go-systemd/v22 v22.3.2
github.com/docker/docker v20.10.13+incompatible
github.com/docker/go-connections v0.4.0
@ -118,6 +118,7 @@ require (
require (
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
github.com/aws/smithy-go v1.11.2 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
)

2
go.sum
View File

@ -124,6 +124,8 @@ github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0 h1:8OgHKRX8uTyIi
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0/go.mod h1:uQSVRwN3dRA6hguqKpgzwonvQtpxaWo7/t5cbz3iHbE=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 h1:lirjIOHv5RrmDbZXw9lUz/fY68uU05qR4uIef58WMvQ=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1/go.mod h1:j1J9XXIo/eXD7YSrr73sYZTEY/AQ0+/Q6Aa96z1e2j8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1 h1:H+YaV8IY4sVFxrkSmyvfvXZv+wxUq/qTQOa9TkqUqPE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1/go.mod h1:IwvRqY+EcaQzfAGUdIZpzWELUdsZzuItWRP6cNTzgr0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0 h1:kqRtiAe9aH0WzzQm3Mq7N6mzcdZHGJZrdteepKT7ymU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0/go.mod h1:isx+19QmRnAX0Ls0Adm/8SL3b8bIaZiSPbhpoyZX5Mw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1 h1:CyGPbnjITjA63agVN1nNznge7Tip0g7OiAvFPiT0btU=

View File

@ -60,6 +60,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.3.1 // indirect

View File

@ -113,6 +113,8 @@ github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0 h1:8OgHKRX8uTyIi
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0/go.mod h1:uQSVRwN3dRA6hguqKpgzwonvQtpxaWo7/t5cbz3iHbE=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 h1:lirjIOHv5RrmDbZXw9lUz/fY68uU05qR4uIef58WMvQ=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1/go.mod h1:j1J9XXIo/eXD7YSrr73sYZTEY/AQ0+/Q6Aa96z1e2j8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1 h1:H+YaV8IY4sVFxrkSmyvfvXZv+wxUq/qTQOa9TkqUqPE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v0.2.1/go.mod h1:IwvRqY+EcaQzfAGUdIZpzWELUdsZzuItWRP6cNTzgr0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0 h1:kqRtiAe9aH0WzzQm3Mq7N6mzcdZHGJZrdteepKT7ymU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0/go.mod h1:isx+19QmRnAX0Ls0Adm/8SL3b8bIaZiSPbhpoyZX5Mw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1 h1:CyGPbnjITjA63agVN1nNznge7Tip0g7OiAvFPiT0btU=

View File

@ -0,0 +1,61 @@
package azureshared
import (
"errors"
"fmt"
"regexp"
"strings"
)
var (
azureVMProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachines/([^/]+)$`)
azureVMSSProviderIDRegexp = regexp.MustCompile(`^azure:///subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.Compute/virtualMachineScaleSets/([^/]+)/virtualMachines/([^/]+)$`)
)
// BasicsFromProviderID extracts subscriptionID and resourceGroup from both types of valid azure providerID.
func BasicsFromProviderID(providerID string) (subscriptionID, resourceGroup string, err error) {
subscriptionID, resourceGroup, _, err = VMInformationFromProviderID(providerID)
if err == nil {
return subscriptionID, resourceGroup, nil
}
subscriptionID, resourceGroup, _, _, err = ScaleSetInformationFromProviderID(providerID)
if err == nil {
return subscriptionID, resourceGroup, nil
}
return "", "", fmt.Errorf("providerID %v is malformatted", providerID)
}
// UIDFromProviderID extracts our own generated unique ID, which is the
// suffix at the resource group, e.g., resource-group-J18dB
// J18dB is the UID.
func UIDFromProviderID(providerID string) (string, error) {
_, resourceGroup, err := BasicsFromProviderID(providerID)
if err != nil {
return "", err
}
parts := strings.Split(resourceGroup, "-")
return parts[len(parts)-1], nil
}
// VMInformationFromProviderID splits a provider's id belonging to a single azure instance into core components.
// A providerID for individual VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<instance-name>'
func VMInformationFromProviderID(providerID string) (subscriptionID, resourceGroup, instanceName string, err error) {
matches := azureVMProviderIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", errors.New("error splitting providerID")
}
return matches[1], matches[2], matches[3], nil
}
// ScaleSetInformationFromProviderID splits a provider's id belonging to an azure scaleset into core components.
// A providerID for scale set VMs is build after the following schema:
// - 'azure:///subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachineScaleSets/<scale-set-name>/virtualMachines/<instance-id>'
func ScaleSetInformationFromProviderID(providerID string) (subscriptionID, resourceGroup, scaleSet, instanceID string, err error) {
matches := azureVMSSProviderIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 5 {
return "", "", "", "", errors.New("error splitting providerID")
}
return matches[1], matches[2], matches[3], matches[4], nil
}

View File

@ -0,0 +1,177 @@
package azureshared
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBasicsFromProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantErr bool
wantSubscriptionID string
wantResourceGroup string
}{
"providerID for individual instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
},
"providerID for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
subscriptionID, resourceGroup, err := BasicsFromProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
})
}
}
func TestUIDFromProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantUID string
wantErr bool
}{
"UID from virtual machine works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group-ABC123/providers/Microsoft.Compute/virtualMachines/instance-name",
wantUID: "ABC123",
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
uid, err := UIDFromProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Equal(tc.wantUID, uid)
})
}
}
func TestVMInformationFromProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantSubscriptionID string
wantResourceGroup string
wantInstanceName string
wantErr bool
}{
"simple id": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-id",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
wantInstanceName: "instance-id",
},
"missing instance": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines",
wantErr: true,
},
"providerID for scale set instance must fail": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantErr: true,
},
"wrong provider": {
providerID: "gcp:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-id",
wantErr: true,
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
subscriptionID, resourceGroup, instanceName, err := VMInformationFromProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
assert.Equal(tc.wantInstanceName, instanceName)
})
}
}
func TestScaleSetInformationFromProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantSubscriptionID string
wantResourceGroup string
wantScaleSet string
wantInstanceID string
wantErr bool
}{
"providerID for scale set instance works": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
wantSubscriptionID: "subscription-id",
wantResourceGroup: "resource-group",
wantScaleSet: "scale-set-name",
wantInstanceID: "instance-id",
},
"providerID for individual instance must fail": {
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
wantErr: true,
},
"providerID is malformed": {
providerID: "malformed-provider-id",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
subscriptionID, resourceGroup, scaleSet, instanceName, err := ScaleSetInformationFromProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.wantSubscriptionID, subscriptionID)
assert.Equal(tc.wantResourceGroup, resourceGroup)
assert.Equal(tc.wantScaleSet, scaleSet)
assert.Equal(tc.wantInstanceID, instanceName)
})
}
}

View File

@ -0,0 +1,24 @@
package gcpshared
import (
"fmt"
"regexp"
)
var providerIDRegex = regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
// SplitProviderID splits a provider's id into core components.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func SplitProviderID(providerID string) (project, zone, instance string, err error) {
matches := providerIDRegex.FindStringSubmatch(providerID)
if len(matches) != 4 {
return "", "", "", fmt.Errorf("error splitting providerID: %v", providerID)
}
return matches[1], matches[2], matches[3], nil
}
// JoinProviderID builds a k8s provider ID for GCP instances.
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
func JoinProviderID(project, zone, instanceName string) string {
return fmt.Sprintf("gce://%v/%v/%v", project, zone, instanceName)
}

View File

@ -0,0 +1,75 @@
package gcpshared
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitProviderID(t *testing.T) {
testCases := map[string]struct {
providerID string
wantProjectID string
wantZone string
wantInstance string
wantErr bool
}{
"simple id": {
providerID: "gce://someProject/someZone/someInstance",
wantProjectID: "someProject",
wantZone: "someZone",
wantInstance: "someInstance",
},
"incomplete id": {
providerID: "gce://someProject/someZone",
wantErr: true,
},
"wrong provider": {
providerID: "azure://someProject/someZone/someInstance",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
projectID, zone, instance, err := SplitProviderID(tc.providerID)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.wantProjectID, projectID)
assert.Equal(tc.wantZone, zone)
assert.Equal(tc.wantInstance, instance)
})
}
}
func TestJoinProviderID(t *testing.T) {
testCases := map[string]struct {
projectID string
zone string
instance string
wantProviderID string
}{
"simple id": {
projectID: "someProject",
zone: "someZone",
instance: "someInstance",
wantProviderID: "gce://someProject/someZone/someInstance",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
providerID := JoinProviderID(tc.projectID, tc.zone, tc.instance)
assert.Equal(tc.wantProviderID, providerID)
})
}
}