Skip to content

Commit

Permalink
feat: adds gcp metadata provider support to cloudmeta package (#4155)
Browse files Browse the repository at this point in the history
* feat: adds gcp metadata provider support to cloudmeta package

This adds gcp metadata provider support to cloudmeta package.

Fixes: #4128
  • Loading branch information
VAveryanov8 authored Dec 17, 2024
1 parent 4e52b04 commit 0372277
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 2 deletions.
66 changes: 66 additions & 0 deletions pkg/cloudmeta/gcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (C) 2024 ScyllaDB

package cloudmeta

import (
"context"
"strings"

"cloud.google.com/go/compute/metadata"
"github.com/pkg/errors"
)

// gcpMetadata is a wrapper around gcp metadata client.
type gcpMetadata struct {
meta *metadata.Client
}

// newGCPMetadata returns gcp metadata provider.
func newGCPMetadata() *gcpMetadata {
return &gcpMetadata{
meta: metadata.NewClient(nil),
}
}

// Metadata returns InstanceMetadata from gcp if available.
func (gcp *gcpMetadata) Metadata(ctx context.Context) (InstanceMetadata, error) {
machineType, err := gcp.getMachineType(ctx)
if err != nil {
return InstanceMetadata{}, errors.Wrap(err, "gcp.meta.GetWithContext")
}
return InstanceMetadata{
CloudProvider: CloudProviderGCP,
InstanceType: machineType,
}, nil
}

func (gcp *gcpMetadata) getMachineType(ctx context.Context) (string, error) {
machineTypeResp, err := gcp.meta.GetWithContext(ctx, "instance/machine-type")
if err != nil {
return "", errors.Wrap(err, "gcp.meta.GetWithContext")
}

machineType, err := parseMachineTypeResponse(machineTypeResp)
if err != nil {
return "", err
}

return machineType, nil
}

// The machine type for this VM. This value has the following format: projects/PROJECT_NUM/machineTypes/MACHINE_TYPE.
// See https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys#instance-metadata.
func parseMachineTypeResponse(resp string) (string, error) {
errUnexpectedFormat := errors.Errorf("unexpected machineType response format: %s", resp)

parts := strings.Split(resp, "/")
if len(parts) != 4 {
return "", errUnexpectedFormat
}

if parts[2] != "machineTypes" {
return "", errUnexpectedFormat
}

return parts[3], nil
}
67 changes: 67 additions & 0 deletions pkg/cloudmeta/gcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (C) 2024 ScyllaDB

package cloudmeta

import "testing"

func TestParseMachineTypeResponse(t *testing.T) {
testCases := []struct {
name string
machineTypeResponse string

expectedErr bool
expected string
}{
{
name: "everything is fine",
machineTypeResponse: "projects/project1/machineTypes/machineType1",

expectedErr: false,
expected: "machineType1",
},
{
name: "new response part is added",
machineTypeResponse: "projects/project1/zone/zone1/machineTypes/machineType1",

expectedErr: true,
expected: "",
},
{
name: "new response part is added after machineTypes part",
machineTypeResponse: "projects/project1/machineTypes/machineType1/zones/zone1",

expectedErr: true,
expected: "",
},
{
name: "parts are mixed up",
machineTypeResponse: "machineTypes/machineType1/projects/project1",

expectedErr: true,
expected: "",
},
{
name: "empty response",
machineTypeResponse: "",

expectedErr: true,
expected: "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
machineType, err := parseMachineTypeResponse(tc.machineTypeResponse)
if tc.expectedErr && err == nil {
t.Fatalf("expected err, but got %v", err)
}
if !tc.expectedErr && err != nil {
t.Fatalf("unexpected err: %v", err)
}

if tc.expected != machineType {
t.Fatalf("machineType(%s) != expected(%s)", machineType, tc.expected)
}
})
}
}
11 changes: 9 additions & 2 deletions pkg/cloudmeta/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ type InstanceMetadata struct {
// CloudProvider is enum of supported cloud providers.
type CloudProvider string

// CloudProviderAWS represents aws provider.
var CloudProviderAWS CloudProvider = "aws"
const (
// CloudProviderAWS represents aws provider.
CloudProviderAWS CloudProvider = "aws"
// CloudProviderGCP represents gcp provider.
CloudProviderGCP CloudProvider = "gcp"
)

// CloudMetadataProvider interface that each metadata provider should implement.
type CloudMetadataProvider interface {
Expand All @@ -43,9 +47,12 @@ func NewCloudMeta() (*CloudMeta, error) {
return nil, err
}

gcpMeta := newGCPMetadata()

return &CloudMeta{
providers: []CloudMetadataProvider{
awsMeta,
gcpMeta,
},
providerTimeout: defaultTimeout,
}, nil
Expand Down

0 comments on commit 0372277

Please sign in to comment.