2022-03-22 11:03:15 -04:00
package client
import (
"context"
"crypto/rand"
2022-08-09 04:26:29 -04:00
"encoding/json"
2022-08-01 08:58:23 -04:00
"errors"
2022-03-22 11:03:15 -04:00
"fmt"
"math/big"
2022-08-01 08:58:23 -04:00
"net/http"
2022-08-05 06:09:39 -04:00
"strings"
2022-03-22 11:03:15 -04:00
compute "cloud.google.com/go/compute/apiv1"
admin "cloud.google.com/go/iam/admin/apiv1"
resourcemanager "cloud.google.com/go/resourcemanager/apiv3"
2022-06-07 05:08:44 -04:00
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
2022-06-08 02:17:52 -04:00
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
2022-03-22 11:03:15 -04:00
"github.com/edgelesssys/constellation/internal/state"
2022-08-01 05:54:29 -04:00
"go.uber.org/multierr"
2022-08-09 04:26:29 -04:00
"golang.org/x/oauth2/google"
2022-08-01 08:58:23 -04:00
"google.golang.org/api/googleapi"
2022-03-22 11:03:15 -04:00
)
// 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-03-22 11:03:15 -04:00
instanceTemplateAPI
instanceGroupManagersAPI
iamAPI
projectsAPI
2022-08-01 10:51:34 -04:00
addressesAPI
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
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
2022-03-22 11:03:15 -04:00
}
// 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 )
forwardingRulesAPI , err := compute . NewForwardingRulesRESTClient ( ctx )
if err != nil {
_ = closeAll ( closers )
return nil , err
}
closers = append ( closers , forwardingRulesAPI )
backendServicesAPI , err := compute . NewRegionBackendServicesRESTClient ( ctx )
if err != nil {
_ = closeAll ( closers )
return nil , err
}
closers = append ( closers , backendServicesAPI )
targetPoolsAPI , err := compute . NewTargetPoolsRESTClient ( ctx )
if err != nil {
_ = closeAll ( closers )
return nil , err
}
closers = append ( closers , targetPoolsAPI )
healthChecksAPI , err := compute . NewRegionHealthChecksRESTClient ( ctx )
if err != nil {
_ = closeAll ( closers )
return nil , err
}
closers = append ( closers , healthChecksAPI )
2022-03-22 11:03:15 -04:00
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 )
addressesAPI , err := compute . NewAddressesRESTClient ( ctx )
if err != nil {
_ = closeAll ( closers )
return nil , err
}
2022-03-22 11:03:15 -04:00
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 } ,
healthChecksAPI : & healthChecksClient { healthChecksAPI } ,
2022-03-22 11:03:15 -04:00
instanceTemplateAPI : & instanceTemplateClient { templAPI } ,
instanceGroupManagersAPI : & instanceGroupManagersClient { groupAPI } ,
iamAPI : & iamClient { iamAPI } ,
projectsAPI : & projectsClient { projectsAPI } ,
2022-08-01 10:51:34 -04:00
addressesAPI : & addressesClient { addressesAPI } ,
2022-06-29 09:26:29 -04:00
workers : make ( cloudtypes . Instances ) ,
controlPlanes : make ( cloudtypes . Instances ) ,
2022-03-22 11:03:15 -04:00
} , nil
}
// NewInitialized creates an initialized client.
func NewInitialized ( ctx context . Context , project , zone , region , name string ) ( * Client , error ) {
2022-08-09 04:26:29 -04:00
// check if ADC are configured for the same project as the cluster
var defaultProject string
creds , err := google . FindDefaultCredentials ( ctx )
if err != nil {
return nil , err
}
// if the CLI is run by a service account, use the project of the service account
defaultProject = creds . ProjectID
// if the CLI is run by a user directly projectID will be empty, use the quota project id of the user instead
if defaultProject == "" {
var projectID struct {
ProjectID string ` json:"quota_project_id" `
}
if err := json . Unmarshal ( creds . JSON , & projectID ) ; err != nil {
return nil , err
}
defaultProject = projectID . ProjectID
}
if defaultProject != project {
return nil , fmt . Errorf ( "application default credentials are configured for project %q, but the cluster is configured for project %q" , defaultProject , project )
}
2022-03-22 11:03:15 -04:00
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 ,
2022-03-22 11:03:15 -04:00
c . operationZoneAPI ,
c . operationGlobalAPI ,
c . networksAPI ,
2022-06-09 16:26:36 -04:00
c . subnetworksAPI ,
2022-03-22 11:03:15 -04:00
c . firewallsAPI ,
2022-06-09 16:26:36 -04:00
c . forwardingRulesAPI ,
c . backendServicesAPI ,
c . healthChecksAPI ,
2022-03-22 11:03:15 -04:00
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 ,
2022-03-22 11:03:15 -04:00
}
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-03-22 11:03:15 -04:00
}
2022-08-01 10:51:34 -04:00
return stat
2022-03-22 11:03:15 -04:00
}
// 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
2022-06-29 09:26:29 -04:00
c . workerInstanceGroup = stat . GCPWorkerInstanceGroup
c . controlPlaneInstanceGroup = stat . GCPControlPlaneInstanceGroup
2022-03-22 11:03:15 -04:00
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
2022-06-29 09:26:29 -04:00
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 ,
}
c . loadbalancers = append ( c . loadbalancers , lb )
}
2022-03-22 11:03:15 -04:00
}
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"
)
2022-03-22 11:03:15 -04:00
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.
2022-08-01 05:54:29 -04:00
var err error
2022-03-22 11:03:15 -04:00
for _ , closer := range closers {
2022-08-01 05:54:29 -04:00
err = multierr . Append ( err , closer . Close ( ) )
2022-03-22 11:03:15 -04:00
}
2022-08-01 05:54:29 -04:00
return err
2022-03-22 11:03:15 -04:00
}
2022-08-01 08:58:23 -04:00
func isNotFoundError ( err error ) bool {
var gAPIErr * googleapi . Error
if ! errors . As ( err , & gAPIErr ) {
return false
}
return gAPIErr . Code == http . StatusNotFound
}