Use tags for UID and role parsing (#242)

* Apply tags to all applicable GCP resources

* Move GCP UID and role from VM metadata to labels

* Adjust Azure tags to be in line with GCP and AWS

* Dont rely on resource name to find resources

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2022-10-24 16:58:21 +02:00 committed by GitHub
parent c2814aeddb
commit b35b74b772
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 344 additions and 360 deletions

View file

@ -135,7 +135,7 @@ func main() {
if err != nil { if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to marshal PCRs") log.With(zap.Error(err)).Fatalf("Failed to marshal PCRs")
} }
cloudControllerManager, err := gcpcloud.NewCloudControllerManager(metadata) cloudControllerManager, err := gcpcloud.NewCloudControllerManager(ctx, metadata)
if err != nil { if err != nil {
log.With(zap.Error(err)).Fatalf("Failed to create cloud controller manager") log.With(zap.Error(err)).Fatalf("Failed to create cloud controller manager")
} }

View file

@ -144,7 +144,7 @@ func (k *KubeWrapper) InitCluster(
zap.String("nodeName", nodeName), zap.String("nodeName", nodeName),
zap.String("providerID", providerID), zap.String("providerID", providerID),
zap.String("nodeIP", nodeIP), zap.String("nodeIP", nodeIP),
zap.String("controlPlaneEndpointEndpoint", controlPlaneEndpoint), zap.String("controlPlaneEndpoint", controlPlaneEndpoint),
zap.String("podCIDR", subnetworkPodCIDR), zap.String("podCIDR", subnetworkPodCIDR),
).Infof("Setting information for node") ).Infof("Setting information for node")

View file

@ -194,7 +194,7 @@ module "scale_set_control_plane" {
instance_type = var.instance_type instance_type = var.instance_type
confidential_vm = var.confidential_vm confidential_vm = var.confidential_vm
secure_boot = var.secure_boot secure_boot = var.secure_boot
tags = merge(local.tags, { role = "control-plane" }) tags = merge(local.tags, { constellation-role = "control-plane" })
image_id = var.image_id image_id = var.image_id
user_assigned_identity = var.user_assigned_identity user_assigned_identity = var.user_assigned_identity
network_security_group_id = azurerm_network_security_group.security_group.id network_security_group_id = azurerm_network_security_group.security_group.id
@ -217,7 +217,7 @@ module "scale_set_worker" {
instance_type = var.instance_type instance_type = var.instance_type
confidential_vm = var.confidential_vm confidential_vm = var.confidential_vm
secure_boot = var.secure_boot secure_boot = var.secure_boot
tags = merge(local.tags, { role = "worker" }) tags = merge(local.tags, { constellation-role = "worker" })
image_id = var.image_id image_id = var.image_id
user_assigned_identity = var.user_assigned_identity user_assigned_identity = var.user_assigned_identity
network_security_group_id = azurerm_network_security_group.security_group.id network_security_group_id = azurerm_network_security_group.security_group.id

View file

@ -22,7 +22,7 @@ provider "google" {
locals { locals {
uid = random_id.uid.hex uid = random_id.uid.hex
name = "${var.name}-${local.uid}" name = "${var.name}-${local.uid}"
tag = "constellation-${local.uid}" labels = { constellation-uid = local.uid }
ports_node_range = "30000-32767" ports_node_range = "30000-32767"
ports_kubernetes = "6443" ports_kubernetes = "6443"
ports_bootstrapper = "9000" ports_bootstrapper = "9000"
@ -138,6 +138,7 @@ module "instance_group_control_plane" {
{ name = "recovery", port = local.ports_recovery }, { name = "recovery", port = local.ports_recovery },
var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [], var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [],
]) ])
labels = local.labels
} }
module "instance_group_worker" { module "instance_group_worker" {
@ -154,6 +155,7 @@ module "instance_group_worker" {
subnetwork = google_compute_subnetwork.vpc_subnetwork.id subnetwork = google_compute_subnetwork.vpc_subnetwork.id
kube_env = local.kube_env kube_env = local.kube_env
debug = var.debug debug = var.debug
labels = local.labels
} }
resource "google_compute_global_address" "loadbalancer_ip" { resource "google_compute_global_address" "loadbalancer_ip" {
@ -168,9 +170,7 @@ module "loadbalancer_kube" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_kubernetes port = local.ports_kubernetes
frontend_labels = { frontend_labels = merge(local.labels, { constellation-use = "kubernetes" })
constellation-uid = local.uid
}
} }
module "loadbalancer_boot" { module "loadbalancer_boot" {
@ -181,6 +181,7 @@ module "loadbalancer_boot" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_bootstrapper port = local.ports_bootstrapper
frontend_labels = merge(local.labels, { constellation-use = "bootstrapper" })
} }
module "loadbalancer_verify" { module "loadbalancer_verify" {
@ -191,6 +192,7 @@ module "loadbalancer_verify" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_verify port = local.ports_verify
frontend_labels = merge(local.labels, { constellation-use = "verify" })
} }
module "loadbalancer_konnectivity" { module "loadbalancer_konnectivity" {
@ -201,6 +203,7 @@ module "loadbalancer_konnectivity" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_konnectivity port = local.ports_konnectivity
frontend_labels = merge(local.labels, { constellation-use = "konnectivity" })
} }
module "loadbalancer_recovery" { module "loadbalancer_recovery" {
@ -211,6 +214,7 @@ module "loadbalancer_recovery" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_recovery port = local.ports_recovery
frontend_labels = merge(local.labels, { constellation-use = "recovery" })
} }
module "loadbalancer_debugd" { module "loadbalancer_debugd" {
@ -222,4 +226,5 @@ module "loadbalancer_debugd" {
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_group = module.instance_group_control_plane.instance_group
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_debugd port = local.ports_debugd
frontend_labels = merge(local.labels, { constellation-use = "debugd" })
} }

View file

@ -16,6 +16,7 @@ resource "google_compute_instance_template" "template" {
name = local.name name = local.name
machine_type = var.instance_type machine_type = var.instance_type
tags = ["constellation-${var.uid}"] tags = ["constellation-${var.uid}"]
labels = merge(var.labels, { constellation-role = local.role_dashed })
confidential_instance_config { confidential_instance_config {
enable_confidential_compute = true enable_confidential_compute = true
@ -41,8 +42,6 @@ resource "google_compute_instance_template" "template" {
metadata = { metadata = {
kube-env = var.kube_env kube-env = var.kube_env
constellation-uid = var.uid
constellation-role = var.role
serial-port-enable = var.debug ? "TRUE" : "FALSE" serial-port-enable = var.debug ? "TRUE" : "FALSE"
} }

View file

@ -13,6 +13,12 @@ variable "uid" {
description = "UID of the cluster. This is used for tags." description = "UID of the cluster. This is used for tags."
} }
variable "labels" {
type = map(string)
default = {}
description = "Labels to apply to the instance group."
}
variable "instance_type" { variable "instance_type" {
type = string type = string
description = "Instance type for the nodes." description = "Instance type for the nodes."

View file

@ -18,6 +18,7 @@ import (
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"k8s.io/utils/clock" "k8s.io/utils/clock"
) )
@ -147,7 +148,7 @@ func (l *Logger) createStream(ctx context.Context, imds imdsAPI) error {
l.streamName = name l.streamName = name
// find log group with matching Constellation UID // find log group with matching Constellation UID
uid, err := readInstanceTag(ctx, imds, tagUID) uid, err := readInstanceTag(ctx, imds, cloud.TagUID)
if err != nil { if err != nil {
return err return err
} }
@ -162,7 +163,7 @@ func (l *Logger) createStream(ctx context.Context, imds imdsAPI) error {
if err != nil { if err != nil {
continue // we may not have permission to read the tags of a log group outside the Constellation scope continue // we may not have permission to read the tags of a log group outside the Constellation scope
} }
if tags.Tags[tagUID] == uid { if tags.Tags[cloud.TagUID] == uid {
l.groupName = *group.LogGroupName l.groupName = *group.LogGroupName
res.NextToken = nil // stop pagination res.NextToken = nil // stop pagination
break break

View file

@ -17,6 +17,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" logs "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
@ -41,7 +42,7 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
@ -50,7 +51,7 @@ func TestCreateStream(t *testing.T) {
{LogGroupName: aws.String("test-group")}, {LogGroupName: aws.String("test-group")},
}, },
}, },
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
}, },
wantStream: "test-instance", wantStream: "test-instance",
wantGroup: "test-group", wantGroup: "test-group",
@ -59,7 +60,7 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
@ -89,13 +90,13 @@ func TestCreateStream(t *testing.T) {
"some-tag": "random-tag", "some-tag": "random-tag",
}, },
"other-group": { "other-group": {
tagUID: "other-uid", cloud.TagUID: "other-uid",
}, },
"another-group": { "another-group": {
"some-tag": "uid", "some-tag": "uid",
}, },
"test-group": { "test-group": {
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
}, },
@ -106,7 +107,7 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
@ -115,7 +116,7 @@ func TestCreateStream(t *testing.T) {
{LogGroupName: aws.String("test-group")}, {LogGroupName: aws.String("test-group")},
}, },
}, },
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
createErr: &types.ResourceAlreadyExistsException{}, createErr: &types.ResourceAlreadyExistsException{},
}, },
wantStream: "test-instance", wantStream: "test-instance",
@ -125,7 +126,7 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
@ -134,7 +135,7 @@ func TestCreateStream(t *testing.T) {
{LogGroupName: aws.String("test-group")}, {LogGroupName: aws.String("test-group")},
}, },
}, },
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
createErr: someErr, createErr: someErr,
}, },
wantErr: true, wantErr: true,
@ -151,14 +152,14 @@ func TestCreateStream(t *testing.T) {
{LogGroupName: aws.String("test-group")}, {LogGroupName: aws.String("test-group")},
}, },
}, },
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
}, },
wantErr: true, wantErr: true,
}, },
"missing name tag": { "missing name tag": {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
@ -167,7 +168,7 @@ func TestCreateStream(t *testing.T) {
{LogGroupName: aws.String("test-group")}, {LogGroupName: aws.String("test-group")},
}, },
}, },
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
}, },
wantErr: true, wantErr: true,
}, },
@ -175,12 +176,12 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
describeErr: someErr, describeErr: someErr,
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
}, },
wantErr: true, wantErr: true,
}, },
@ -188,12 +189,12 @@ func TestCreateStream(t *testing.T) {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
logs: &stubLogs{ logs: &stubLogs{
describeRes1: &logs.DescribeLogGroupsOutput{}, describeRes1: &logs.DescribeLogGroupsOutput{},
listTags: map[string]map[string]string{"test-group": {tagUID: "uid"}}, listTags: map[string]map[string]string{"test-group": {cloud.TagUID: "uid"}},
}, },
wantErr: true, wantErr: true,
}, },

