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-03-22 11:03:15 -04:00
"errors"
"fmt"
"math/big"
"strings"
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-09 04:26:29 -04:00
"golang.org/x/oauth2/google"
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-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
serviceAccount string
2022-06-09 16:26:36 -04:00
// loadbalancer
healthCheck string
backendService string
forwardingRule string
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
}
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-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-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.
func ( c * Client ) GetState ( ) ( state . ConstellationState , error ) {
var stat state . ConstellationState
stat . CloudProvider = cloudprovider . GCP . String ( )
2022-06-29 09:26:29 -04:00
if len ( c . workers ) == 0 {
return state . ConstellationState { } , errors . New ( "client has no workers" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPWorkers = c . workers
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if len ( c . controlPlanes ) == 0 {
return state . ConstellationState { } , errors . New ( "client has no controlPlanes" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPControlPlanes = c . controlPlanes
2022-07-08 04:59:59 -04:00
publicIPs := c . controlPlanes . PublicIPs ( )
if len ( publicIPs ) == 0 {
return state . ConstellationState { } , errors . New ( "client has no bootstrapper endpoint" )
}
stat . BootstrapperHost = publicIPs [ 0 ]
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if c . workerInstanceGroup == "" {
return state . ConstellationState { } , errors . New ( "client has no workerInstanceGroup" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPWorkerInstanceGroup = c . workerInstanceGroup
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if c . controlPlaneInstanceGroup == "" {
return state . ConstellationState { } , errors . New ( "client has no controlPlaneInstanceGroup" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPControlPlaneInstanceGroup = c . controlPlaneInstanceGroup
2022-03-22 11:03:15 -04:00
if c . project == "" {
return state . ConstellationState { } , errors . New ( "client has no project" )
}
stat . GCPProject = c . project
if c . zone == "" {
return state . ConstellationState { } , errors . New ( "client has no zone" )
}
stat . GCPZone = c . zone
if c . region == "" {
return state . ConstellationState { } , errors . New ( "client has no region" )
}
stat . GCPRegion = c . region
if c . name == "" {
return state . ConstellationState { } , errors . New ( "client has no name" )
}
stat . Name = c . name
if c . uid == "" {
return state . ConstellationState { } , errors . New ( "client has no uid" )
}
stat . UID = c . uid
if len ( c . firewalls ) == 0 {
return state . ConstellationState { } , errors . New ( "client has no firewalls" )
}
stat . GCPFirewalls = c . firewalls
if c . network == "" {
return state . ConstellationState { } , errors . New ( "client has no network" )
}
stat . GCPNetwork = c . network
if c . subnetwork == "" {
return state . ConstellationState { } , errors . New ( "client has no subnetwork" )
}
stat . GCPSubnetwork = c . subnetwork
2022-06-29 09:26:29 -04:00
if c . workerTemplate == "" {
return state . ConstellationState { } , errors . New ( "client has no worker instance template" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPWorkerInstanceTemplate = c . workerTemplate
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if c . controlPlaneTemplate == "" {
return state . ConstellationState { } , errors . New ( "client has no controlPlane instance template" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
stat . GCPControlPlaneInstanceTemplate = c . controlPlaneTemplate
2022-03-22 11:03:15 -04:00
2022-06-09 16:26:36 -04:00
if c . healthCheck == "" {
return state . ConstellationState { } , errors . New ( "client has no health check" )
}
stat . GCPHealthCheck = c . healthCheck
if c . backendService == "" {
return state . ConstellationState { } , errors . New ( "client has no backend service" )
}
stat . GCPBackendService = c . backendService
if c . forwardingRule == "" {
return state . ConstellationState { } , errors . New ( "client has no forwarding rule" )
}
stat . GCPForwardingRule = c . forwardingRule
2022-03-22 11:03:15 -04:00
// service account does not have to be set at all times
stat . GCPServiceAccount = c . serviceAccount
return stat , nil
}
// SetState sets the state of the client to the handed ConstellationState.
func ( c * Client ) SetState ( stat state . ConstellationState ) error {
if stat . CloudProvider != cloudprovider . GCP . String ( ) {
return errors . New ( "state is not gcp state" )
}
2022-06-29 09:26:29 -04:00
if len ( stat . GCPWorkers ) == 0 {
return errors . New ( "state has no workers" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . workers = stat . GCPWorkers
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if len ( stat . GCPControlPlanes ) == 0 {
return errors . New ( "state has no controlPlane" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . controlPlanes = stat . GCPControlPlanes
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if stat . GCPWorkerInstanceGroup == "" {
return errors . New ( "state has no workerInstanceGroup" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . workerInstanceGroup = stat . GCPWorkerInstanceGroup
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if stat . GCPControlPlaneInstanceGroup == "" {
return errors . New ( "state has no controlPlaneInstanceGroup" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . controlPlaneInstanceGroup = stat . GCPControlPlaneInstanceGroup
2022-03-22 11:03:15 -04:00
if stat . GCPProject == "" {
return errors . New ( "state has no project" )
}
c . project = stat . GCPProject
if stat . GCPZone == "" {
return errors . New ( "state has no zone" )
}
c . zone = stat . GCPZone
if stat . GCPRegion == "" {
return errors . New ( "state has no region" )
}
c . region = stat . GCPRegion
if stat . Name == "" {
return errors . New ( "state has no name" )
}
c . name = stat . Name
if stat . UID == "" {
return errors . New ( "state has no uid" )
}
c . uid = stat . UID
if len ( stat . GCPFirewalls ) == 0 {
return errors . New ( "state has no firewalls" )
}
c . firewalls = stat . GCPFirewalls
if stat . GCPNetwork == "" {
return errors . New ( "state has no network" )
}
c . network = stat . GCPNetwork
if stat . GCPSubnetwork == "" {
return errors . New ( "state has no subnetwork" )
}
c . subnetwork = stat . GCPSubnetwork
2022-06-29 09:26:29 -04:00
if stat . GCPWorkerInstanceTemplate == "" {
return errors . New ( "state has no worker instance template" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . workerTemplate = stat . GCPWorkerInstanceTemplate
2022-03-22 11:03:15 -04:00
2022-06-29 09:26:29 -04:00
if stat . GCPControlPlaneInstanceTemplate == "" {
return errors . New ( "state has no controlPlane instance template" )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
c . controlPlaneTemplate = stat . GCPControlPlaneInstanceTemplate
2022-03-22 11:03:15 -04:00
2022-06-09 16:26:36 -04:00
if stat . GCPHealthCheck == "" {
return errors . New ( "state has no health check" )
}
c . healthCheck = stat . GCPHealthCheck
if stat . GCPBackendService == "" {
return errors . New ( "state has no backend service" )
}
c . backendService = stat . GCPBackendService
if stat . GCPForwardingRule == "" {
return errors . New ( "state has no forwarding rule" )
}
c . forwardingRule = stat . GCPForwardingRule
2022-03-22 11:03:15 -04:00
// service account does not have to be set at all times
c . serviceAccount = stat . GCPServiceAccount
return nil
}
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
}
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 errs [ ] error
for _ , closer := range closers {
errs = append ( errs , closer . Close ( ) )
}
return composeErr ( errs )
}
// composeErr composes a list of errors to a single error.
//
// If all errs are nil, the returned error is also nil.
func composeErr ( errs [ ] error ) error {
var composed strings . Builder
for i , err := range errs {
if err != nil {
composed . WriteString ( fmt . Sprintf ( "%d: %s" , i , err . Error ( ) ) )
}
}
if composed . Len ( ) != 0 {
return errors . New ( composed . String ( ) )
}
return nil
}