diff --git a/bootstrapper/cloudprovider/azure/metadata.go b/bootstrapper/cloudprovider/azure/metadata.go index 148fcf12e..e1edb8525 100644 --- a/bootstrapper/cloudprovider/azure/metadata.go +++ b/bootstrapper/cloudprovider/azure/metadata.go @@ -2,6 +2,7 @@ package azure import ( "context" + "errors" "fmt" "net/http" "regexp" @@ -16,8 +17,9 @@ import ( ) var ( - publicIPAddressRegexp = regexp.MustCompile(`/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.Network/publicIPAddresses/(?P[^/]+)`) - keyPathRegexp = regexp.MustCompile(`^\/home\/([^\/]+)\/\.ssh\/authorized_keys$`) + publicIPAddressRegexp = regexp.MustCompile(`/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.Network/publicIPAddresses/(?P[^/]+)`) + keyPathRegexp = regexp.MustCompile(`^\/home\/([^\/]+)\/\.ssh\/authorized_keys$`) + resourceGroupNameRegexp = regexp.MustCompile(`^(.*)-([^-]+)$`) ) // Metadata implements azure metadata APIs. @@ -183,6 +185,25 @@ func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) { return *virtualNetwork.Properties.Subnets[0].Properties.AddressPrefix, nil } +// UID retrieves the UID of the constellation. +func (m *Metadata) UID(ctx context.Context) (string, error) { + providerID, err := m.providerID(ctx) + if err != nil { + return "", err + } + _, resourceGroup, err := azureshared.BasicsFromProviderID(providerID) + if err != nil { + return "", err + } + + uid, err := getUIDFromResourceGroup(resourceGroup) + if err != nil { + return "", err + } + + return uid, nil +} + // getLoadBalancer retrieves the load balancer from cloud provider metadata. func (m *Metadata) getLoadBalancer(ctx context.Context) (*armnetwork.LoadBalancer, error) { providerID, err := m.providerID(ctx) @@ -314,3 +335,11 @@ func extractSSHKeys(sshConfig armcomputev2.SSHConfiguration) map[string][]string } return sshKeys } + +func getUIDFromResourceGroup(resourceGroup string) (string, error) { + matches := resourceGroupNameRegexp.FindStringSubmatch(resourceGroup) + if len(matches) != 3 { + return "", errors.New("error splitting resource group name") + } + return matches[2], nil +} diff --git a/bootstrapper/cloudprovider/azure/metadata_test.go b/bootstrapper/cloudprovider/azure/metadata_test.go index 0e11e6ced..a4b9310e9 100644 --- a/bootstrapper/cloudprovider/azure/metadata_test.go +++ b/bootstrapper/cloudprovider/azure/metadata_test.go @@ -499,6 +499,58 @@ func TestProviderID(t *testing.T) { } } +func TestUID(t *testing.T) { + testCases := map[string]struct { + imdsAPI imdsAPI + wantErr bool + wantUID string + }{ + "uid extraction from providerID works": { + imdsAPI: &stubIMDSAPI{ + res: metadataResponse{Compute: struct { + ResourceID string `json:"resourceId,omitempty"` + }{"/subscriptions/subscription-id/resourceGroups/basename-uid/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}}, + }, + wantUID: "uid", + }, + "providerID does not contain uid": { + imdsAPI: &stubIMDSAPI{ + res: metadataResponse{Compute: struct { + ResourceID string `json:"resourceId,omitempty"` + }{"/subscriptions/subscription-id/resourceGroups/invalid/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"}}, + }, + wantErr: true, + }, + "providerID is invalid": { + imdsAPI: newInvalidIMDSStub(), + wantErr: true, + }, + "imds retrieval fails": { + imdsAPI: &stubIMDSAPI{retrieveErr: errors.New("imds err")}, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + metadata := Metadata{ + imdsAPI: tc.imdsAPI, + } + uid, err := metadata.UID(context.Background()) + + if tc.wantErr { + assert.Error(err) + return + } + require.NoError(err) + assert.Equal(tc.wantUID, uid) + }) + } +} + func TestExtractInstanceTags(t *testing.T) { testCases := map[string]struct { in map[string]*string diff --git a/bootstrapper/cloudprovider/gcp/client.go b/bootstrapper/cloudprovider/gcp/client.go index 56b2b22ab..b7425d446 100644 --- a/bootstrapper/cloudprovider/gcp/client.go +++ b/bootstrapper/cloudprovider/gcp/client.go @@ -53,7 +53,7 @@ func NewClient(ctx context.Context) (*Client, error) { // RetrieveInstances returns list of instances including their ips and metadata. func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) { - uid, err := c.uid() + uid, err := c.UID() if err != nil { return nil, err } @@ -214,7 +214,7 @@ func (c *Client) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, // RetrieveLoadBalancerIP returns the IP address of the load balancer specified by project, zone and loadBalancerName. func (c *Client) RetrieveLoadBalancerIP(ctx context.Context, project, zone string) (string, error) { - uid, err := c.uid() + uid, err := c.UID() if err != nil { return "", err } @@ -284,8 +284,8 @@ func (c *Client) updateInstanceMetadata(ctx context.Context, project, zone, inst return nil } -// uid retrieves the current instances uid. -func (c *Client) uid() (string, error) { +// UID retrieves the current instances uid. +func (c *Client) UID() (string, error) { // API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid uid, err := c.RetrieveInstanceMetadata(constellationUIDMetadataKey) if err != nil { diff --git a/bootstrapper/cloudprovider/gcp/metadata.go b/bootstrapper/cloudprovider/gcp/metadata.go index efa8bdd06..a164dda8c 100644 --- a/bootstrapper/cloudprovider/gcp/metadata.go +++ b/bootstrapper/cloudprovider/gcp/metadata.go @@ -10,6 +10,8 @@ import ( // API handles all GCP API requests. type API interface { + // UID retrieves the current instances uid. + UID() (string, error) // RetrieveInstances retrieves a list of all accessible GCP instances with their metadata. RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) // RetrieveInstances retrieves a single GCP instances with its metadata. @@ -122,6 +124,11 @@ func (m *Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) { return m.api.RetrieveLoadBalancerIP(ctx, project, zone) } +// UID retrieves the UID of the constellation. +func (m *Metadata) UID(ctx context.Context) (string, error) { + return m.api.UID() +} + // Supported is used to determine if metadata API is implemented for this cloud provider. func (m *Metadata) Supported() bool { return true diff --git a/bootstrapper/cloudprovider/gcp/metadata_test.go b/bootstrapper/cloudprovider/gcp/metadata_test.go index 6d8fa890c..e3a48b58e 100644 --- a/bootstrapper/cloudprovider/gcp/metadata_test.go +++ b/bootstrapper/cloudprovider/gcp/metadata_test.go @@ -231,6 +231,8 @@ func TestGetInstance(t *testing.T) { } type stubGCPClient struct { + retrieveUIDValue string + retrieveUIDErr error retrieveInstanceValue metadata.InstanceMetadata retrieveInstanceErr error retrieveInstancesValues []metadata.InstanceMetadata @@ -289,6 +291,10 @@ func (s *stubGCPClient) RetrieveLoadBalancerIP(ctx context.Context, project, zon return s.loadBalancerIP, s.retrieveLoadBalancerErr } +func (s *stubGCPClient) UID() (string, error) { + return s.retrieveUIDValue, s.retrieveUIDErr +} + func (s *stubGCPClient) SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error { s.instanceMetadataProjects = append(s.instanceMetadataProjects, project) s.instanceMetadataZones = append(s.instanceMetadataZones, zone) diff --git a/bootstrapper/cloudprovider/qemu/metadata.go b/bootstrapper/cloudprovider/qemu/metadata.go index b1ee6d0a1..e0203f412 100644 --- a/bootstrapper/cloudprovider/qemu/metadata.go +++ b/bootstrapper/cloudprovider/qemu/metadata.go @@ -70,6 +70,13 @@ func (m Metadata) GetLoadBalancerIP(ctx context.Context) (string, error) { panic("function *Metadata.GetLoadBalancerIP not implemented") } +// UID returns the UID of the constellation. +func (m Metadata) UID(ctx context.Context) (string, error) { + // We expect only one constellation to be deployed in the same QEMU / libvirt environment. + // the UID can be an empty string. + return "", nil +} + // GetSubnetworkCIDR retrieves the subnetwork CIDR from cloud provider metadata. func (m Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) { return "10.244.0.0/16", nil diff --git a/bootstrapper/internal/kubernetes/cloud_provider.go b/bootstrapper/internal/kubernetes/cloud_provider.go index 6b03e957a..5a650a5e6 100644 --- a/bootstrapper/internal/kubernetes/cloud_provider.go +++ b/bootstrapper/internal/kubernetes/cloud_provider.go @@ -11,6 +11,8 @@ import ( // ProviderMetadata implementers read/write cloud provider metadata. type ProviderMetadata interface { + // UID returns the unique identifier for the constellation. + UID(ctx context.Context) (string, error) // List retrieves all instances belonging to the current Constellation. List(ctx context.Context) ([]metadata.InstanceMetadata, error) // Self retrieves the current instance. @@ -100,6 +102,9 @@ type stubProviderMetadata struct { SupportedResp bool SupportsLoadBalancerResp bool + + UIDErr error + UIDResp string } func (m *stubProviderMetadata) GetLoadBalancerIP(ctx context.Context) (string, error) { @@ -130,6 +135,10 @@ func (m *stubProviderMetadata) SupportsLoadBalancer() bool { return m.SupportsLoadBalancerResp } +func (m *stubProviderMetadata) UID(ctx context.Context) (string, error) { + return m.UIDResp, m.UIDErr +} + type stubCloudControllerManager struct { SupportedResp bool }