View file

@ -17,14 +17,13 @@ import (
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
) )
const ( const (
tagName = "Name" tagName = "Name"
tagRole = "constellation-role"
tagUID = "constellation-uid"
) )
type ec2API interface { type ec2API interface {
@ -62,7 +61,7 @@ func (m *Metadata) Supported() bool {
// List retrieves all instances belonging to the current Constellation. // List retrieves all instances belonging to the current Constellation.
func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) { func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
uid, err := readInstanceTag(ctx, m.imds, tagUID) uid, err := readInstanceTag(ctx, m.imds, cloud.TagUID)
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving uid tag: %w", err) return nil, fmt.Errorf("retrieving uid tag: %w", err)
} }
@ -85,7 +84,7 @@ func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error)
if err != nil { if err != nil {
return metadata.InstanceMetadata{}, fmt.Errorf("retrieving name tag: %w", err) return metadata.InstanceMetadata{}, fmt.Errorf("retrieving name tag: %w", err)
} }
instanceRole, err := readInstanceTag(ctx, m.imds, tagRole) instanceRole, err := readInstanceTag(ctx, m.imds, cloud.TagRole)
if err != nil { if err != nil {
return metadata.InstanceMetadata{}, fmt.Errorf("retrieving role tag: %w", err) return metadata.InstanceMetadata{}, fmt.Errorf("retrieving role tag: %w", err)
} }
@ -128,7 +127,7 @@ func (m *Metadata) GetInstance(ctx context.Context, providerID string) (metadata
// UID returns the UID of the Constellation. // UID returns the UID of the Constellation.
func (m *Metadata) UID(ctx context.Context) (string, error) { func (m *Metadata) UID(ctx context.Context) (string, error) {
return readInstanceTag(ctx, m.imds, tagUID) return readInstanceTag(ctx, m.imds, cloud.TagUID)
} }
// SupportsLoadBalancer returns true if the cloud provider supports load balancers. // SupportsLoadBalancer returns true if the cloud provider supports load balancers.
@ -151,7 +150,7 @@ func (m *Metadata) getAllInstancesInGroup(ctx context.Context, uid string) ([]ty
instanceReq := &ec2.DescribeInstancesInput{ instanceReq := &ec2.DescribeInstancesInput{
Filters: []types.Filter{ Filters: []types.Filter{
{ {
Name: aws.String("tag:" + tagUID), Name: aws.String("tag:" + cloud.TagUID),
Values: []string{uid}, Values: []string{uid},
}, },
}, },
@ -199,7 +198,7 @@ func (m *Metadata) convertToMetadataInstance(ec2Instances []types.Instance) ([]m
} }
newInstance.Name = name newInstance.Name = name
instanceRole, err := findTag(ec2Instance.Tags, tagRole) instanceRole, err := findTag(ec2Instance.Tags, cloud.TagRole)
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving tag for instance %s: %w", *ec2Instance.InstanceId, err) return nil, fmt.Errorf("retrieving tag for instance %s: %w", *ec2Instance.InstanceId, err)
} }

View file

@ -17,6 +17,7 @@ import (
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -42,7 +43,7 @@ func TestSelf(t *testing.T) {
}, },
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagRole: "controlplane", cloud.TagRole: "controlplane",
}, },
}, },
wantSelf: metadata.InstanceMetadata{ wantSelf: metadata.InstanceMetadata{
@ -63,7 +64,7 @@ func TestSelf(t *testing.T) {
}, },
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagRole: "worker", cloud.TagRole: "worker",
}, },
}, },
wantSelf: metadata.InstanceMetadata{ wantSelf: metadata.InstanceMetadata{
@ -78,7 +79,7 @@ func TestSelf(t *testing.T) {
getInstanceIdentityDocumentErr: someErr, getInstanceIdentityDocumentErr: someErr,
tags: map[string]string{ tags: map[string]string{
tagName: "test-instance", tagName: "test-instance",
tagRole: "controlplane", cloud.TagRole: "controlplane",
}, },
}, },
wantErr: true, wantErr: true,
@ -106,7 +107,7 @@ func TestSelf(t *testing.T) {
}, },
}, },
tags: map[string]string{ tags: map[string]string{
tagRole: "controlplane", cloud.TagRole: "controlplane",
}, },
}, },
wantErr: true, wantErr: true,
@ -165,11 +166,11 @@ func TestList(t *testing.T) {
Value: aws.String("name-1"), Value: aws.String("name-1"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
{ {
Key: aws.String(tagUID), Key: aws.String(cloud.TagUID),
Value: aws.String("uid"), Value: aws.String("uid"),
}, },
}, },
@ -187,11 +188,11 @@ func TestList(t *testing.T) {
Value: aws.String("name-2"), Value: aws.String("name-2"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("worker"), Value: aws.String("worker"),
}, },
{ {
Key: aws.String(tagUID), Key: aws.String(cloud.TagUID),
Value: aws.String("uid"), Value: aws.String("uid"),
}, },
}, },
@ -210,7 +211,7 @@ func TestList(t *testing.T) {
"success single page": { "success single page": {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
ec2: &stubEC2{ ec2: &stubEC2{
@ -234,7 +235,7 @@ func TestList(t *testing.T) {
"success multiple pages": { "success multiple pages": {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
ec2: &stubEC2{ ec2: &stubEC2{
@ -255,11 +256,11 @@ func TestList(t *testing.T) {
Value: aws.String("name-3"), Value: aws.String("name-3"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("worker"), Value: aws.String("worker"),
}, },
{ {
Key: aws.String(tagUID), Key: aws.String(cloud.TagUID),
Value: aws.String("uid"), Value: aws.String("uid"),
}, },
}, },
@ -302,7 +303,7 @@ func TestList(t *testing.T) {
"describe instances fails": { "describe instances fails": {
imds: &stubIMDS{ imds: &stubIMDS{
tags: map[string]string{ tags: map[string]string{
tagUID: "uid", cloud.TagUID: "uid",
}, },
}, },
ec2: &stubEC2{ ec2: &stubEC2{
@ -350,7 +351,7 @@ func TestConvertToMetadataInstance(t *testing.T) {
Value: aws.String("name-1"), Value: aws.String("name-1"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
}, },
@ -377,7 +378,7 @@ func TestConvertToMetadataInstance(t *testing.T) {
Value: aws.String("name-1"), Value: aws.String("name-1"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
}, },
@ -417,7 +418,7 @@ func TestConvertToMetadataInstance(t *testing.T) {
Value: aws.String("name-1"), Value: aws.String("name-1"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
}, },
@ -439,7 +440,7 @@ func TestConvertToMetadataInstance(t *testing.T) {
Value: aws.String("name-1"), Value: aws.String("name-1"),
}, },
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
}, },
@ -458,7 +459,7 @@ func TestConvertToMetadataInstance(t *testing.T) {
}, },
Tags: []types.Tag{ Tags: []types.Tag{
{ {
Key: aws.String(tagRole), Key: aws.String(cloud.TagRole),
Value: aws.String("controlplane"), Value: aws.String("controlplane"),
}, },
}, },

View file

@ -15,6 +15,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
) )
@ -91,7 +92,7 @@ func (c *imdsClient) UID(ctx context.Context) (string, error) {
} }
for _, tag := range c.cache.Compute.Tags { for _, tag := range c.cache.Compute.Tags {
if tag.Name == "constellation-uid" { if tag.Name == cloud.TagUID {
return tag.Value, nil return tag.Value, nil
} }
} }
@ -107,7 +108,7 @@ func (c *imdsClient) Role(ctx context.Context) (role.Role, error) {
} }
for _, tag := range c.cache.Compute.Tags { for _, tag := range c.cache.Compute.Tags {
if tag.Name == "role" { if tag.Name == cloud.TagRole {
return role.FromString(tag.Value), nil return role.FromString(tag.Value), nil
} }
} }

View file

@ -15,6 +15,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/grpc/test/bufconn" "google.golang.org/grpc/test/bufconn"
@ -22,8 +23,8 @@ import (
func TestIMDSClient(t *testing.T) { func TestIMDSClient(t *testing.T) {
uidTags := []metadataTag{ uidTags := []metadataTag{
{Name: "constellation-uid", Value: "uid"}, {Name: cloud.TagUID, Value: "uid"},
{Name: "role", Value: "worker"}, {Name: cloud.TagRole, Value: "worker"},
} }
response := metadataResponse{ response := metadataResponse{
Compute: metadataResponseCompute{ Compute: metadataResponseCompute{
@ -48,14 +49,14 @@ func TestIMDSClient(t *testing.T) {
Compute: metadataResponseCompute{ Compute: metadataResponseCompute{
ResourceID: "resource-id", ResourceID: "resource-id",
ResourceGroup: "resource-group", ResourceGroup: "resource-group",
Tags: []metadataTag{{Name: "role", Value: "worker"}}, Tags: []metadataTag{{Name: cloud.TagRole, Value: "worker"}},
}, },
} }
responseWithoutRole := metadataResponse{ responseWithoutRole := metadataResponse{
Compute: metadataResponseCompute{ Compute: metadataResponseCompute{
ResourceID: "resource-id", ResourceID: "resource-id",
ResourceGroup: "resource-group", ResourceGroup: "resource-group",
Tags: []metadataTag{{Name: "constellation-uid", Value: "uid"}}, Tags: []metadataTag{{Name: cloud.TagUID, Value: "uid"}},
}, },
} }

View file

@ -17,6 +17,7 @@ import (
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
) )
@ -301,7 +302,7 @@ func (m *Metadata) getAppInsights(ctx context.Context) (*armapplicationinsights.
continue continue
} }
tag, ok := component.Tags["constellation-uid"] tag, ok := component.Tags[cloud.TagUID]
if !ok || tag == nil { if !ok || tag == nil {
continue continue
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -654,8 +655,8 @@ func newScaleSetsStub() *stubScaleSetsAPI {
list: []armcomputev2.VirtualMachineScaleSet{{ list: []armcomputev2.VirtualMachineScaleSet{{
Name: to.Ptr("scale-set-name"), Name: to.Ptr("scale-set-name"),
Tags: map[string]*string{ Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"), cloud.TagUID: to.Ptr("uid"),
"role": to.Ptr("worker"), cloud.TagRole: to.Ptr("worker"),
}, },
}}, }},
}, },
@ -691,8 +692,8 @@ func newVirtualMachineScaleSetsVMsStub() *stubVirtualMachineScaleSetVMsAPI {
}, },
}, },
Tags: map[string]*string{ Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"), cloud.TagUID: to.Ptr("uid"),
"role": to.Ptr("worker"), cloud.TagRole: to.Ptr("worker"),
}, },
}, },
pager: &stubVirtualMachineScaleSetVMPager{ pager: &stubVirtualMachineScaleSetVMPager{
@ -724,8 +725,8 @@ func newVirtualMachineScaleSetsVMsStub() *stubVirtualMachineScaleSetVMsAPI {
}, },
}, },
Tags: map[string]*string{ Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"), cloud.TagUID: to.Ptr("uid"),
"role": to.Ptr("worker"), cloud.TagRole: to.Ptr("worker"),
}, },
}, },
}, },

