diff --git a/detectors/gcp/gce.go b/detectors/gcp/gce.go index 37259fc4..ac855879 100644 --- a/detectors/gcp/gce.go +++ b/detectors/gcp/gce.go @@ -16,13 +16,19 @@ package gcp import ( "fmt" + "regexp" "strings" + + "cloud.google.com/go/compute/metadata" ) // See the available GCE instance metadata: -// https://cloud.google.com/compute/docs/metadata/default-metadata-values#vm_instance_metadata +// https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys#instance-metadata const machineTypeMetadataAttr = "instance/machine-type" +// https://cloud.google.com/compute/docs/instance-groups/getting-info-about-migs#checking_if_a_vm_instance_is_part_of_a_mig +const createdByMetadataAttr = "instance/created-by" + func (d *Detector) onGCE() bool { _, err := d.metadata.Get(machineTypeMetadataAttr) return err == nil @@ -73,3 +79,36 @@ func (d *Detector) GCEAvailabilityZoneAndRegion() (string, string, error) { } return zone, strings.Join(splitZone[0:2], "-"), nil } + +type ManagedInstanceGroup struct { + Name string + Location string + Type LocationType +} + +var createdByMIGRE = regexp.MustCompile(`^projects/[^/]+/(zones|regions)/([^/]+)/instanceGroupManagers/([^/]+)$`) + +func (d *Detector) GCEManagedInstanceGroup() (ManagedInstanceGroup, error) { + createdBy, err := d.metadata.Get(createdByMetadataAttr) + if _, ok := err.(metadata.NotDefinedError); ok { + return ManagedInstanceGroup{}, nil + } else if err != nil { + return ManagedInstanceGroup{}, err + } + matches := createdByMIGRE.FindStringSubmatch(createdBy) + if matches == nil { + return ManagedInstanceGroup{}, nil + } + + mig := ManagedInstanceGroup{ + Name: matches[3], + Location: matches[2], + } + switch matches[1] { + case "zones": + mig.Type = Zone + case "regions": + mig.Type = Region + } + return mig, nil +} diff --git a/detectors/gcp/gce_test.go b/detectors/gcp/gce_test.go index bcf0b8d9..35be4fc2 100644 --- a/detectors/gcp/gce_test.go +++ b/detectors/gcp/gce_test.go @@ -124,7 +124,7 @@ func TestGCEAvaiabilityZoneAndRegionNoZone(t *testing.T) { assert.Equal(t, region, "") } -func TestGCEAvaiabilityZoneAndRegionErr(t *testing.T) { +func TestGCEAvailabilityZoneAndRegionErr(t *testing.T) { d := NewTestDetector(&FakeMetadataProvider{ Err: fmt.Errorf("fake error"), }, &FakeOSProvider{}) @@ -133,3 +133,58 @@ func TestGCEAvaiabilityZoneAndRegionErr(t *testing.T) { assert.Equal(t, zone, "") assert.Equal(t, region, "") } + +func TestGCEManagedInstanceGroup(t *testing.T) { + for _, test := range []struct { + createdBy string + mig *ManagedInstanceGroup + }{ + { + "projects/123456789012/zones/us-central1-f/instanceGroupManagers/igm-metadata", + &ManagedInstanceGroup{ + Name: "igm-metadata", + Location: "us-central1-f", + Type: Zone, + }, + }, + { + "projects/123456789012/regions/us-central1-f/instanceGroupManagers/igm-metadata", + &ManagedInstanceGroup{ + Name: "igm-metadata", + Location: "us-central1-f", + Type: Region, + }, + }, + { + "projects/123456789012/zones/us-central1-f/someOtherInstanceGroup/igm-metadata", + &ManagedInstanceGroup{}, + }, + { + "", + &ManagedInstanceGroup{}, + }, + { + "", + nil, + }, + } { + t.Run(test.createdBy, func(t *testing.T) { + fmp := &FakeMetadataProvider{ + Attributes: map[string]string{}, + } + if test.createdBy != "" { + fmp.Attributes[createdByMetadataAttr] = test.createdBy + } + if test.mig == nil { + fmp.Err = fmt.Errorf("fake error") + } + d := NewTestDetector(fmp, &FakeOSProvider{}) + mig, err := d.GCEManagedInstanceGroup() + if test.mig == nil { + assert.Error(t, err) + } else { + assert.Equal(t, mig, *test.mig) + } + }) + } +}