constellation/cli/internal/gcp/client/client.go

395 lines
11 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package client
import (
"context"
"crypto/rand"
"errors"
"math/big"
"net/http"
2022-08-05 06:09:39 -04:00
"strings"
compute "cloud.google.com/go/compute/apiv1"
admin "cloud.google.com/go/iam/admin/apiv1"
resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/internal/state"
"go.uber.org/multierr"
"google.golang.org/api/googleapi"
)
// Client is a client for the Google Compute Engine.
type Client struct {
instanceAPI
operationRegionAPI
operationZoneAPI
operationGlobalAPI
networksAPI
subnetworksAPI
firewallsAPI
2022-06-09 16:26:36 -04:00
forwardingRulesAPI
backendServicesAPI
healthChecksAPI
2022-08-31 21:40:29 -04:00
targetTCPProxiesAPI
instanceTemplateAPI
instanceGroupManagersAPI
iamAPI
projectsAPI
2022-08-01 10:51:34 -04:00
addressesAPI
workers cloudtypes.Instances
controlPlanes cloudtypes.Instances
workerInstanceGroup string
controlPlaneInstanceGroup string
controlPlaneTemplate string
workerTemplate string
network string
subnetwork string
secondarySubnetworkRange string
firewalls []string
name string
project string
uid string
zone string
region string
2022-06-09 16:26:36 -04:00
// loadbalancer
2022-08-01 10:51:34 -04:00
loadbalancerIP string
loadbalancerIPname string
loadbalancers []*loadBalancer
}
// NewFromDefault creates an uninitialized client.
func NewFromDefault(ctx context.Context) (*Client, error) {
var closers []closer
insAPI, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return nil, err
}
closers = append(closers, insAPI)
opZoneAPI, err := compute.NewZoneOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opZoneAPI)
opRegionAPI, err := compute.NewRegionOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opRegionAPI)
opGlobalAPI, err := compute.NewGlobalOperationsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, opGlobalAPI)
netAPI, err := compute.NewNetworksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, netAPI)
subnetAPI, err := compute.NewSubnetworksRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, subnetAPI)
fwAPI, err := compute.NewFirewallsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
2022-06-09 16:26:36 -04:00
closers = append(closers, subnetAPI)
2022-08-31 21:40:29 -04:00
forwardingRulesAPI, err := compute.NewGlobalForwardingRulesRESTClient(ctx)
2022-06-09 16:26:36 -04:00
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, forwardingRulesAPI)
2022-08-31 21:40:29 -04:00
backendServicesAPI, err := compute.NewBackendServicesRESTClient(ctx)
2022-06-09 16:26:36 -04:00
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, backendServicesAPI)
2022-08-31 21:40:29 -04:00
targetTCPProxiesAPI, err := compute.NewTargetTcpProxiesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, targetTCPProxiesAPI)
2022-06-09 16:26:36 -04:00
targetPoolsAPI, err := compute.NewTargetPoolsRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, targetPoolsAPI)
2022-08-31 21:40:29 -04:00
healthChecksAPI, err := compute.NewHealthChecksRESTClient(ctx)
2022-06-09 16:26:36 -04:00
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, healthChecksAPI)
templAPI, err := compute.NewInstanceTemplatesRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, templAPI)
groupAPI, err := compute.NewInstanceGroupManagersRESTClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, groupAPI)
iamAPI, err := admin.NewIamClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
closers = append(closers, iamAPI)
projectsAPI, err := resourcemanager.NewProjectsClient(ctx)
if err != nil {
_ = closeAll(closers)
return nil, err
}
2022-08-01 10:51:34 -04:00
closers = append(closers, projectsAPI)
2022-08-31 21:40:29 -04:00
addressesAPI, err := compute.NewGlobalAddressesRESTClient(ctx)
2022-08-01 10:51:34 -04:00
if err != nil {
_ = closeAll(closers)
return nil, err
}
return &Client{
instanceAPI: &instanceClient{insAPI},
operationRegionAPI: opRegionAPI,
operationZoneAPI: opZoneAPI,
operationGlobalAPI: opGlobalAPI,
networksAPI: &networksClient{netAPI},
subnetworksAPI: &subnetworksClient{subnetAPI},
firewallsAPI: &firewallsClient{fwAPI},
2022-06-09 16:26:36 -04:00
forwardingRulesAPI: &forwardingRulesClient{forwardingRulesAPI},
backendServicesAPI: &backendServicesClient{backendServicesAPI},
2022-08-31 21:40:29 -04:00
targetTCPProxiesAPI: &targetTCPProxiesClient{targetTCPProxiesAPI},
2022-06-09 16:26:36 -04:00
healthChecksAPI: &healthChecksClient{healthChecksAPI},
instanceTemplateAPI: &instanceTemplateClient{templAPI},
instanceGroupManagersAPI: &instanceGroupManagersClient{groupAPI},
iamAPI: &iamClient{iamAPI},
projectsAPI: &projectsClient{projectsAPI},
2022-08-01 10:51:34 -04:00
addressesAPI: &addressesClient{addressesAPI},
workers: make(cloudtypes.Instances),
controlPlanes: make(cloudtypes.Instances),
}, nil
}
// NewInitialized creates an initialized client.
func NewInitialized(ctx context.Context, project, zone, region, name string) (*Client, error) {
client, err := NewFromDefault(ctx)
if err != nil {
return nil, err
}
err = client.init(project, zone, region, name)
return client, err
}
// Close closes the client's connection.
func (c *Client) Close() error {
closers := []closer{
c.instanceAPI,
2022-06-09 16:26:36 -04:00
c.operationRegionAPI,
c.operationZoneAPI,
c.operationGlobalAPI,
c.networksAPI,
2022-06-09 16:26:36 -04:00
c.subnetworksAPI,
c.firewallsAPI,
2022-06-09 16:26:36 -04:00
c.forwardingRulesAPI,
c.backendServicesAPI,
c.healthChecksAPI,
c.instanceTemplateAPI,
c.instanceGroupManagersAPI,
2022-06-09 16:26:36 -04:00
c.iamAPI,
c.projectsAPI,
2022-08-01 10:51:34 -04:00
c.addressesAPI,
}
return closeAll(closers)
}
// init initializes the client.
func (c *Client) init(project, zone, region, name string) error {
c.project = project
c.zone = zone
c.name = name
c.region = region
uid, err := c.generateUID()
if err != nil {
return err
}
c.uid = uid
return nil
}
// GetState returns the state of the client as ConstellationState.
2022-08-01 06:35:35 -04:00
func (c *Client) GetState() state.ConstellationState {
2022-08-01 10:51:34 -04:00
stat := state.ConstellationState{
2022-08-01 06:35:35 -04:00
Name: c.name,
UID: c.uid,
CloudProvider: cloudprovider.GCP.String(),
2022-08-01 10:51:34 -04:00
LoadBalancerIP: c.loadbalancerIP,
2022-08-01 06:35:35 -04:00
GCPProject: c.project,
GCPZone: c.zone,
GCPRegion: c.region,
GCPWorkerInstances: c.workers,
GCPWorkerInstanceGroup: c.workerInstanceGroup,
GCPWorkerInstanceTemplate: c.workerTemplate,
GCPControlPlaneInstances: c.controlPlanes,
GCPControlPlaneInstanceGroup: c.controlPlaneInstanceGroup,
GCPControlPlaneInstanceTemplate: c.controlPlaneTemplate,
GCPFirewalls: c.firewalls,
GCPNetwork: c.network,
GCPSubnetwork: c.subnetwork,
2022-08-01 10:51:34 -04:00
GCPLoadbalancerIPname: c.loadbalancerIPname,
}
for _, lb := range c.loadbalancers {
stat.GCPLoadbalancers = append(stat.GCPLoadbalancers, lb.name)
}
2022-08-01 10:51:34 -04:00
return stat
}
// SetState sets the state of the client to the handed ConstellationState.
2022-08-01 06:35:35 -04:00
func (c *Client) SetState(stat state.ConstellationState) {
2022-07-29 02:10:51 -04:00
c.workers = stat.GCPWorkerInstances
c.controlPlanes = stat.GCPControlPlaneInstances
c.workerInstanceGroup = stat.GCPWorkerInstanceGroup
c.controlPlaneInstanceGroup = stat.GCPControlPlaneInstanceGroup
c.project = stat.GCPProject
c.zone = stat.GCPZone
c.region = stat.GCPRegion
c.name = stat.Name
c.uid = stat.UID
c.firewalls = stat.GCPFirewalls
c.network = stat.GCPNetwork
c.subnetwork = stat.GCPSubnetwork
c.workerTemplate = stat.GCPWorkerInstanceTemplate
c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate
2022-08-01 10:51:34 -04:00
c.loadbalancerIPname = stat.GCPLoadbalancerIPname
c.loadbalancerIP = stat.LoadBalancerIP
for _, lbName := range stat.GCPLoadbalancers {
lb := &loadBalancer{
name: lbName,
hasForwardingRules: true,
hasBackendService: true,
hasHealthCheck: true,
2022-08-31 21:40:29 -04:00
hasTargetTCPProxy: true,
2022-08-01 10:51:34 -04:00
}
c.loadbalancers = append(c.loadbalancers, lb)
}
}
func (c *Client) generateUID() (string, error) {
letters := []byte("abcdefghijklmnopqrstuvwxyz0123456789")
const uidLen = 5
uid := make([]byte, uidLen)
for i := 0; i < uidLen; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
uid[i] = letters[n.Int64()]
}
return string(uid), nil
}
2022-08-05 06:18:26 -04:00
// buildInstanceName returns a formatted name string.
// The names are joined with a '-'.
// If names is empty, the returned value is c.name + "-" + c.uid.
func (c *Client) buildResourceName(names ...string) string {
builder := strings.Builder{}
builder.WriteString(c.name)
builder.WriteRune('-')
for _, name := range names {
builder.WriteString(name)
builder.WriteRune('-')
}
builder.WriteString(c.uid)
return builder.String()
}
2022-08-05 06:09:39 -04:00
func (c *Client) resourceURI(scope resourceScope, resourceType, resourceName string) string {
const baseURI = "https://www.googleapis.com/compute/v1/projects/"
builder := strings.Builder{}
builder.WriteString(baseURI)
builder.WriteString(c.project)
switch scope {
case scopeGlobal:
builder.WriteString("/global/")
case scopeRegion:
builder.WriteString("/regions/")
builder.WriteString(c.region)
2022-08-05 06:18:26 -04:00
builder.WriteRune('/')
2022-08-05 06:09:39 -04:00
case scopeZone:
builder.WriteString("/zones/")
builder.WriteString(c.zone)
2022-08-05 06:18:26 -04:00
builder.WriteRune('/')
2022-08-05 06:09:39 -04:00
default:
panic("unknown scope")
}
builder.WriteString(resourceType)
2022-08-05 06:18:26 -04:00
builder.WriteRune('/')
2022-08-05 06:09:39 -04:00
builder.WriteString(resourceName)
return builder.String()
}
type resourceScope string
const (
scopeGlobal resourceScope = "global"
scopeRegion resourceScope = "region"
scopeZone resourceScope = "zone"
)
type closer interface {
Close() error
}
// closeAll closes all closers, even if an error occurs.
//
// Errors are collected and a composed error is returned.
func closeAll(closers []closer) error {
// Since this function is intended to be deferred, it will always call all
// close operations, even if a previous operation failed. The if multiple
// errors occur, the returned error will be composed of the error messages
// of those errors.
var err error
for _, closer := range closers {
err = multierr.Append(err, closer.Close())
}
return err
}
func isNotFoundError(err error) bool {
var gAPIErr *googleapi.Error
if !errors.As(err, &gAPIErr) {
return false
}
return gAPIErr.Code == http.StatusNotFound
}