View file

@ -16,6 +16,7 @@ import (
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/v2/internal/azureshared" "github.com/edgelesssys/constellation/v2/internal/azureshared"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
) )
@ -117,7 +118,7 @@ func extractScaleSetVMRole(tags map[string]*string) role.Role {
if tags == nil { if tags == nil {
return role.Unknown return role.Unknown
} }
roleStr, ok := tags["role"] roleStr, ok := tags[cloud.TagRole]
if !ok { if !ok {
return role.Unknown return role.Unknown
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -223,15 +224,15 @@ func TestExtractScaleSetVMRole(t *testing.T) {
wantRole role.Role wantRole role.Role
}{ }{
"control-plane role": { "control-plane role": {
tags: map[string]*string{"role": to.Ptr("control-plane")}, tags: map[string]*string{cloud.TagRole: to.Ptr("control-plane")},
wantRole: role.ControlPlane, wantRole: role.ControlPlane,
}, },
"worker role": { "worker role": {
tags: map[string]*string{"role": to.Ptr("worker")}, tags: map[string]*string{cloud.TagRole: to.Ptr("worker")},
wantRole: role.Worker, wantRole: role.Worker,
}, },
"unknown role": { "unknown role": {
tags: map[string]*string{"role": to.Ptr("foo")}, tags: map[string]*string{cloud.TagRole: to.Ptr("foo")},
wantRole: role.Unknown, wantRole: role.Unknown,
}, },
"no role": { "no role": {
@ -239,7 +240,7 @@ func TestExtractScaleSetVMRole(t *testing.T) {
wantRole: role.Unknown, wantRole: role.Unknown,
}, },
"nil role": { "nil role": {
tags: map[string]*string{"role": nil}, tags: map[string]*string{cloud.TagRole: nil},
wantRole: role.Unknown, wantRole: role.Unknown,
}, },
"nil tags": { "nil tags": {
@ -280,7 +281,7 @@ func newListContainingNilScaleSetVirtualMachinesStub() *stubVirtualMachineScaleS
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"), ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id"),
InstanceID: to.Ptr("instance-id"), InstanceID: to.Ptr("instance-id"),
Tags: map[string]*string{ Tags: map[string]*string{
"role": to.Ptr("worker"), cloud.TagRole: to.Ptr("worker"),
}, },
Properties: &armcomputev2.VirtualMachineScaleSetVMProperties{ Properties: &armcomputev2.VirtualMachineScaleSetVMProperties{
NetworkProfile: &armcomputev2.NetworkProfile{ NetworkProfile: &armcomputev2.NetworkProfile{

14
internal/cloud/cloud.go Normal file
View file

@ -0,0 +1,14 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloud
const (
// TagRole is the tag/label key used to identify the role of a node.
TagRole = "constellation-role"
// TagUID is the tag/label key used to identify the UID of a cluster.
TagUID = "constellation-uid"
)

View file

@ -35,6 +35,7 @@ type forwardingRulesAPI interface {
type metadataAPI interface { type metadataAPI interface {
InstanceAttributeValue(attr string) (string, error) InstanceAttributeValue(attr string) (string, error)
InstanceID() (string, error)
ProjectID() (string, error) ProjectID() (string, error)
Zone() (string, error) Zone() (string, error)
InstanceName() (string, error) InstanceName() (string, error)

View file

@ -26,8 +26,8 @@ type CloudControllerManager struct {
} }
// NewCloudControllerManager returns an initialized cloud controller manager configuration struct for GCP. // NewCloudControllerManager returns an initialized cloud controller manager configuration struct for GCP.
func NewCloudControllerManager(metadata *Metadata) (*CloudControllerManager, error) { func NewCloudControllerManager(ctx context.Context, metadata *Metadata) (*CloudControllerManager, error) {
uid, err := metadata.api.UID() uid, err := metadata.api.UID(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("getting uid from metadata: %w", err) return nil, fmt.Errorf("getting uid from metadata: %w", err)
} }

View file

@ -15,8 +15,10 @@ import (
"strings" "strings"
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/gcpshared" "github.com/edgelesssys/constellation/v2/internal/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/role"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@ -24,7 +26,6 @@ import (
const ( const (
gcpSSHMetadataKey = "ssh-keys" gcpSSHMetadataKey = "ssh-keys"
constellationUIDMetadataKey = "constellation-uid"
) )
var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])") var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])")
@ -61,11 +62,12 @@ func NewClient(ctx context.Context) (*Client, error) {
// RetrieveInstances returns list of instances including their ips and metadata. // RetrieveInstances returns list of instances including their ips and metadata.
func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) { func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) {
uid, err := c.UID() uid, err := c.UID(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req := &computepb.ListInstancesRequest{ req := &computepb.ListInstancesRequest{
Filter: proto.String(fmt.Sprintf("labels.%s:%s", cloud.TagUID, uid)),
Project: project, Project: project,
Zone: zone, Zone: zone,
} }
@ -80,11 +82,6 @@ func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([
if err != nil { if err != nil {
return nil, fmt.Errorf("retrieving instance list from compute API client: %w", err) return nil, fmt.Errorf("retrieving instance list from compute API client: %w", err)
} }
metadata := extractInstanceMetadata(resp.Metadata, "", false)
// skip instances not belonging to the current constellation
if instanceUID, ok := metadata[constellationUIDMetadataKey]; !ok || instanceUID != uid {
continue
}
instance, err := convertToCoreInstance(resp, project, zone) instance, err := convertToCoreInstance(resp, project, zone)
if err != nil { if err != nil {
return nil, err return nil, err
@ -223,7 +220,7 @@ func (c *Client) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone,
// RetrieveLoadBalancerEndpoint returns the endpoint of the load balancer with the constellation-uid tag. // RetrieveLoadBalancerEndpoint returns the endpoint of the load balancer with the constellation-uid tag.
func (c *Client) RetrieveLoadBalancerEndpoint(ctx context.Context, project string) (string, error) { func (c *Client) RetrieveLoadBalancerEndpoint(ctx context.Context, project string) (string, error) {
uid, err := c.UID() uid, err := c.UID(ctx)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -240,7 +237,7 @@ func (c *Client) RetrieveLoadBalancerEndpoint(ctx context.Context, project strin
if err != nil { if err != nil {
return "", fmt.Errorf("retrieving load balancer IP failed: %w", err) return "", fmt.Errorf("retrieving load balancer IP failed: %w", err)
} }
if resp.Labels["constellation-uid"] == uid { if resp.Labels[cloud.TagUID] == uid && resp.Labels["constellation-use"] == "kubernetes" {
if resp.PortRange == nil { if resp.PortRange == nil {
return "", errors.New("load balancer with searched UID has no ports") return "", errors.New("load balancer with searched UID has no ports")
} }
@ -292,13 +289,30 @@ func (c *Client) updateInstanceMetadata(ctx context.Context, project, zone, inst
} }
// UID retrieves the current instances uid. // UID retrieves the current instances uid.
func (c *Client) UID() (string, error) { func (c *Client) UID(ctx context.Context) (string, error) {
// API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid // API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid
uid, err := c.RetrieveInstanceMetadata(constellationUIDMetadataKey) instanceID, err := c.InstanceID()
if err != nil { if err != nil {
return "", fmt.Errorf("retrieving constellation uid: %w", err) return "", fmt.Errorf("retrieving instance ID: %w", err)
} }
return uid, nil project, err := c.ProjectID()
if err != nil {
return "", fmt.Errorf("retrieving project ID: %w", err)
}
zone, err := c.Zone()
if err != nil {
return "", fmt.Errorf("retrieving zone: %w", err)
}
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
Project: project,
Zone: zone,
Instance: instanceID,
})
if err != nil {
return "", fmt.Errorf("retrieving instance labels: %w", err)
}
return instance.Labels[cloud.TagUID], nil
} }
// extractVPCIP extracts the primary private IP from a list of interfaces. // extractVPCIP extracts the primary private IP from a list of interfaces.
@ -385,7 +399,7 @@ func convertToCoreInstance(in *computepb.Instance, project string, zone string)
return metadata.InstanceMetadata{ return metadata.InstanceMetadata{
Name: *in.Name, Name: *in.Name,
ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name), ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name),
Role: extractRole(mdata), Role: role.FromString(in.Labels[cloud.TagRole]),
VPCIP: extractVPCIP(in.NetworkInterfaces), VPCIP: extractVPCIP(in.NetworkInterfaces),
PublicIP: extractPublicIP(in.NetworkInterfaces), PublicIP: extractPublicIP(in.NetworkInterfaces),
AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces), AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces),

View file

@ -12,6 +12,7 @@ import (
"testing" "testing"
compute "cloud.google.com/go/compute/apiv1" compute "cloud.google.com/go/compute/apiv1"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/role"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
@ -38,6 +39,10 @@ func TestRetrieveInstances(t *testing.T) {
instances: []*computepb.Instance{ instances: []*computepb.Instance{
{ {
Name: proto.String("someInstance"), Name: proto.String("someInstance"),
Labels: map[string]string{
cloud.TagRole: role.ControlPlane.String(),
cloud.TagUID: uid,
},
Metadata: &computepb.Metadata{ Metadata: &computepb.Metadata{
Items: []*computepb.Items{ Items: []*computepb.Items{
{ {
@ -48,14 +53,6 @@ func TestRetrieveInstances(t *testing.T) {
Key: proto.String("key-2"), Key: proto.String("key-2"),
Value: proto.String("value-2"), Value: proto.String("value-2"),
}, },
{
Key: proto.String(constellationUIDMetadataKey),
Value: proto.String(uid),
},
{
Key: proto.String(roleMetadataKey),
Value: proto.String(role.ControlPlane.String()),
},
}, },
}, },
NetworkInterfaces: []*computepb.NetworkInterface{ NetworkInterfaces: []*computepb.NetworkInterface{
@ -70,6 +67,13 @@ func TestRetrieveInstances(t *testing.T) {
}, },
} }
} }
instance := &computepb.Instance{
Name: proto.String("instance"),
Labels: map[string]string{
cloud.TagRole: role.ControlPlane.String(),
cloud.TagUID: uid,
},
}
testCases := map[string]struct { testCases := map[string]struct {
client stubInstancesClient client stubInstancesClient
@ -80,7 +84,7 @@ func TestRetrieveInstances(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"retrieve works": { "retrieve works": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
wantInstances: []metadata.InstanceMetadata{ wantInstances: []metadata.InstanceMetadata{
@ -96,14 +100,14 @@ func TestRetrieveInstances(t *testing.T) {
}, },
}, },
"instance name is null": { "instance name is null": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Name = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Name = nil },
wantErr: true, wantErr: true,
}, },
"no instance with network ip": { "no instance with network ip": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces = nil },
@ -120,7 +124,7 @@ func TestRetrieveInstances(t *testing.T) {
}, },
}, },
"network ip is nil": { "network ip is nil": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil }, instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].NetworkInterfaces[0].NetworkIP = nil },
@ -136,24 +140,17 @@ func TestRetrieveInstances(t *testing.T) {
}, },
}, },
}, },
"constellation id is not set": {
client: stubInstancesClient{},
metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[2].Key = proto.String("") },
wantInstances: []metadata.InstanceMetadata{},
},
"constellation retrieval fails": { "constellation retrieval fails": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceErr: someErr}, metadata: stubMetadataClient{instanceIDErr: someErr},
instanceIter: newTestIter(), instanceIter: newTestIter(),
wantErr: true, wantErr: true,
}, },
"role is not set": { "role is not set": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: newTestIter(), instanceIter: newTestIter(),
instanceIterMutator: func(sii *stubInstanceIterator) { sii.instances[0].Metadata.Items[3].Key = proto.String("") }, instanceIterMutator: func(sii *stubInstanceIterator) { delete(sii.instances[0].Labels, cloud.TagRole) },
wantInstances: []metadata.InstanceMetadata{ wantInstances: []metadata.InstanceMetadata{
{ {
Name: "someInstance", Name: "someInstance",
@ -167,7 +164,7 @@ func TestRetrieveInstances(t *testing.T) {
}, },
}, },
"instance iterator Next() errors": { "instance iterator Next() errors": {
client: stubInstancesClient{}, client: stubInstancesClient{GetInstance: instance},
metadata: stubMetadataClient{InstanceValue: uid}, metadata: stubMetadataClient{InstanceValue: uid},
instanceIter: &stubInstanceIterator{nextErr: someErr}, instanceIter: &stubInstanceIterator{nextErr: someErr},
wantErr: true, wantErr: true,
@ -204,6 +201,7 @@ func TestRetrieveInstance(t *testing.T) {
newTestInstance := func() *computepb.Instance { newTestInstance := func() *computepb.Instance {
return &computepb.Instance{ return &computepb.Instance{
Name: proto.String("someInstance"), Name: proto.String("someInstance"),
Labels: map[string]string{},
Metadata: &computepb.Metadata{ Metadata: &computepb.Metadata{
Items: []*computepb.Items{ Items: []*computepb.Items{
{ {
@ -266,8 +264,7 @@ func TestRetrieveInstance(t *testing.T) {
client: stubInstancesClient{}, client: stubInstancesClient{},
clientInstance: newTestInstance(), clientInstance: newTestInstance(),
clientInstanceMutator: func(i *computepb.Instance) { clientInstanceMutator: func(i *computepb.Instance) {
i.Metadata.Items[0].Key = proto.String(roleMetadataKey) i.Labels[cloud.TagRole] = role.ControlPlane.String()
i.Metadata.Items[0].Value = proto.String(role.ControlPlane.String())
}, },
wantInstance: metadata.InstanceMetadata{ wantInstance: metadata.InstanceMetadata{
Name: "someInstance", Name: "someInstance",
@ -782,22 +779,31 @@ func TestRetrieveSubnetworkAliasCIDR(t *testing.T) {
func TestRetrieveLoadBalancerEndpoint(t *testing.T) { func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
loadBalancerIP := "192.0.2.1" loadBalancerIP := "192.0.2.1"
uid := "uid" uid := "uid"
use := "kubernetes"
someErr := errors.New("some error") someErr := errors.New("some error")
instance := &computepb.Instance{
Labels: map[string]string{
cloud.TagUID: uid,
},
}
testCases := map[string]struct { testCases := map[string]struct {
instanceAPI stubInstancesClient
stubForwardingRulesClient stubForwardingRulesClient stubForwardingRulesClient stubForwardingRulesClient
stubMetadataClient stubMetadataClient stubMetadataClient stubMetadataClient
wantLoadBalancerIP string wantLoadBalancerIP string
wantErr bool wantErr bool
}{ }{
"works": { "works": {
stubMetadataClient: stubMetadataClient{InstanceValue: uid}, instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{},
stubForwardingRulesClient: stubForwardingRulesClient{ stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{ ForwardingRuleIterator: &stubForwardingRuleIterator{
rules: []*computepb.ForwardingRule{ rules: []*computepb.ForwardingRule{
{ {
IPAddress: proto.String(loadBalancerIP), IPAddress: proto.String(loadBalancerIP),
PortRange: proto.String("100-100"), PortRange: proto.String("100-100"),
Labels: map[string]string{"constellation-uid": uid}, Labels: map[string]string{cloud.TagUID: uid, "constellation-use": use},
}, },
}, },
}, },
@ -805,7 +811,8 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
wantLoadBalancerIP: loadBalancerIP, wantLoadBalancerIP: loadBalancerIP,
}, },
"fails when no matching load balancers exists": { "fails when no matching load balancers exists": {
stubMetadataClient: stubMetadataClient{InstanceValue: uid}, instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{},
stubForwardingRulesClient: stubForwardingRulesClient{ stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{ ForwardingRuleIterator: &stubForwardingRuleIterator{
rules: []*computepb.ForwardingRule{ rules: []*computepb.ForwardingRule{
@ -819,14 +826,15 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"fails when retrieving uid": { "fails when retrieving uid": {
stubMetadataClient: stubMetadataClient{InstanceErr: someErr}, instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{instanceIDErr: someErr},
stubForwardingRulesClient: stubForwardingRulesClient{ stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{ ForwardingRuleIterator: &stubForwardingRuleIterator{
rules: []*computepb.ForwardingRule{ rules: []*computepb.ForwardingRule{
{ {
IPAddress: proto.String(loadBalancerIP), IPAddress: proto.String(loadBalancerIP),
PortRange: proto.String("100-100"), PortRange: proto.String("100-100"),
Labels: map[string]string{"constellation-uid": uid}, Labels: map[string]string{cloud.TagUID: uid, "constellation-use": use},
}, },
}, },
}, },
@ -834,13 +842,14 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"fails when answer has empty port range": { "fails when answer has empty port range": {
stubMetadataClient: stubMetadataClient{InstanceErr: someErr}, instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{},
stubForwardingRulesClient: stubForwardingRulesClient{ stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{ ForwardingRuleIterator: &stubForwardingRuleIterator{
rules: []*computepb.ForwardingRule{ rules: []*computepb.ForwardingRule{
{ {
IPAddress: proto.String(loadBalancerIP), IPAddress: proto.String(loadBalancerIP),
Labels: map[string]string{"constellation-uid": uid}, Labels: map[string]string{cloud.TagUID: uid, "constellation-use": use},
}, },
}, },
}, },
@ -848,6 +857,7 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"fails when retrieving loadbalancer IP": { "fails when retrieving loadbalancer IP": {
instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{}, stubMetadataClient: stubMetadataClient{},
stubForwardingRulesClient: stubForwardingRulesClient{ stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{ ForwardingRuleIterator: &stubForwardingRuleIterator{
@ -856,7 +866,23 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
{ {
IPAddress: proto.String(loadBalancerIP), IPAddress: proto.String(loadBalancerIP),
PortRange: proto.String("100-100"), PortRange: proto.String("100-100"),
Labels: map[string]string{"constellation-uid": uid}, Labels: map[string]string{cloud.TagUID: uid, "constellation-use": use},
},
},
},
},
wantErr: true,
},
"fails on incorrect use label": {
instanceAPI: stubInstancesClient{GetInstance: instance},
stubMetadataClient: stubMetadataClient{InstanceValue: uid},
stubForwardingRulesClient: stubForwardingRulesClient{
ForwardingRuleIterator: &stubForwardingRuleIterator{
rules: []*computepb.ForwardingRule{
{
IPAddress: proto.String(loadBalancerIP),
PortRange: proto.String("100-100"),
Labels: map[string]string{cloud.TagUID: uid, "constellation-use": "bootstrapper"},
}, },
}, },
}, },
@ -869,7 +895,7 @@ func TestRetrieveLoadBalancerEndpoint(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
client := Client{forwardingRulesAPI: tc.stubForwardingRulesClient, metadataAPI: tc.stubMetadataClient} client := Client{instanceAPI: tc.instanceAPI, forwardingRulesAPI: tc.stubForwardingRulesClient, metadataAPI: tc.stubMetadataClient}
aliasCIDR, err := client.RetrieveLoadBalancerEndpoint(context.Background(), "project") aliasCIDR, err := client.RetrieveLoadBalancerEndpoint(context.Background(), "project")
if tc.wantErr { if tc.wantErr {
@ -1049,6 +1075,8 @@ func (s stubForwardingRulesClient) Close() error {
type stubMetadataClient struct { type stubMetadataClient struct {
InstanceValue string InstanceValue string
InstanceErr error InstanceErr error
instanceIDValue string
instanceIDErr error
ProjectIDValue string ProjectIDValue string
ProjectIDErr error ProjectIDErr error
ZoneValue string ZoneValue string
@ -1061,6 +1089,10 @@ func (s stubMetadataClient) InstanceAttributeValue(attr string) (string, error)
return s.InstanceValue, s.InstanceErr return s.InstanceValue, s.InstanceErr
} }
func (s stubMetadataClient) InstanceID() (string, error) {
return s.instanceIDValue, s.instanceIDErr
}
func (s stubMetadataClient) ProjectID() (string, error) { func (s stubMetadataClient) ProjectID() (string, error) {
return s.ProjectIDValue, s.ProjectIDErr return s.ProjectIDValue, s.ProjectIDErr
} }

View file

@ -17,7 +17,7 @@ import (
// API handles all GCP API requests. // API handles all GCP API requests.
type API interface { type API interface {
// UID retrieves the current instances uid. // UID retrieves the current instances uid.
UID() (string, error) UID(context.Context) (string, error)
// RetrieveInstances retrieves a list of all accessible GCP instances with their metadata. // RetrieveInstances retrieves a list of all accessible GCP instances with their metadata.
RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error)
// RetrieveInstances retrieves a single GCP instances with its metadata. // RetrieveInstances retrieves a single GCP instances with its metadata.
@ -128,7 +128,7 @@ func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error)
// UID retrieves the UID of the constellation. // UID retrieves the UID of the constellation.
func (m *Metadata) UID(ctx context.Context) (string, error) { func (m *Metadata) UID(ctx context.Context) (string, error) {
return m.api.UID() return m.api.UID(ctx)
} }
// Supported is used to determine if metadata API is implemented for this cloud provider. // Supported is used to determine if metadata API is implemented for this cloud provider.

View file

@ -11,6 +11,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -41,7 +42,7 @@ func TestList(t *testing.T) {
projectID: "someProjectID", projectID: "someProjectID",
zone: "someZone", zone: "someZone",
retrieveInstanceMetadaValues: map[string]string{ retrieveInstanceMetadaValues: map[string]string{
"constellation-uid": uid, cloud.TagUID: uid,
}, },
}, },
instancesGenerator: instancesGenerator, instancesGenerator: instancesGenerator,
@ -58,7 +59,7 @@ func TestList(t *testing.T) {
projectID: "someProjectID", projectID: "someProjectID",
zone: "someZone", zone: "someZone",
retrieveInstanceMetadaValues: map[string]string{ retrieveInstanceMetadaValues: map[string]string{
"constellation-uid": uid, cloud.TagUID: uid,
}, },
retrieveInstancesErr: err, retrieveInstancesErr: err,
}, },
@ -133,7 +134,7 @@ func TestSelf(t *testing.T) {
projectID: "someProjectID", projectID: "someProjectID",
zone: "someZone", zone: "someZone",
retrieveInstanceMetadaValues: map[string]string{ retrieveInstanceMetadaValues: map[string]string{
"constellation-uid": uid, cloud.TagUID: uid,
}, },
retrieveInstanceErr: err, retrieveInstanceErr: err,
}, },
@ -297,7 +298,7 @@ func (s *stubGCPClient) RetrieveLoadBalancerEndpoint(ctx context.Context, projec
return s.loadBalancerIP, s.retrieveLoadBalancerErr return s.loadBalancerIP, s.retrieveLoadBalancerErr
} }
func (s *stubGCPClient) UID() (string, error) { func (s *stubGCPClient) UID(context.Context) (string, error) {
return s.retrieveUIDValue, s.retrieveUIDErr return s.retrieveUIDValue, s.retrieveUIDErr
} }

View file

@ -1,25 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
import (
"github.com/edgelesssys/constellation/v2/internal/role"
)
const roleMetadataKey = "constellation-role"
// extractRole extracts role from cloud provider metadata.
func extractRole(metadata map[string]string) role.Role {
switch metadata[roleMetadataKey] {
case role.ControlPlane.String():
return role.ControlPlane
case role.Worker.String():
return role.Worker
default:
return role.Unknown
}
}

View file

@ -1,53 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
import (
"testing"
"github.com/edgelesssys/constellation/v2/internal/role"
"github.com/stretchr/testify/assert"
)
func TestExtractRole(t *testing.T) {
testCases := map[string]struct {
metadata map[string]string
wantRole role.Role
}{
"bootstrapper role": {
metadata: map[string]string{
roleMetadataKey: role.ControlPlane.String(),
},
wantRole: role.ControlPlane,
},
"node role": {
metadata: map[string]string{
roleMetadataKey: role.Worker.String(),
},
wantRole: role.Worker,
},
"unknown role": {
metadata: map[string]string{
roleMetadataKey: "some-unknown-role",
},
wantRole: role.Unknown,
},
"no role": {
wantRole: role.Unknown,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
role := extractRole(tc.metadata)
assert.Equal(tc.wantRole, role)
})
}
}

View file

@ -69,6 +69,10 @@ func (c *metadataClient) InstanceAttributeValue(attr string) (string, error) {
return metadata.InstanceAttributeValue(attr) return metadata.InstanceAttributeValue(attr)
} }
func (c *metadataClient) InstanceID() (string, error) {
return metadata.InstanceID()
}
func (c *metadataClient) ProjectID() (string, error) { func (c *metadataClient) ProjectID() (string, error) {
return metadata.ProjectID() return metadata.ProjectID()
} }

View file

@ -52,7 +52,7 @@ const (
VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v2.1.0@sha256:7a1e6bec4cda270924c3495466fa536a2b6cd2d2f9c0be319fc6368710c255e8" VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v2.1.0@sha256:7a1e6bec4cda270924c3495466fa536a2b6cd2d2f9c0be319fc6368710c255e8"
// Check for new versions at https://github.com/GoogleCloudPlatform/guest-agent/releases and update in /.github/workflows/build-gcp-guest-agent.yml. // Check for new versions at https://github.com/GoogleCloudPlatform/guest-agent/releases and update in /.github/workflows/build-gcp-guest-agent.yml.
GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:20220927.00@sha256:3dea1ae3f162d2353e6584b325f0e325a39cda5f380f41e5a0ee43c6641d3905" GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:20220927.00@sha256:3dea1ae3f162d2353e6584b325f0e325a39cda5f380f41e5a0ee43c6641d3905"
NodeOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-operator-catalog:v2.2.0-pre.0.20221012150059-4b2dd1317a77@sha256:f840435fe3a7669afe78aa12b0ba7f36a0087dc6d86d4fe3f3e340395f002e3f" NodeOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-operator-catalog:v2.2.0-pre.0.20221021130530-c6623f2ebd72@sha256:f2750cb3c0f8368808686ffd5835b6d7152ae2845e35ee9891e910738dc26718"
// TODO: switch node maintenance operator catalog back to upstream quay.io/medik8s/node-maintenance-operator-catalog // TODO: switch node maintenance operator catalog back to upstream quay.io/medik8s/node-maintenance-operator-catalog
// once https://github.com/medik8s/node-maintenance-operator/issues/49 is resolved. // once https://github.com/medik8s/node-maintenance-operator/issues/49 is resolved.
NodeMaintenanceOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-maintenance-operator-catalog:v0.13.1-alpha1@sha256:d382c3aaf9bc470cde6f6c05c2c6ff5c9dcfd90540d5b11f9cf69c4e1dd1ca9d" NodeMaintenanceOperatorCatalogImage = "ghcr.io/edgelesssys/constellation/node-maintenance-operator-catalog:v0.13.1-alpha1@sha256:d382c3aaf9bc470cde6f6c05c2c6ff5c9dcfd90540d5b11f9cf69c4e1dd1ca9d"

View file

@ -1,32 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"fmt"
)
// getScaleSets retrieves the IDs of all scale sets of a resource group.
func (c *Client) getScaleSets(ctx context.Context) ([]string, error) {
pager := c.scaleSetsAPI.NewListPager(c.config.ResourceGroup, nil)
var scaleSets []string
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("paging scale sets: %w", err)
}
for _, scaleSet := range page.Value {
if scaleSet == nil || scaleSet.ID == nil {
continue
}
scaleSets = append(scaleSets, *scaleSet.ID)
}
}
return scaleSets, nil
}

View file

@ -1,62 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"errors"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetScaleSets(t *testing.T) {
testCases := map[string]struct {
scaleSet armcomputev2.VirtualMachineScaleSet
fetchPageErr error
wantScaleSets []string
wantErr bool
}{
"fetching scale sets works": {
scaleSet: armcomputev2.VirtualMachineScaleSet{
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name"),
},
wantScaleSets: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name"},
},
"fetching scale sets fails": {
fetchPageErr: errors.New("fetch page error"),
wantErr: true,
},
"scale set is invalid": {},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
client := Client{
scaleSetsAPI: &stubScaleSetsAPI{
pager: &stubVMSSPager{
list: []armcomputev2.VirtualMachineScaleSet{tc.scaleSet},
fetchErr: tc.fetchPageErr,
},
},
}
gotScaleSets, err := client.getScaleSets(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.ElementsMatch(tc.wantScaleSets, gotScaleSets)
})
}
}

View file

@ -80,34 +80,35 @@ func (c *Client) GetAutoscalingGroupName(scalingGroupID string) (string, error)
// ListScalingGroups retrieves a list of scaling groups for the cluster. // ListScalingGroups retrieves a list of scaling groups for the cluster.
func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlaneGroupIDs []string, workerGroupIDs []string, err error) { func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlaneGroupIDs []string, workerGroupIDs []string, err error) {
scaleSetIDs, err := c.getScaleSets(ctx) pager := c.scaleSetsAPI.NewListPager(c.config.ResourceGroup, nil)
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("listing scaling groups: %w", err) return nil, nil, fmt.Errorf("paging scale sets: %w", err)
} }
for _, scaleSetID := range scaleSetIDs { for _, scaleSet := range page.Value {
_, _, scaleSet, err := splitVMSSID(scaleSetID) if scaleSet == nil || scaleSet.ID == nil {
continue
}
if scaleSet.Tags == nil || scaleSet.Tags["constellation-uid"] == nil || *scaleSet.Tags["constellation-uid"] != uid {
continue
}
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("getting scaling group name: %w", err) return nil, nil, fmt.Errorf("getting scaling group name: %w", err)
} }
if isControlPlaneInstanceGroup(scaleSet) { switch *scaleSet.Tags["constellation-role"] {
controlPlaneGroupIDs = append(controlPlaneGroupIDs, scaleSetID) case "control-plane", "controlplane":
} else if isWorkerInstanceGroup(scaleSet) { controlPlaneGroupIDs = append(controlPlaneGroupIDs, *scaleSet.ID)
workerGroupIDs = append(workerGroupIDs, scaleSetID) case "worker":
workerGroupIDs = append(workerGroupIDs, *scaleSet.ID)
}
} }
} }
return controlPlaneGroupIDs, workerGroupIDs, nil return controlPlaneGroupIDs, workerGroupIDs, nil
} }
// isControlPlaneInstanceGroup returns true if the instance group is a control plane instance group.
func isControlPlaneInstanceGroup(instanceGroupName string) bool {
return strings.Contains(instanceGroupName, "control-plane")
}
// isWorkerInstanceGroup returns true if the instance group is a worker instance group.
func isWorkerInstanceGroup(instanceGroupName string) bool {
return strings.Contains(instanceGroupName, "worker")
}
func imageReferenceFromImage(img string) *armcompute.ImageReference { func imageReferenceFromImage(img string) *armcompute.ImageReference {
ref := &armcompute.ImageReference{} ref := &armcompute.ImageReference{}

View file

@ -193,15 +193,33 @@ func TestListScalingGroups(t *testing.T) {
"listing control-plane works": { "listing control-plane works": {
scaleSet: armcomputev2.VirtualMachineScaleSet{ scaleSet: armcomputev2.VirtualMachineScaleSet{
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-control-planes-uid"), ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-control-planes-uid"),
Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"),
"constellation-role": to.Ptr("control-plane"),
},
}, },
wantControlPlanes: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-control-planes-uid"}, wantControlPlanes: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-control-planes-uid"},
}, },
"listing worker works": { "listing worker works": {
scaleSet: armcomputev2.VirtualMachineScaleSet{ scaleSet: armcomputev2.VirtualMachineScaleSet{
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-workers-uid"), ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-workers-uid"),
Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"),
"constellation-role": to.Ptr("worker"),
},
}, },
wantWorkers: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-workers-uid"}, wantWorkers: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/constellation-scale-set-workers-uid"},
}, },
"listing is not dependent on resource name": {
scaleSet: armcomputev2.VirtualMachineScaleSet{
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/some-scale-set"),
Tags: map[string]*string{
"constellation-uid": to.Ptr("uid"),
"constellation-role": to.Ptr("control-plane"),
},
},
wantControlPlanes: []string{"/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/some-scale-set"},
},
"listing other works": { "listing other works": {
scaleSet: armcomputev2.VirtualMachineScaleSet{ scaleSet: armcomputev2.VirtualMachineScaleSet{
ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/other"), ID: to.Ptr("/subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/other"),

View file

@ -10,7 +10,6 @@ import (
"context" "context"
"fmt" "fmt"
"regexp" "regexp"
"strings"
) )
var instanceGroupIDRegex = regexp.MustCompile(`^projects/([^/]+)/zones/([^/]+)/instanceGroupManagers/([^/]+)$`) var instanceGroupIDRegex = regexp.MustCompile(`^projects/([^/]+)/zones/([^/]+)/instanceGroupManagers/([^/]+)$`)
@ -36,16 +35,6 @@ func splitInstanceGroupID(instanceGroupID string) (project, zone, instanceGroup
return matches[1], matches[2], matches[3], nil return matches[1], matches[2], matches[3], nil
} }
// isControlPlaneInstanceGroup returns true if the instance group is a control plane instance group.
func isControlPlaneInstanceGroup(instanceGroupName string) bool {
return strings.Contains(instanceGroupName, "control-plane")
}
// isWorkerInstanceGroup returns true if the instance group is a worker instance group.
func isWorkerInstanceGroup(instanceGroupName string) bool {
return strings.Contains(instanceGroupName, "worker")
}
// generateInstanceName generates a random instance name. // generateInstanceName generates a random instance name.
func generateInstanceName(baseInstanceName string, random prng) string { func generateInstanceName(baseInstanceName string, random prng) string {
letters := []byte("abcdefghijklmnopqrstuvwxyz0123456789") letters := []byte("abcdefghijklmnopqrstuvwxyz0123456789")

View file

@ -10,10 +10,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"google.golang.org/protobuf/proto"
) )
// GetScalingGroupImage returns the image URI of the scaling group. // GetScalingGroupImage returns the image URI of the scaling group.
@ -108,7 +108,6 @@ func (c *Client) GetAutoscalingGroupName(scalingGroupID string) (string, error)
// ListScalingGroups retrieves a list of scaling groups for the cluster. // ListScalingGroups retrieves a list of scaling groups for the cluster.
func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlaneGroupIDs []string, workerGroupIDs []string, err error) { func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlaneGroupIDs []string, workerGroupIDs []string, err error) {
iter := c.instanceGroupManagersAPI.AggregatedList(ctx, &computepb.AggregatedListInstanceGroupManagersRequest{ iter := c.instanceGroupManagersAPI.AggregatedList(ctx, &computepb.AggregatedListInstanceGroupManagersRequest{
Filter: proto.String(fmt.Sprintf("name eq \".+-%s-.+\"", uid)), // filter by constellation UID
Project: c.projectID, Project: c.projectID,
}) })
for instanceGroupManagerScopedListPair, err := iter.Next(); ; instanceGroupManagerScopedListPair, err = iter.Next() { for instanceGroupManagerScopedListPair, err := iter.Next(); ; instanceGroupManagerScopedListPair, err = iter.Next() {
@ -121,18 +120,38 @@ func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlan
if instanceGroupManagerScopedListPair.Value == nil { if instanceGroupManagerScopedListPair.Value == nil {
continue continue
} }
for _, instanceGroupManager := range instanceGroupManagerScopedListPair.Value.InstanceGroupManagers { for _, grpManager := range instanceGroupManagerScopedListPair.Value.InstanceGroupManagers {
if instanceGroupManager == nil || instanceGroupManager.Name == nil || instanceGroupManager.SelfLink == nil { if grpManager == nil || grpManager.Name == nil || grpManager.SelfLink == nil || grpManager.InstanceTemplate == nil {
continue continue
} }
groupID, err := c.canonicalInstanceGroupID(ctx, *instanceGroupManager.SelfLink)
templateURI := strings.Split(*grpManager.InstanceTemplate, "/")
if len(templateURI) < 1 {
continue // invalid template URI
}
template, err := c.instanceTemplateAPI.Get(ctx, &computepb.GetInstanceTemplateRequest{
Project: c.projectID,
InstanceTemplate: templateURI[len(templateURI)-1],
})
if err != nil {
return nil, nil, fmt.Errorf("getting instance template: %w", err)
}
if template.Properties == nil || template.Properties.Labels == nil {
continue
}
if template.Properties.Labels["constellation-uid"] != uid {
continue
}
groupID, err := c.canonicalInstanceGroupID(ctx, *grpManager.SelfLink)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("normalizing instance group ID: %w", err) return nil, nil, fmt.Errorf("normalizing instance group ID: %w", err)
} }
if isControlPlaneInstanceGroup(*instanceGroupManager.Name) { switch strings.ToLower(template.Properties.Labels["constellation-role"]) {
case "control-plane", "controlplane":
controlPlaneGroupIDs = append(controlPlaneGroupIDs, groupID) controlPlaneGroupIDs = append(controlPlaneGroupIDs, groupID)
} else if isWorkerInstanceGroup(*instanceGroupManager.Name) { case "worker":
workerGroupIDs = append(workerGroupIDs, groupID) workerGroupIDs = append(workerGroupIDs, groupID)
} }
} }

View file

@ -326,7 +326,10 @@ func TestListScalingGroups(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
name *string name *string
groupID *string groupID *string
templateRef *string
templateLabels map[string]string
listInstanceGroupManagersErr error listInstanceGroupManagersErr error
templateGetErr error
wantControlPlanes []string wantControlPlanes []string
wantWorkers []string wantWorkers []string
wantErr bool wantErr bool
@ -335,9 +338,21 @@ func TestListScalingGroups(t *testing.T) {
listInstanceGroupManagersErr: errors.New("list instance group managers error"), listInstanceGroupManagersErr: errors.New("list instance group managers error"),
wantErr: true, wantErr: true,
}, },
"get instance template fails": {
name: proto.String("test-control-plane-uid"),
groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-control-plane-uid"),
templateRef: proto.String("projects/project/global/instanceTemplates/test-control-plane-uid"),
templateGetErr: errors.New("get instance template error"),
wantErr: true,
},
"list instance group managers for control plane": { "list instance group managers for control plane": {
name: proto.String("test-control-plane-uid"), name: proto.String("test-control-plane-uid"),
groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-control-plane-uid"), groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-control-plane-uid"),
templateRef: proto.String("projects/project/global/instanceTemplates/test-control-plane-uid"),
templateLabels: map[string]string{
"constellation-uid": "uid",
"constellation-role": "control-plane",
},
wantControlPlanes: []string{ wantControlPlanes: []string{
"projects/project/zones/zone/instanceGroupManagers/test-control-plane-uid", "projects/project/zones/zone/instanceGroupManagers/test-control-plane-uid",
}, },
@ -345,13 +360,34 @@ func TestListScalingGroups(t *testing.T) {
"list instance group managers for worker": { "list instance group managers for worker": {
name: proto.String("test-worker-uid"), name: proto.String("test-worker-uid"),
groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-worker-uid"), groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-worker-uid"),
templateRef: proto.String("projects/project/global/instanceTemplates/test-control-plane-uid"),
templateLabels: map[string]string{
"constellation-uid": "uid",
"constellation-role": "worker",
},
wantWorkers: []string{ wantWorkers: []string{
"projects/project/zones/zone/instanceGroupManagers/test-worker-uid", "projects/project/zones/zone/instanceGroupManagers/test-worker-uid",
}, },
}, },
"listing instance group managers is not dependant on resource name": {
name: proto.String("some-instance-group-manager"),
groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/some-instance-group-manager"),
templateRef: proto.String("projects/project/global/instanceTemplates/some-instance-group-template"),
templateLabels: map[string]string{
"constellation-uid": "uid",
"constellation-role": "control-plane",
},
wantControlPlanes: []string{
"projects/project/zones/zone/instanceGroupManagers/some-instance-group-manager",
},
},
"unrelated instance group manager": { "unrelated instance group manager": {
name: proto.String("test-unrelated-uid"), name: proto.String("test-control-plane-uid"),
groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-unrelated-uid"), groupID: proto.String("projects/project/zones/zone/instanceGroupManagers/test-unrelated-uid"),
templateRef: proto.String("projects/project/global/instanceTemplates/test-control-plane-uid"),
templateLabels: map[string]string{
"label": "value",
},
}, },
"invalid instance group manager": {}, "invalid instance group manager": {},
} }
@ -367,8 +403,17 @@ func TestListScalingGroups(t *testing.T) {
instanceGroupManager: &computepb.InstanceGroupManager{ instanceGroupManager: &computepb.InstanceGroupManager{
Name: tc.name, Name: tc.name,
SelfLink: tc.groupID, SelfLink: tc.groupID,
InstanceTemplate: tc.templateRef,
}, },
}, },
instanceTemplateAPI: &stubInstanceTemplateAPI{
template: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
Labels: tc.templateLabels,
},
},
getErr: tc.templateGetErr,
},
} }
gotControlPlanes, gotWorkers, err := client.ListScalingGroups(context.Background(), "uid") gotControlPlanes, gotWorkers, err := client.ListScalingGroups(context.Background(), "uid")
if tc.wantErr { if tc.wantErr {