operator: use GCP REST API for instance templates (#3361)

This commit is contained in:
Moritz Sanft 2024-09-18 08:57:14 +02:00 committed by GitHub
parent dda6d5c16c
commit effb086cd3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 174 additions and 192 deletions

View file

@ -29,6 +29,7 @@ go_library(
"@com_github_spf13_afero//:afero",
"@com_google_cloud_go_compute//apiv1",
"@com_google_cloud_go_compute//apiv1/computepb",
"@org_golang_google_api//compute/v1:compute",
"@org_golang_google_api//googleapi",
"@org_golang_google_api//iterator",
"@org_golang_google_protobuf//proto",
@ -61,6 +62,7 @@ go_test(
"@com_github_stretchr_testify//require",
"@com_google_cloud_go_compute//apiv1",
"@com_google_cloud_go_compute//apiv1/computepb",
"@org_golang_google_api//compute/v1:compute",
"@org_golang_google_api//googleapi",
"@org_golang_google_api//iterator",
"@org_golang_google_protobuf//proto",

View file

@ -12,6 +12,7 @@ import (
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"github.com/googleapis/gax-go/v2"
computeREST "google.golang.org/api/compute/v1"
)
type projectAPI interface {
@ -27,13 +28,9 @@ type instanceAPI interface {
}
type instanceTemplateAPI interface {
Close() error
Get(ctx context.Context, req *computepb.GetInstanceTemplateRequest,
opts ...gax.CallOption) (*computepb.InstanceTemplate, error)
Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption) (Operation, error)
Get(projectID, template string) (*computeREST.InstanceTemplate, error)
Delete(projectID, template string) (*computeREST.Operation, error)
Insert(projectID string, template *computeREST.InstanceTemplate) (*computeREST.Operation, error)
}
type instanceGroupManagersAPI interface {

View file

@ -14,6 +14,7 @@ import (
compute "cloud.google.com/go/compute/apiv1"
"github.com/spf13/afero"
computeREST "google.golang.org/api/compute/v1"
)
// Client is a client for the Google Compute Engine.
@ -48,12 +49,17 @@ func New(ctx context.Context, configPath string) (*Client, error) {
return nil, err
}
closers = append(closers, insAPI)
templAPI, err := compute.NewInstanceTemplatesRESTClient(ctx)
// TODO(msanft): Go back to protobuf-based API when it supports setting
// a confidential instance type.
// See https://github.com/googleapis/google-cloud-go/issues/10873 for the current status.
restClient, err := computeREST.NewService(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, templAPI)
templAPI := computeREST.NewInstanceTemplatesService(restClient)
groupAPI, err := compute.NewInstanceGroupManagersRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
@ -81,7 +87,6 @@ func (c *Client) Close() error {
closers := []closer{
c.projectAPI,
c.instanceAPI,
c.instanceTemplateAPI,
c.instanceGroupManagersAPI,
c.diskAPI,
}

View file

@ -12,6 +12,7 @@ import (
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"github.com/googleapis/gax-go/v2"
computeREST "google.golang.org/api/compute/v1"
"google.golang.org/api/iterator"
"google.golang.org/protobuf/proto"
)
@ -47,7 +48,7 @@ func (a stubInstanceAPI) Get(_ context.Context, _ *computepb.GetInstanceRequest,
}
type stubInstanceTemplateAPI struct {
template *computepb.InstanceTemplate
template *computeREST.InstanceTemplate
getErr error
deleteErr error
insertErr error
@ -57,30 +58,16 @@ func (a stubInstanceTemplateAPI) Close() error {
return nil
}
func (a stubInstanceTemplateAPI) Get(_ context.Context, _ *computepb.GetInstanceTemplateRequest,
_ ...gax.CallOption,
) (*computepb.InstanceTemplate, error) {
func (a stubInstanceTemplateAPI) Get(_, _ string) (*computeREST.InstanceTemplate, error) {
return a.template, a.getErr
}
func (a stubInstanceTemplateAPI) Delete(_ context.Context, _ *computepb.DeleteInstanceTemplateRequest,
_ ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.deleteErr
func (a stubInstanceTemplateAPI) Delete(_, _ string) (*computeREST.Operation, error) {
return &computeREST.Operation{}, a.deleteErr
}
func (a stubInstanceTemplateAPI) Insert(_ context.Context, _ *computepb.InsertInstanceTemplateRequest,
_ ...gax.CallOption,
) (Operation, error) {
return &stubOperation{
&computepb.Operation{
Name: proto.String("name"),
},
}, a.insertErr
func (a stubInstanceTemplateAPI) Insert(_ string, _ *computeREST.InstanceTemplate) (*computeREST.Operation, error) {
return &computeREST.Operation{}, a.insertErr
}
type stubInstanceGroupManagersAPI struct {

View file

@ -12,26 +12,27 @@ import (
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"github.com/googleapis/gax-go/v2"
computeREST "google.golang.org/api/compute/v1"
)
type instanceTemplateClient struct {
*compute.InstanceTemplatesClient
*computeREST.InstanceTemplatesService
}
func (c *instanceTemplateClient) Close() error {
return c.InstanceTemplatesClient.Close()
return nil // no-op
}
func (c *instanceTemplateClient) Delete(ctx context.Context, req *computepb.DeleteInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Delete(ctx, req, opts...)
func (c *instanceTemplateClient) Get(project, template string) (*computeREST.InstanceTemplate, error) {
return c.InstanceTemplatesService.Get(project, template).Do()
}
func (c *instanceTemplateClient) Insert(ctx context.Context, req *computepb.InsertInstanceTemplateRequest,
opts ...gax.CallOption,
) (Operation, error) {
return c.InstanceTemplatesClient.Insert(ctx, req, opts...)
func (c *instanceTemplateClient) Delete(project, template string) (*computeREST.Operation, error) {
return c.InstanceTemplatesService.Delete(project, template).Do()
}
func (c *instanceTemplateClient) Insert(projectID string, template *computeREST.InstanceTemplate) (*computeREST.Operation, error) {
return c.InstanceTemplatesService.Insert(projectID, template).Do()
}
type instanceGroupManagersClient struct {

View file

@ -16,6 +16,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants"
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/api/v1alpha1"
cspapi "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/internal/cloud/api"
computeREST "google.golang.org/api/compute/v1"
"google.golang.org/api/iterator"
)
@ -49,29 +50,22 @@ func (c *Client) SetScalingGroupImage(ctx context.Context, scalingGroupID, image
}
// clone template with desired image
if instanceTemplate.Name == nil {
if instanceTemplate.Name == "" {
return fmt.Errorf("instance template of scaling group %q has no name", scalingGroupID)
}
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage = &imageURI
newTemplateName, err := generateInstanceTemplateName(*instanceTemplate.Name)
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage = imageURI
newTemplateName, err := generateInstanceTemplateName(instanceTemplate.Name)
if err != nil {
return err
}
instanceTemplate.Name = &newTemplateName
op, err := c.instanceTemplateAPI.Insert(ctx, &computepb.InsertInstanceTemplateRequest{
Project: project,
InstanceTemplateResource: instanceTemplate,
})
if err != nil {
instanceTemplate.Name = newTemplateName
if _, err := c.instanceTemplateAPI.Insert(project, instanceTemplate); err != nil {
return fmt.Errorf("cloning instance template: %w", err)
}
if err := op.Wait(ctx); err != nil {
return fmt.Errorf("waiting for cloned instance template: %w", err)
}
newTemplateURI := joinInstanceTemplateURI(project, newTemplateName)
// update instance group manager to use new template
op, err = c.instanceGroupManagersAPI.SetInstanceTemplate(ctx, &computepb.SetInstanceTemplateInstanceGroupManagerRequest{
op, err := c.instanceGroupManagersAPI.SetInstanceTemplate(ctx, &computepb.SetInstanceTemplateInstanceGroupManagerRequest{
InstanceGroupManager: instanceGroupName,
Project: project,
Zone: zone,
@ -133,10 +127,7 @@ func (c *Client) ListScalingGroups(ctx context.Context, uid string) ([]cspapi.Sc
if len(templateURI) < 1 {
continue // invalid template URI
}
template, err := c.instanceTemplateAPI.Get(ctx, &computepb.GetInstanceTemplateRequest{
Project: c.projectID,
InstanceTemplate: templateURI[len(templateURI)-1],
})
template, err := c.instanceTemplateAPI.Get(c.projectID, templateURI[len(templateURI)-1])
if err != nil {
return nil, fmt.Errorf("getting instance template: %w", err)
}
@ -188,7 +179,7 @@ func (c *Client) ListScalingGroups(ctx context.Context, uid string) ([]cspapi.Sc
return results, nil
}
func (c *Client) getScalingGroupTemplate(ctx context.Context, scalingGroupID string) (*computepb.InstanceTemplate, error) {
func (c *Client) getScalingGroupTemplate(ctx context.Context, scalingGroupID string) (*computeREST.InstanceTemplate, error) {
project, zone, instanceGroupName, err := splitInstanceGroupID(scalingGroupID)
if err != nil {
return nil, err
@ -208,22 +199,19 @@ func (c *Client) getScalingGroupTemplate(ctx context.Context, scalingGroupID str
if err != nil {
return nil, fmt.Errorf("splitting instance template name: %w", err)
}
instanceTemplate, err := c.instanceTemplateAPI.Get(ctx, &computepb.GetInstanceTemplateRequest{
InstanceTemplate: instanceTemplateName,
Project: instanceTemplateProject,
})
instanceTemplate, err := c.instanceTemplateAPI.Get(instanceTemplateProject, instanceTemplateName)
if err != nil {
return nil, fmt.Errorf("getting instance template %q: %w", instanceTemplateName, err)
}
return instanceTemplate, nil
}
func instanceTemplateSourceImage(instanceTemplate *computepb.InstanceTemplate) (string, error) {
func instanceTemplateSourceImage(instanceTemplate *computeREST.InstanceTemplate) (string, error) {
if instanceTemplate.Properties == nil ||
len(instanceTemplate.Properties.Disks) == 0 ||
instanceTemplate.Properties.Disks[0].InitializeParams == nil ||
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage == nil {
instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage == "" {
return "", errors.New("instance template has no source image")
}
return uriNormalize(*instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage), nil
return uriNormalize(instanceTemplate.Properties.Disks[0].InitializeParams.SourceImage), nil
}

View file

@ -16,6 +16,7 @@ import (
cspapi "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/internal/cloud/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
computeREST "google.golang.org/api/compute/v1"
"google.golang.org/protobuf/proto"
)
@ -23,7 +24,7 @@ func TestGetScalingGroupImage(t *testing.T) {
testCases := map[string]struct {
scalingGroupID string
instanceGroupManagerTemplateID *string
instanceTemplate *computepb.InstanceTemplate
instanceTemplate *computeREST.InstanceTemplate
getInstanceGroupManagerErr error
getInstanceTemplateErr error
wantImage string
@ -32,12 +33,12 @@ func TestGetScalingGroupImage(t *testing.T) {
"getting image works": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image",
},
},
},
@ -72,8 +73,8 @@ func TestGetScalingGroupImage(t *testing.T) {
"instance template has no disks": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{},
instanceTemplate: &computeREST.InstanceTemplate{
Properties: &computeREST.InstanceProperties{},
},
wantErr: true,
},
@ -112,7 +113,7 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID string
imageURI string
instanceGroupManagerTemplateID *string
instanceTemplate *computepb.InstanceTemplate
instanceTemplate *computeREST.InstanceTemplate
getInstanceGroupManagerErr error
getInstanceTemplateErr error
setInstanceTemplateErr error
@ -123,13 +124,13 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Name: "instance-template",
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image-1",
},
},
},
@ -140,13 +141,13 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Name: "instance-template",
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image",
},
},
},
@ -182,8 +183,8 @@ func TestSetScalingGroupImage(t *testing.T) {
"instance template has no disks": {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{},
instanceTemplate: &computeREST.InstanceTemplate{
Properties: &computeREST.InstanceProperties{},
},
wantErr: true,
},
@ -191,12 +192,12 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image-1",
},
},
},
@ -208,13 +209,13 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template-999999999999999999999"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Name: "instance-template-999999999999999999999",
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image-1",
},
},
},
@ -226,13 +227,13 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Name: "instance-template",
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image-1",
},
},
},
@ -245,13 +246,13 @@ func TestSetScalingGroupImage(t *testing.T) {
scalingGroupID: "projects/project/zones/zone/instanceGroupManagers/instance-group",
imageURI: "projects/project/global/images/image-2",
instanceGroupManagerTemplateID: proto.String("projects/project/global/instanceTemplates/instance-template"),
instanceTemplate: &computepb.InstanceTemplate{
Name: proto.String("instance-template"),
Properties: &computepb.InstanceProperties{
Disks: []*computepb.AttachedDisk{
instanceTemplate: &computeREST.InstanceTemplate{
Name: "instance-template",
Properties: &computeREST.InstanceProperties{
Disks: []*computeREST.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
SourceImage: proto.String("https://www.googleapis.com/compute/v1/projects/project/global/images/image-1"),
InitializeParams: &computeREST.AttachedDiskInitializeParams{
SourceImage: "https://www.googleapis.com/compute/v1/projects/project/global/images/image-1",
},
},
},
@ -445,8 +446,8 @@ func TestListScalingGroups(t *testing.T) {
},
},
instanceTemplateAPI: &stubInstanceTemplateAPI{
template: &computepb.InstanceTemplate{
Properties: &computepb.InstanceProperties{
template: &computeREST.InstanceTemplate{
Properties: &computeREST.InstanceProperties{
Labels: tc.templateLabels,
},
},