mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-25 15:39:37 -05:00
[node operator] GCP: use canonical references
This commit is contained in:
parent
51cf638361
commit
80ebfab164
@ -8,6 +8,12 @@ import (
|
|||||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type projectAPI interface {
|
||||||
|
Close() error
|
||||||
|
Get(ctx context.Context, req *computepb.GetProjectRequest,
|
||||||
|
opts ...gax.CallOption) (*computepb.Project, error)
|
||||||
|
}
|
||||||
|
|
||||||
type instanceAPI interface {
|
type instanceAPI interface {
|
||||||
Close() error
|
Close() error
|
||||||
Get(ctx context.Context, req *computepb.GetInstanceRequest,
|
Get(ctx context.Context, req *computepb.GetInstanceRequest,
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
// Client is a client for the Google Compute Engine.
|
// Client is a client for the Google Compute Engine.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
projectID string
|
projectID string
|
||||||
|
projectAPI
|
||||||
instanceAPI
|
instanceAPI
|
||||||
instanceTemplateAPI
|
instanceTemplateAPI
|
||||||
instanceGroupManagersAPI
|
instanceGroupManagersAPI
|
||||||
@ -29,8 +30,15 @@ func New(ctx context.Context, configPath string) (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var closers []closer
|
var closers []closer
|
||||||
|
projectAPI, err := compute.NewProjectsRESTClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
closers = append(closers, projectAPI)
|
||||||
insAPI, err := compute.NewInstancesRESTClient(ctx)
|
insAPI, err := compute.NewInstancesRESTClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = closeAll(closers)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
closers = append(closers, insAPI)
|
closers = append(closers, insAPI)
|
||||||
@ -53,6 +61,7 @@ func New(ctx context.Context, configPath string) (*Client, error) {
|
|||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
projectID: projectID,
|
projectID: projectID,
|
||||||
|
projectAPI: projectAPI,
|
||||||
instanceAPI: insAPI,
|
instanceAPI: insAPI,
|
||||||
instanceTemplateAPI: &instanceTemplateClient{templAPI},
|
instanceTemplateAPI: &instanceTemplateClient{templAPI},
|
||||||
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
|
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
|
||||||
@ -64,6 +73,7 @@ func New(ctx context.Context, configPath string) (*Client, error) {
|
|||||||
// Close closes the client's connection.
|
// Close closes the client's connection.
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
closers := []closer{
|
closers := []closer{
|
||||||
|
c.projectAPI,
|
||||||
c.instanceAPI,
|
c.instanceAPI,
|
||||||
c.instanceTemplateAPI,
|
c.instanceTemplateAPI,
|
||||||
c.instanceGroupManagersAPI,
|
c.instanceGroupManagersAPI,
|
||||||
|
@ -10,6 +10,21 @@ import (
|
|||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type stubProjectAPI struct {
|
||||||
|
project *computepb.Project
|
||||||
|
getErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a stubProjectAPI) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a stubProjectAPI) Get(ctx context.Context, req *computepb.GetProjectRequest,
|
||||||
|
opts ...gax.CallOption,
|
||||||
|
) (*computepb.Project, error) {
|
||||||
|
return a.project, a.getErr
|
||||||
|
}
|
||||||
|
|
||||||
type stubInstanceAPI struct {
|
type stubInstanceAPI struct {
|
||||||
instance *computepb.Instance
|
instance *computepb.Instance
|
||||||
getErr error
|
getErr error
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
@ -11,6 +12,18 @@ var (
|
|||||||
workerInstanceGroupNameRegex = regexp.MustCompile(`^(.*)worker(.*)$`)
|
workerInstanceGroupNameRegex = regexp.MustCompile(`^(.*)worker(.*)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *Client) canonicalInstanceGroupID(ctx context.Context, instanceGroupID string) (string, error) {
|
||||||
|
project, zone, instanceGroup, err := splitInstanceGroupID(uriNormalize(instanceGroupID))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
project, err = c.canonicalProjectID(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("projects/%s/zones/%s/instanceGroupManagers/%s", project, zone, instanceGroup), nil
|
||||||
|
}
|
||||||
|
|
||||||
// splitInstanceGroupID splits an instance group ID into core components.
|
// splitInstanceGroupID splits an instance group ID into core components.
|
||||||
func splitInstanceGroupID(instanceGroupID string) (project, zone, instanceGroup string, err error) {
|
func splitInstanceGroupID(instanceGroupID string) (project, zone, instanceGroup string, err error) {
|
||||||
matches := instanceGroupIDRegex.FindStringSubmatch(instanceGroupID)
|
matches := instanceGroupIDRegex.FindStringSubmatch(instanceGroupID)
|
||||||
|
@ -14,6 +14,10 @@ func (c *Client) GetNodeImage(ctx context.Context, providerID string) (string, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
project, err = c.canonicalProjectID(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||||
Instance: instanceName,
|
Instance: instanceName,
|
||||||
Project: project,
|
Project: project,
|
||||||
@ -61,6 +65,10 @@ func (c *Client) GetScalingGroupID(ctx context.Context, providerID string) (stri
|
|||||||
if scalingGroupID == "" {
|
if scalingGroupID == "" {
|
||||||
return "", fmt.Errorf("instance %q has no created-by metadata", instanceName)
|
return "", fmt.Errorf("instance %q has no created-by metadata", instanceName)
|
||||||
}
|
}
|
||||||
|
scalingGroupID, err = c.canonicalInstanceGroupID(ctx, scalingGroupID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return scalingGroupID, nil
|
return scalingGroupID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +78,10 @@ func (c *Client) CreateNode(ctx context.Context, scalingGroupID string) (nodeNam
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
project, err = c.canonicalProjectID(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
instanceGroupManager, err := c.instanceGroupManagersAPI.Get(ctx, &computepb.GetInstanceGroupManagerRequest{
|
instanceGroupManager, err := c.instanceGroupManagersAPI.Get(ctx, &computepb.GetInstanceGroupManagerRequest{
|
||||||
InstanceGroupManager: instanceGroupName,
|
InstanceGroupManager: instanceGroupName,
|
||||||
Project: project,
|
Project: project,
|
||||||
|
@ -116,8 +116,8 @@ func TestGetScalingGroupID(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"scaling group is found": {
|
"scaling group is found": {
|
||||||
providerID: "gce://project/zone/instance-name",
|
providerID: "gce://project/zone/instance-name",
|
||||||
createdBy: "projects/project/zones/zone/instanceGroups/instance-group",
|
createdBy: "projects/project/zones/zone/instanceGroupManagers/instance-group",
|
||||||
wantScalingGroupID: "projects/project/zones/zone/instanceGroups/instance-group",
|
wantScalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
|
||||||
},
|
},
|
||||||
"splitting providerID fails": {
|
"splitting providerID fails": {
|
||||||
providerID: "invalid",
|
providerID: "invalid",
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var numericProjectIDRegex = regexp.MustCompile(`^\d+$`)
|
||||||
|
|
||||||
|
// canonicalProjectID returns the project id for a given project id or project number.
|
||||||
|
func (c *Client) canonicalProjectID(ctx context.Context, project string) (string, error) {
|
||||||
|
if !numericProjectIDRegex.MatchString(project) {
|
||||||
|
return project, nil
|
||||||
|
}
|
||||||
|
computeProject, err := c.projectAPI.Get(ctx, &compute.GetProjectRequest{Project: project})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if computeProject == nil || computeProject.Name == nil {
|
||||||
|
return "", errors.New("invalid project")
|
||||||
|
}
|
||||||
|
return *computeProject.Name, nil
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCanonicalProjectID(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
projectID string
|
||||||
|
project *computepb.Project
|
||||||
|
getProjectErr error
|
||||||
|
wantProjectID string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"already canonical": {
|
||||||
|
projectID: "project-12345",
|
||||||
|
wantProjectID: "project-12345",
|
||||||
|
},
|
||||||
|
"numeric project id": {
|
||||||
|
projectID: "12345",
|
||||||
|
wantProjectID: "project-12345",
|
||||||
|
project: &computepb.Project{Name: proto.String("project-12345")},
|
||||||
|
},
|
||||||
|
"numeric project id with error": {
|
||||||
|
projectID: "12345",
|
||||||
|
wantProjectID: "project-12345",
|
||||||
|
getProjectErr: errors.New("get error"),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"numeric project id with nil project": {
|
||||||
|
projectID: "12345",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
projectAPI: &stubProjectAPI{
|
||||||
|
project: tc.project,
|
||||||
|
getErr: tc.getProjectErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
gotID, err := client.canonicalProjectID(context.Background(), tc.projectID)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantProjectID, gotID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -109,7 +109,10 @@ func (c *Client) ListScalingGroups(ctx context.Context, uid string) (controlPlan
|
|||||||
if instanceGroupManager == nil || instanceGroupManager.Name == nil || instanceGroupManager.SelfLink == nil {
|
if instanceGroupManager == nil || instanceGroupManager.Name == nil || instanceGroupManager.SelfLink == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groupID := uriNormalize(*instanceGroupManager.SelfLink)
|
groupID, err := c.canonicalInstanceGroupID(ctx, *instanceGroupManager.SelfLink)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("normalizing instance group ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if isControlPlaneInstanceGroup(*instanceGroupManager.Name) {
|
if isControlPlaneInstanceGroup(*instanceGroupManager.Name) {
|
||||||
controlPlaneGroupIDs = append(controlPlaneGroupIDs, groupID)
|
controlPlaneGroupIDs = append(controlPlaneGroupIDs, groupID)
|
||||||
|
Loading…
Reference in New Issue
Block a user