mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 23:19:39 -05:00
AB#2523 Refactor GCP metadata/cloud API (#387)
* Refactor GCP metadata/cloud API * Remove cloud controller manager from metadata package * Remove PublicIP * Move shared cloud packages * Remove dead code Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
e9fecec0bc
commit
c9873f2bfb
@ -105,9 +105,8 @@ func main() {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to set up cloud logger")
|
||||
}
|
||||
|
||||
cloudControllerManager := &awscloud.CloudControllerManager{}
|
||||
clusterInitJoiner = kubernetes.New(
|
||||
"aws", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(), cloudControllerManager,
|
||||
"aws", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(),
|
||||
metadata, pcrsJSON, helmClient, &kubewaiter.CloudKubeAPIWaiter{},
|
||||
)
|
||||
openTPM = vtpm.OpenVTPM
|
||||
@ -121,30 +120,24 @@ func main() {
|
||||
|
||||
issuer = initserver.NewIssuerWrapper(gcp.NewIssuer(), vmtype.Unknown, nil)
|
||||
|
||||
gcpClient, err := gcpcloud.NewClient(ctx)
|
||||
metadata, err := gcpcloud.New(ctx)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to create GCP metadata client")
|
||||
}
|
||||
metadata := gcpcloud.New(gcpClient)
|
||||
descr, err := metadata.Self(ctx)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to get instance metadata")
|
||||
}
|
||||
cloudLogger, err = gcpcloud.NewLogger(ctx, descr.ProviderID, "constellation-boot-log")
|
||||
defer metadata.Close()
|
||||
|
||||
cloudLogger, err = gcpcloud.NewLogger(ctx, "constellation-boot-log")
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to set up cloud logger")
|
||||
}
|
||||
|
||||
metadataAPI = metadata
|
||||
pcrsJSON, err := json.Marshal(pcrs)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to marshal PCRs")
|
||||
}
|
||||
cloudControllerManager, err := gcpcloud.NewCloudControllerManager(ctx, metadata)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to create cloud controller manager")
|
||||
}
|
||||
clusterInitJoiner = kubernetes.New(
|
||||
"gcp", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(), cloudControllerManager,
|
||||
"gcp", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(),
|
||||
metadata, pcrsJSON, helmClient, &kubewaiter.CloudKubeAPIWaiter{},
|
||||
)
|
||||
openTPM = vtpm.OpenVTPM
|
||||
@ -178,7 +171,7 @@ func main() {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to marshal PCRs")
|
||||
}
|
||||
clusterInitJoiner = kubernetes.New(
|
||||
"azure", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(), azurecloud.NewCloudControllerManager(metadata),
|
||||
"azure", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(),
|
||||
metadata, pcrsJSON, helmClient, &kubewaiter.CloudKubeAPIWaiter{},
|
||||
)
|
||||
|
||||
@ -200,7 +193,7 @@ func main() {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to marshal PCRs")
|
||||
}
|
||||
clusterInitJoiner = kubernetes.New(
|
||||
"qemu", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(), &qemucloud.CloudControllerManager{},
|
||||
"qemu", k8sapi.NewKubernetesUtil(), &k8sapi.KubdeadmConfiguration{}, kubectl.New(),
|
||||
metadata, pcrsJSON, helmClient, &kubewaiter.CloudKubeAPIWaiter{},
|
||||
)
|
||||
metadataAPI = metadata
|
||||
|
@ -52,18 +52,6 @@ func (f *providerMetadataFake) Self(ctx context.Context) (metadata.InstanceMetad
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *providerMetadataFake) SignalRole(ctx context.Context, role role.Role) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *providerMetadataFake) SetVPNIP(ctx context.Context, vpnIP string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *providerMetadataFake) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *providerMetadataFake) Supported() bool {
|
||||
return true
|
||||
}
|
||||
|
@ -10,9 +10,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ProviderMetadata implementers read/write cloud provider metadata.
|
||||
@ -27,63 +24,6 @@ type ProviderMetadata interface {
|
||||
GetLoadBalancerEndpoint(ctx context.Context) (string, error)
|
||||
// GetInstance retrieves an instance using its providerID.
|
||||
GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error)
|
||||
// Supported is used to determine if metadata API is implemented for this cloud provider.
|
||||
Supported() bool
|
||||
}
|
||||
|
||||
// CloudControllerManager implementers provide configuration for the k8s cloud-controller-manager.
|
||||
type CloudControllerManager interface {
|
||||
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
|
||||
Image(k8sVersion versions.ValidK8sVersion) (string, error)
|
||||
// Path returns the path used by cloud-controller-manager executable within the container image.
|
||||
Path() string
|
||||
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
|
||||
Name() string
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
|
||||
ExtraArgs() []string
|
||||
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
|
||||
ConfigMaps() (kubernetes.ConfigMaps, error)
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
|
||||
Secrets(ctx context.Context, providerID, cloudServiceAccountURI string) (kubernetes.Secrets, error)
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
|
||||
Volumes() []k8s.Volume
|
||||
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
|
||||
VolumeMounts() []k8s.VolumeMount
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
|
||||
Env() []k8s.EnvVar
|
||||
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
|
||||
Supported() bool
|
||||
}
|
||||
|
||||
// CloudNodeManager implementers provide configuration for the k8s cloud-node-manager.
|
||||
type CloudNodeManager interface {
|
||||
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
|
||||
Image(k8sVersion versions.ValidK8sVersion) (string, error)
|
||||
// Path returns the path used by cloud-node-manager executable within the container image.
|
||||
Path() string
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
|
||||
ExtraArgs() []string
|
||||
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
|
||||
Supported() bool
|
||||
}
|
||||
|
||||
// ClusterAutoscaler implementers provide configuration for the k8s cluster-autoscaler.
|
||||
type ClusterAutoscaler interface {
|
||||
// Name returns the cloud-provider name as used by k8s cluster-autoscaler.
|
||||
Name() string
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
|
||||
Secrets(providerID, cloudServiceAccountURI string) (kubernetes.Secrets, error)
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
|
||||
Volumes() []k8s.Volume
|
||||
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
|
||||
VolumeMounts() []k8s.VolumeMount
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
|
||||
Env() []k8s.EnvVar
|
||||
// Supported is used to determine if cluster autoscaler is implemented for this cloud provider.
|
||||
Supported() bool
|
||||
}
|
||||
|
||||
type stubProviderMetadata struct {
|
||||
@ -99,9 +39,6 @@ type stubProviderMetadata struct {
|
||||
GetInstanceErr error
|
||||
GetInstanceResp metadata.InstanceMetadata
|
||||
|
||||
SupportedResp bool
|
||||
SupportsLoadBalancerResp bool
|
||||
|
||||
UIDErr error
|
||||
UIDResp string
|
||||
}
|
||||
@ -122,90 +59,6 @@ func (m *stubProviderMetadata) GetInstance(ctx context.Context, providerID strin
|
||||
return m.GetInstanceResp, m.GetInstanceErr
|
||||
}
|
||||
|
||||
func (m *stubProviderMetadata) Supported() bool {
|
||||
return m.SupportedResp
|
||||
}
|
||||
|
||||
func (m *stubProviderMetadata) SupportsLoadBalancer() bool {
|
||||
return m.SupportsLoadBalancerResp
|
||||
}
|
||||
|
||||
func (m *stubProviderMetadata) UID(ctx context.Context) (string, error) {
|
||||
return m.UIDResp, m.UIDErr
|
||||
}
|
||||
|
||||
type stubCloudControllerManager struct {
|
||||
SupportedResp bool
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "stub-image:latest", nil
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Path() string {
|
||||
return "/stub-controller-manager"
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Name() string {
|
||||
return "stub"
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) ConfigMaps() (kubernetes.ConfigMaps, error) {
|
||||
return []*k8s.ConfigMap{}, nil
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Secrets(ctx context.Context, instance, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
return []*k8s.Secret{}, nil
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{}
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{}
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{}
|
||||
}
|
||||
|
||||
func (m *stubCloudControllerManager) Supported() bool {
|
||||
return m.SupportedResp
|
||||
}
|
||||
|
||||
type stubClusterAutoscaler struct {
|
||||
SupportedResp bool
|
||||
}
|
||||
|
||||
func (a *stubClusterAutoscaler) Name() string {
|
||||
return "stub"
|
||||
}
|
||||
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cluster-autoscaler.
|
||||
func (a *stubClusterAutoscaler) Secrets(instance, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
return kubernetes.Secrets{}, nil
|
||||
}
|
||||
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cluster-autoscaler.
|
||||
func (a *stubClusterAutoscaler) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{}
|
||||
}
|
||||
|
||||
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cluster-autoscaler.
|
||||
func (a *stubClusterAutoscaler) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{}
|
||||
}
|
||||
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cluster-autoscaler.
|
||||
func (a *stubClusterAutoscaler) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{}
|
||||
}
|
||||
|
||||
func (a *stubClusterAutoscaler) Supported() bool {
|
||||
return a.SupportedResp
|
||||
}
|
||||
|
@ -21,12 +21,11 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/k8sapi"
|
||||
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/k8sapi/resources"
|
||||
kubewaiter "github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/kubeWaiter"
|
||||
"github.com/edgelesssys/constellation/v2/internal/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
@ -61,14 +60,13 @@ type KubeWrapper struct {
|
||||
configProvider configurationProvider
|
||||
client k8sapi.Client
|
||||
kubeconfigReader configReader
|
||||
cloudControllerManager CloudControllerManager
|
||||
providerMetadata ProviderMetadata
|
||||
initialMeasurementsJSON []byte
|
||||
getIPAddr func() (string, error)
|
||||
}
|
||||
|
||||
// New creates a new KubeWrapper with real values.
|
||||
func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client, cloudControllerManager CloudControllerManager,
|
||||
func New(cloudProvider string, clusterUtil clusterUtil, configProvider configurationProvider, client k8sapi.Client,
|
||||
providerMetadata ProviderMetadata, initialMeasurementsJSON []byte, helmClient helmClient, kubeAPIWaiter kubeAPIWaiter,
|
||||
) *KubeWrapper {
|
||||
return &KubeWrapper{
|
||||
@ -79,7 +77,6 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura
|
||||
configProvider: configProvider,
|
||||
client: client,
|
||||
kubeconfigReader: &KubeconfigReader{fs: afero.Afero{Fs: afero.NewOsFs()}},
|
||||
cloudControllerManager: cloudControllerManager,
|
||||
providerMetadata: providerMetadata,
|
||||
initialMeasurementsJSON: initialMeasurementsJSON,
|
||||
getIPAddr: getIPAddr,
|
||||
@ -101,56 +98,47 @@ func (k *KubeWrapper) InitCluster(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := k.getIPAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeName := ip
|
||||
var providerID string
|
||||
var instance metadata.InstanceMetadata
|
||||
var nodePodCIDR string
|
||||
var subnetworkPodCIDR string
|
||||
var controlPlaneEndpoint string // this is the endpoint in "kubeadm init --control-plane-endpoint=<IP/DNS>:<port>"
|
||||
var nodeIP string
|
||||
var validIPs []net.IP
|
||||
|
||||
// Step 1: retrieve cloud metadata for Kubernetes configuration
|
||||
if k.providerMetadata.Supported() {
|
||||
log.Infof("Retrieving node metadata")
|
||||
instance, err = k.providerMetadata.Self(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving own instance metadata: %w", err)
|
||||
}
|
||||
if instance.VPCIP != "" {
|
||||
validIPs = append(validIPs, net.ParseIP(instance.VPCIP))
|
||||
}
|
||||
nodeName = k8sCompliantHostname(instance.Name)
|
||||
providerID = instance.ProviderID
|
||||
nodeIP = instance.VPCIP
|
||||
subnetworkPodCIDR = instance.SecondaryIPRange
|
||||
|
||||
if len(instance.AliasIPRanges) > 0 {
|
||||
nodePodCIDR = instance.AliasIPRanges[0]
|
||||
}
|
||||
controlPlaneEndpoint, err = k.providerMetadata.GetLoadBalancerEndpoint(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving load balancer endpoint: %w", err)
|
||||
}
|
||||
log.Infof("Retrieving node metadata")
|
||||
instance, err := k.providerMetadata.Self(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving own instance metadata: %w", err)
|
||||
}
|
||||
if instance.VPCIP != "" {
|
||||
validIPs = append(validIPs, net.ParseIP(instance.VPCIP))
|
||||
}
|
||||
nodeName := k8sCompliantHostname(instance.Name)
|
||||
nodeIP := instance.VPCIP
|
||||
subnetworkPodCIDR := instance.SecondaryIPRange
|
||||
if len(instance.AliasIPRanges) > 0 {
|
||||
nodePodCIDR = instance.AliasIPRanges[0]
|
||||
}
|
||||
|
||||
// this is the endpoint in "kubeadm init --control-plane-endpoint=<IP/DNS>:<port>"
|
||||
controlPlaneEndpoint, err := k.providerMetadata.GetLoadBalancerEndpoint(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving load balancer endpoint: %w", err)
|
||||
}
|
||||
|
||||
log.With(
|
||||
zap.String("nodeName", nodeName),
|
||||
zap.String("providerID", providerID),
|
||||
zap.String("providerID", instance.ProviderID),
|
||||
zap.String("nodeIP", nodeIP),
|
||||
zap.String("controlPlaneEndpoint", controlPlaneEndpoint),
|
||||
zap.String("podCIDR", subnetworkPodCIDR),
|
||||
).Infof("Setting information for node")
|
||||
|
||||
// Step 2: configure kubeadm init config
|
||||
initConfig := k.configProvider.InitConfiguration(k.cloudControllerManager.Supported(), k8sVersion)
|
||||
ccmSupported := cloudprovider.FromString(k.cloudProvider) == cloudprovider.Azure ||
|
||||
cloudprovider.FromString(k.cloudProvider) == cloudprovider.GCP
|
||||
initConfig := k.configProvider.InitConfiguration(ccmSupported, k8sVersion)
|
||||
initConfig.SetNodeIP(nodeIP)
|
||||
initConfig.SetCertSANs([]string{nodeIP})
|
||||
initConfig.SetNodeName(nodeName)
|
||||
initConfig.SetProviderID(providerID)
|
||||
initConfig.SetProviderID(instance.ProviderID)
|
||||
initConfig.SetControlPlaneEndpoint(controlPlaneEndpoint)
|
||||
initConfigYAML, err := initConfig.Marshal()
|
||||
if err != nil {
|
||||
@ -262,28 +250,19 @@ func (k *KubeWrapper) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTo
|
||||
}
|
||||
|
||||
// Step 1: retrieve cloud metadata for Kubernetes configuration
|
||||
nodeInternalIP, err := k.getIPAddr()
|
||||
log.Infof("Retrieving node metadata")
|
||||
instance, err := k.providerMetadata.Self(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("retrieving own instance metadata: %w", err)
|
||||
}
|
||||
nodeName := nodeInternalIP
|
||||
var providerID string
|
||||
var loadbalancerEndpoint string
|
||||
if k.providerMetadata.Supported() {
|
||||
log.Infof("Retrieving node metadata")
|
||||
instance, err := k.providerMetadata.Self(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving own instance metadata: %w", err)
|
||||
}
|
||||
providerID = instance.ProviderID
|
||||
nodeName = instance.Name
|
||||
nodeInternalIP = instance.VPCIP
|
||||
loadbalancerEndpoint, err = k.providerMetadata.GetLoadBalancerEndpoint(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving loadbalancer endpoint: %w", err)
|
||||
}
|
||||
providerID := instance.ProviderID
|
||||
nodeInternalIP := instance.VPCIP
|
||||
nodeName := k8sCompliantHostname(instance.Name)
|
||||
|
||||
loadbalancerEndpoint, err := k.providerMetadata.GetLoadBalancerEndpoint(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving own instance metadata: %w", err)
|
||||
}
|
||||
nodeName = k8sCompliantHostname(nodeName)
|
||||
|
||||
log.With(
|
||||
zap.String("nodeName", nodeName),
|
||||
@ -292,7 +271,9 @@ func (k *KubeWrapper) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTo
|
||||
).Infof("Setting information for node")
|
||||
|
||||
// Step 2: configure kubeadm join config
|
||||
joinConfig := k.configProvider.JoinConfiguration(k.cloudControllerManager.Supported())
|
||||
ccmSupported := cloudprovider.FromString(k.cloudProvider) == cloudprovider.Azure ||
|
||||
cloudprovider.FromString(k.cloudProvider) == cloudprovider.GCP
|
||||
joinConfig := k.configProvider.JoinConfiguration(ccmSupported)
|
||||
joinConfig.SetAPIServerEndpoint(args.APIServerEndpoint)
|
||||
joinConfig.SetToken(args.Token)
|
||||
joinConfig.AppendDiscoveryTokenCaCertHash(args.CACertHashes[0])
|
||||
@ -443,85 +424,85 @@ func (k *KubeWrapper) setupExtraVals(ctx context.Context, initialMeasurementsJSO
|
||||
|
||||
switch cloudprovider.FromString(k.cloudProvider) {
|
||||
case cloudprovider.GCP:
|
||||
{
|
||||
uid, err := k.providerMetadata.UID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting uid: %w", err)
|
||||
}
|
||||
|
||||
projectID, _, _, err := gcpshared.SplitProviderID(instance.ProviderID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("splitting providerID: %w", err)
|
||||
}
|
||||
|
||||
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting service account key: %w", err)
|
||||
}
|
||||
rawKey, err := json.Marshal(serviceAccountKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling service account key: %w", err)
|
||||
}
|
||||
|
||||
ccmVals, ok := extraVals["ccm"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid ccm values")
|
||||
}
|
||||
ccmVals["GCP"] = map[string]any{
|
||||
"projectID": projectID,
|
||||
"uid": uid,
|
||||
"secretData": string(rawKey),
|
||||
"subnetworkPodCIDR": subnetworkPodCIDR,
|
||||
}
|
||||
uid, err := k.providerMetadata.UID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting uid: %w", err)
|
||||
}
|
||||
|
||||
projectID, _, _, err := gcpshared.SplitProviderID(instance.ProviderID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("splitting providerID: %w", err)
|
||||
}
|
||||
|
||||
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting service account key: %w", err)
|
||||
}
|
||||
rawKey, err := json.Marshal(serviceAccountKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling service account key: %w", err)
|
||||
}
|
||||
|
||||
ccmVals, ok := extraVals["ccm"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid ccm values")
|
||||
}
|
||||
ccmVals["GCP"] = map[string]any{
|
||||
"projectID": projectID,
|
||||
"uid": uid,
|
||||
"secretData": string(rawKey),
|
||||
"subnetworkPodCIDR": subnetworkPodCIDR,
|
||||
}
|
||||
|
||||
case cloudprovider.Azure:
|
||||
{
|
||||
// TODO: After refactoring the ProviderMetadata interface this section should be rewritten.
|
||||
// Currently, we have to rely on the Secrets(..) method, as GetNetworkSecurityGroupName & GetLoadBalancerName
|
||||
// rely on Azure specific API endpoints.
|
||||
ccmSecrets, err := k.cloudControllerManager.Secrets(ctx, instance.ProviderID, cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating ccm secret: %w", err)
|
||||
}
|
||||
if len(ccmSecrets) < 1 {
|
||||
return nil, errors.New("missing secret")
|
||||
}
|
||||
rawConfig := ccmSecrets[0].Data["azure.json"]
|
||||
|
||||
ccmVals, ok := extraVals["ccm"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid ccm values")
|
||||
}
|
||||
ccmVals["Azure"] = map[string]any{
|
||||
"azureConfig": string(rawConfig),
|
||||
"subnetworkPodCIDR": subnetworkPodCIDR,
|
||||
}
|
||||
|
||||
joinVals, ok := extraVals["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid join-service values")
|
||||
}
|
||||
joinVals["idkeydigest"] = hex.EncodeToString(idkeydigest)
|
||||
|
||||
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds, err := azureshared.ApplicationCredentialsFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extraVals["autoscaler"] = map[string]any{
|
||||
"Azure": map[string]any{
|
||||
"clientID": creds.AppClientID,
|
||||
"clientSecret": creds.ClientSecretValue,
|
||||
"resourceGroup": resourceGroup,
|
||||
"subscriptionID": subscriptionID,
|
||||
"tenantID": creds.TenantID,
|
||||
},
|
||||
}
|
||||
ccmAzure, ok := k.providerMetadata.(ccmConfigGetter)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid cloud provider metadata for Azure")
|
||||
}
|
||||
|
||||
ccmConfig, err := ccmAzure.GetCCMConfig(ctx, instance.ProviderID, cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating ccm secret: %w", err)
|
||||
}
|
||||
|
||||
ccmVals, ok := extraVals["ccm"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid ccm values")
|
||||
}
|
||||
ccmVals["Azure"] = map[string]any{
|
||||
"azureConfig": string(ccmConfig),
|
||||
"subnetworkPodCIDR": subnetworkPodCIDR,
|
||||
}
|
||||
|
||||
joinVals, ok := extraVals["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid join-service values")
|
||||
}
|
||||
joinVals["idkeydigest"] = hex.EncodeToString(idkeydigest)
|
||||
|
||||
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds, err := azureshared.ApplicationCredentialsFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extraVals["autoscaler"] = map[string]any{
|
||||
"Azure": map[string]any{
|
||||
"clientID": creds.AppClientID,
|
||||
"clientSecret": creds.ClientSecretValue,
|
||||
"resourceGroup": resourceGroup,
|
||||
"subscriptionID": subscriptionID,
|
||||
"tenantID": creds.TenantID,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
return extraVals, nil
|
||||
}
|
||||
|
||||
type ccmConfigGetter interface {
|
||||
GetCCMConfig(ctx context.Context, providerID, cloudServiceAccountURI string) ([]byte, error)
|
||||
}
|
||||
|
@ -45,41 +45,16 @@ func TestInitCluster(t *testing.T) {
|
||||
aliasIPRange := "192.0.2.0/24"
|
||||
|
||||
testCases := map[string]struct {
|
||||
clusterUtil stubClusterUtil
|
||||
helmClient stubHelmClient
|
||||
kubectl stubKubectl
|
||||
kubeAPIWaiter stubKubeAPIWaiter
|
||||
providerMetadata ProviderMetadata
|
||||
CloudControllerManager CloudControllerManager
|
||||
ClusterAutoscaler ClusterAutoscaler
|
||||
kubeconfigReader configReader
|
||||
wantConfig k8sapi.KubeadmInitYAML
|
||||
wantErr bool
|
||||
k8sVersion versions.ValidK8sVersion
|
||||
clusterUtil stubClusterUtil
|
||||
helmClient stubHelmClient
|
||||
kubectl stubKubectl
|
||||
kubeAPIWaiter stubKubeAPIWaiter
|
||||
providerMetadata ProviderMetadata
|
||||
kubeconfigReader configReader
|
||||
wantConfig k8sapi.KubeadmInitYAML
|
||||
wantErr bool
|
||||
k8sVersion versions.ValidK8sVersion
|
||||
}{
|
||||
"kubeadm init works without metadata": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{SupportedResp: false},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantConfig: k8sapi.KubeadmInitYAML{
|
||||
InitConfiguration: kubeadm.InitConfiguration{
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
KubeletExtraArgs: map[string]string{
|
||||
"node-ip": "",
|
||||
"provider-id": "",
|
||||
},
|
||||
Name: privateIP,
|
||||
},
|
||||
},
|
||||
ClusterConfiguration: kubeadm.ClusterConfiguration{},
|
||||
},
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init works with metadata and loadbalancer": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
@ -87,7 +62,6 @@ func TestInitCluster(t *testing.T) {
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SupportedResp: true,
|
||||
SelfResp: metadata.InstanceMetadata{
|
||||
Name: nodeName,
|
||||
ProviderID: providerID,
|
||||
@ -95,10 +69,7 @@ func TestInitCluster(t *testing.T) {
|
||||
AliasIPRanges: []string{aliasIPRange},
|
||||
},
|
||||
GetLoadBalancerEndpointResp: loadbalancerIP,
|
||||
SupportsLoadBalancerResp: true,
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantConfig: k8sapi.KubeadmInitYAML{
|
||||
InitConfiguration: kubeadm.InitConfiguration{
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
@ -126,13 +97,10 @@ func TestInitCluster(t *testing.T) {
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SelfErr: someErr,
|
||||
SupportedResp: true,
|
||||
SelfErr: someErr,
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when retrieving metadata loadbalancer ip": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
@ -141,25 +109,19 @@ func TestInitCluster(t *testing.T) {
|
||||
},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
GetLoadBalancerEndpointErr: someErr,
|
||||
SupportsLoadBalancerResp: true,
|
||||
SupportedResp: true,
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when applying the init config": {
|
||||
clusterUtil: stubClusterUtil{initClusterErr: someErr},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when deploying cilium": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
@ -167,11 +129,9 @@ func TestInitCluster(t *testing.T) {
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when setting up constellation-services chart": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
@ -179,12 +139,10 @@ func TestInitCluster(t *testing.T) {
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{SupportedResp: true},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when setting the cloud node manager": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
@ -192,12 +150,10 @@ func TestInitCluster(t *testing.T) {
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when setting the cluster autoscaler": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
@ -205,72 +161,60 @@ func TestInitCluster(t *testing.T) {
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{SupportedResp: true},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when reading kubeconfig": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
ReadErr: someErr,
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when setting up konnectivity": {
|
||||
clusterUtil: stubClusterUtil{setupKonnectivityError: someErr},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{SupportedResp: false},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when setting up verification service": {
|
||||
clusterUtil: stubClusterUtil{setupVerificationServiceErr: someErr},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{SupportedResp: false},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
wantErr: true,
|
||||
k8sVersion: versions.Default,
|
||||
},
|
||||
"kubeadm init fails when waiting for kubeAPI server": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{waitErr: someErr},
|
||||
providerMetadata: &stubProviderMetadata{SupportedResp: false},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
k8sVersion: versions.Default,
|
||||
wantErr: true,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{waitErr: someErr},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
k8sVersion: versions.Default,
|
||||
wantErr: true,
|
||||
},
|
||||
"unsupported k8sVersion fails cluster creation": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
kubeconfigReader: &stubKubeconfigReader{
|
||||
Kubeconfig: []byte("someKubeconfig"),
|
||||
},
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
ClusterAutoscaler: &stubClusterAutoscaler{},
|
||||
k8sVersion: "1.19",
|
||||
wantErr: true,
|
||||
kubeAPIWaiter: stubKubeAPIWaiter{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
k8sVersion: "1.19",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -280,15 +224,14 @@ func TestInitCluster(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
kube := KubeWrapper{
|
||||
clusterUtil: &tc.clusterUtil,
|
||||
helmClient: &tc.helmClient,
|
||||
providerMetadata: tc.providerMetadata,
|
||||
kubeAPIWaiter: &tc.kubeAPIWaiter,
|
||||
cloudControllerManager: tc.CloudControllerManager,
|
||||
configProvider: &stubConfigProvider{InitConfig: k8sapi.KubeadmInitYAML{}},
|
||||
client: &tc.kubectl,
|
||||
kubeconfigReader: tc.kubeconfigReader,
|
||||
getIPAddr: func() (string, error) { return privateIP, nil },
|
||||
clusterUtil: &tc.clusterUtil,
|
||||
helmClient: &tc.helmClient,
|
||||
providerMetadata: tc.providerMetadata,
|
||||
kubeAPIWaiter: &tc.kubeAPIWaiter,
|
||||
configProvider: &stubConfigProvider{InitConfig: k8sapi.KubeadmInitYAML{}},
|
||||
client: &tc.kubectl,
|
||||
kubeconfigReader: tc.kubeconfigReader,
|
||||
getIPAddr: func() (string, error) { return privateIP, nil },
|
||||
}
|
||||
|
||||
_, err := kube.InitCluster(
|
||||
@ -322,40 +265,22 @@ func TestJoinCluster(t *testing.T) {
|
||||
k8sVersion := versions.Default
|
||||
|
||||
testCases := map[string]struct {
|
||||
clusterUtil stubClusterUtil
|
||||
providerMetadata ProviderMetadata
|
||||
CloudControllerManager CloudControllerManager
|
||||
wantConfig kubeadm.JoinConfiguration
|
||||
role role.Role
|
||||
wantErr bool
|
||||
clusterUtil stubClusterUtil
|
||||
providerMetadata ProviderMetadata
|
||||
wantConfig kubeadm.JoinConfiguration
|
||||
role role.Role
|
||||
wantErr bool
|
||||
}{
|
||||
"kubeadm join worker works without metadata": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
role: role.Worker,
|
||||
wantConfig: kubeadm.JoinConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
BootstrapToken: joinCommand,
|
||||
},
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
Name: privateIP,
|
||||
KubeletExtraArgs: map[string]string{"node-ip": privateIP},
|
||||
},
|
||||
},
|
||||
},
|
||||
"kubeadm join worker works with metadata": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SupportedResp: true,
|
||||
SelfResp: metadata.InstanceMetadata{
|
||||
ProviderID: "provider-id",
|
||||
Name: "metadata-name",
|
||||
VPCIP: "192.0.2.1",
|
||||
},
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
role: role.Worker,
|
||||
role: role.Worker,
|
||||
wantConfig: kubeadm.JoinConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
BootstrapToken: joinCommand,
|
||||
@ -369,16 +294,12 @@ func TestJoinCluster(t *testing.T) {
|
||||
"kubeadm join worker works with metadata and cloud controller manager": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SupportedResp: true,
|
||||
SelfResp: metadata.InstanceMetadata{
|
||||
ProviderID: "provider-id",
|
||||
Name: "metadata-name",
|
||||
VPCIP: "192.0.2.1",
|
||||
},
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{
|
||||
SupportedResp: true,
|
||||
},
|
||||
role: role.Worker,
|
||||
wantConfig: kubeadm.JoinConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
@ -393,15 +314,13 @@ func TestJoinCluster(t *testing.T) {
|
||||
"kubeadm join control-plane node works with metadata": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SupportedResp: true,
|
||||
SelfResp: metadata.InstanceMetadata{
|
||||
ProviderID: "provider-id",
|
||||
Name: "metadata-name",
|
||||
VPCIP: "192.0.2.1",
|
||||
},
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
role: role.ControlPlane,
|
||||
role: role.ControlPlane,
|
||||
wantConfig: kubeadm.JoinConfiguration{
|
||||
Discovery: kubeadm.Discovery{
|
||||
BootstrapToken: joinCommand,
|
||||
@ -422,19 +341,16 @@ func TestJoinCluster(t *testing.T) {
|
||||
"kubeadm join worker fails when retrieving self metadata": {
|
||||
clusterUtil: stubClusterUtil{},
|
||||
providerMetadata: &stubProviderMetadata{
|
||||
SupportedResp: true,
|
||||
SelfErr: someErr,
|
||||
SelfErr: someErr,
|
||||
},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
role: role.Worker,
|
||||
wantErr: true,
|
||||
role: role.Worker,
|
||||
wantErr: true,
|
||||
},
|
||||
"kubeadm join worker fails when applying the join config": {
|
||||
clusterUtil: stubClusterUtil{joinClusterErr: someErr},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
CloudControllerManager: &stubCloudControllerManager{},
|
||||
role: role.Worker,
|
||||
wantErr: true,
|
||||
clusterUtil: stubClusterUtil{joinClusterErr: someErr},
|
||||
providerMetadata: &stubProviderMetadata{},
|
||||
role: role.Worker,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -444,11 +360,10 @@ func TestJoinCluster(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
kube := KubeWrapper{
|
||||
clusterUtil: &tc.clusterUtil,
|
||||
providerMetadata: tc.providerMetadata,
|
||||
cloudControllerManager: tc.CloudControllerManager,
|
||||
configProvider: &stubConfigProvider{},
|
||||
getIPAddr: func() (string, error) { return privateIP, nil },
|
||||
clusterUtil: &tc.clusterUtil,
|
||||
providerMetadata: tc.providerMetadata,
|
||||
configProvider: &stubConfigProvider{},
|
||||
getIPAddr: func() (string, error) { return privateIP, nil },
|
||||
}
|
||||
|
||||
err := kube.JoinCluster(context.Background(), joinCommand, tc.role, string(k8sVersion), logger.NewTest(t))
|
||||
|
@ -20,14 +20,14 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/ssh"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
|
||||
grpcRetry "github.com/edgelesssys/constellation/v2/internal/grpc/retry"
|
||||
"github.com/edgelesssys/constellation/v2/internal/license"
|
||||
|
@ -22,10 +22,10 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
|
||||
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
|
||||
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
|
||||
|
@ -20,11 +20,16 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/fallback"
|
||||
"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/server"
|
||||
awscloud "github.com/edgelesssys/constellation/v2/internal/cloud/aws"
|
||||
azurecloud "github.com/edgelesssys/constellation/v2/internal/cloud/azure"
|
||||
platform "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
gcpcloud "github.com/edgelesssys/constellation/v2/internal/cloud/gcp"
|
||||
qemucloud "github.com/edgelesssys/constellation/v2/internal/cloud/qemu"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/ssh"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/user"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/spf13/afero"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const debugBanner = `
|
||||
@ -56,34 +61,39 @@ func main() {
|
||||
csp := os.Getenv("CONSTEL_CSP")
|
||||
switch platform.FromString(csp) {
|
||||
case platform.AWS:
|
||||
awsFetcher, err := cloudprovider.NewAWS(ctx)
|
||||
meta, err := awscloud.New(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
log.With(zap.Error(err)).Fatalf("Failed to initialize AWS metadata")
|
||||
}
|
||||
fetcher = awsFetcher
|
||||
fetcher = cloudprovider.New(meta)
|
||||
|
||||
case platform.Azure:
|
||||
azureFetcher, err := cloudprovider.NewAzure(ctx)
|
||||
meta, err := azurecloud.NewMetadata(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
log.With(zap.Error(err)).Fatalf("Failed to initialize Azure metadata")
|
||||
}
|
||||
fetcher = azureFetcher
|
||||
fetcher = cloudprovider.New(meta)
|
||||
|
||||
case platform.GCP:
|
||||
gcpFetcher, err := cloudprovider.NewGCP(ctx)
|
||||
meta, err := gcpcloud.New(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
log.With(zap.Error(err)).Fatalf("Failed to initialize GCP metadata")
|
||||
}
|
||||
fetcher = gcpFetcher
|
||||
log.Infof("Added load balancer IP to local routing table")
|
||||
defer meta.Close()
|
||||
fetcher = cloudprovider.New(meta)
|
||||
|
||||
case platform.QEMU:
|
||||
fetcher = cloudprovider.NewQEMU()
|
||||
fetcher = cloudprovider.New(&qemucloud.Metadata{})
|
||||
|
||||
default:
|
||||
log.Errorf("Unknown / unimplemented cloud provider CONSTEL_CSP=%v. Using fallback", csp)
|
||||
fetcher = fallback.Fetcher{}
|
||||
}
|
||||
|
||||
sched := metadata.NewScheduler(log.Named("scheduler"), fetcher, ssh, download)
|
||||
serv := server.New(log.Named("server"), ssh, serviceManager, streamer)
|
||||
if err := deploy.DefaultServiceUnit(ctx, serviceManager); err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
log.With(zap.Error(err)).Fatalf("Failed to create default service unit")
|
||||
}
|
||||
|
||||
writeDebugBanner(log)
|
||||
@ -101,11 +111,11 @@ func main() {
|
||||
func writeDebugBanner(log *logger.Logger) {
|
||||
tty, err := os.OpenFile("/dev/ttyS0", os.O_WRONLY, os.ModeAppend)
|
||||
if err != nil {
|
||||
log.Infof("Unable to open /dev/ttyS0 for printing banner: %v", err)
|
||||
log.With(zap.Error(err)).Errorf("Unable to open /dev/ttyS0 for printing banner")
|
||||
return
|
||||
}
|
||||
defer tty.Close()
|
||||
if _, err := fmt.Fprint(tty, debugBanner); err != nil {
|
||||
log.Infof("Unable to print to /dev/ttyS0: %v", err)
|
||||
log.With(zap.Error(err)).Errorf("Unable to print to /dev/ttyS0")
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
awscloud "github.com/edgelesssys/constellation/v2/internal/cloud/aws"
|
||||
azurecloud "github.com/edgelesssys/constellation/v2/internal/cloud/azure"
|
||||
gcpcloud "github.com/edgelesssys/constellation/v2/internal/cloud/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
qemucloud "github.com/edgelesssys/constellation/v2/internal/cloud/qemu"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/ssh"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
)
|
||||
@ -34,46 +30,9 @@ type Fetcher struct {
|
||||
metaAPI providerMetadata
|
||||
}
|
||||
|
||||
// NewGCP creates a new GCP fetcher.
|
||||
func NewGCP(ctx context.Context) (*Fetcher, error) {
|
||||
gcpClient, err := gcpcloud.NewClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metaAPI := gcpcloud.New(gcpClient)
|
||||
|
||||
func New(cloud providerMetadata) *Fetcher {
|
||||
return &Fetcher{
|
||||
metaAPI: metaAPI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewAzure creates a new Azure fetcher.
|
||||
func NewAWS(ctx context.Context) (*Fetcher, error) {
|
||||
metaAPI, err := awscloud.New(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Fetcher{
|
||||
metaAPI: metaAPI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewAzure creates a new Azure fetcher.
|
||||
func NewAzure(ctx context.Context) (*Fetcher, error) {
|
||||
metaAPI, err := azurecloud.NewMetadata(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Fetcher{
|
||||
metaAPI: metaAPI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewQEMU() *Fetcher {
|
||||
return &Fetcher{
|
||||
metaAPI: &qemucloud.Metadata{},
|
||||
metaAPI: cloud,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,6 @@ type stubMetadata struct {
|
||||
getInstanceErr error
|
||||
getLBEndpointRes string
|
||||
getLBEndpointErr error
|
||||
supportedRes bool
|
||||
}
|
||||
|
||||
func (m *stubMetadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
@ -244,7 +243,3 @@ func (m *stubMetadata) GetInstance(ctx context.Context, providerID string) (meta
|
||||
func (m *stubMetadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
return m.getLBEndpointRes, m.getLBEndpointErr
|
||||
}
|
||||
|
||||
func (m *stubMetadata) Supported() bool {
|
||||
return m.supportedRes
|
||||
}
|
||||
|
@ -101,11 +101,12 @@ func main() {
|
||||
log.With(zap.Error(err)).Fatalf("Unable to resolve GCP state disk path")
|
||||
}
|
||||
issuer = gcp.NewIssuer()
|
||||
gcpClient, err := gcpcloud.NewClient(context.Background())
|
||||
gcpMeta, err := gcpcloud.New(context.Background())
|
||||
if err != nil {
|
||||
log.With(zap.Error).Fatalf("Failed to create GCP client")
|
||||
}
|
||||
metadataAPI = gcpcloud.New(gcpClient)
|
||||
defer gcpMeta.Close()
|
||||
metadataAPI = gcpMeta
|
||||
|
||||
case cloudprovider.QEMU:
|
||||
diskPath = qemuStateDiskPath
|
||||
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azureshared
|
||||
|
||||
/*
|
||||
Package azureshared contains code that is related to Microsoft Azure
|
||||
and is used by multiple microservices.
|
||||
|
||||
This package is intended to have a minimal size and surface. If you
|
||||
have Azure related code that is not shared by multiple microservices,
|
||||
please keep the code in the microservice's internal package.
|
||||
*/
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// TODO: Implement for AWS.
|
||||
|
||||
// CloudControllerManager holds the AWS cloud-controller-manager configuration.
|
||||
type CloudControllerManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
|
||||
func (c CloudControllerManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-controller-manager executable within the container image.
|
||||
func (c CloudControllerManager) Path() string {
|
||||
return "/aws-cloud-controller-manager"
|
||||
}
|
||||
|
||||
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
|
||||
func (c CloudControllerManager) Name() string {
|
||||
return "aws"
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
|
||||
func (c CloudControllerManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
|
||||
func (c CloudControllerManager) ConfigMaps() (kubernetes.ConfigMaps, error) {
|
||||
return kubernetes.ConfigMaps{}, nil
|
||||
}
|
||||
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
|
||||
func (c CloudControllerManager) Secrets(ctx context.Context, providerID, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
return kubernetes.Secrets{}, nil
|
||||
}
|
||||
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
|
||||
func (c CloudControllerManager) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{}
|
||||
}
|
||||
|
||||
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
|
||||
func (c CloudControllerManager) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{}
|
||||
}
|
||||
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
|
||||
func (c CloudControllerManager) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{}
|
||||
}
|
||||
|
||||
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
|
||||
// Allows for cloud-provider specific hooks.
|
||||
func (c CloudControllerManager) PrepareInstance(instance metadata.InstanceMetadata, vpnIP string) error {
|
||||
// no specific hook required.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
|
||||
func (c CloudControllerManager) Supported() bool {
|
||||
return false
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package aws
|
||||
|
||||
import "github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
|
||||
// TODO: Implement for AWS.
|
||||
|
||||
// CloudNodeManager holds the AWS cloud-node-manager configuration.
|
||||
type CloudNodeManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
|
||||
func (c *CloudNodeManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-node-manager executable within the container image.
|
||||
func (c *CloudNodeManager) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
|
||||
func (c *CloudNodeManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
|
||||
func (c *CloudNodeManager) Supported() bool {
|
||||
return false
|
||||
}
|
@ -70,11 +70,6 @@ func New(ctx context.Context) (*Metadata, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Supported is used to determine if metadata API is implemented for this cloud provider.
|
||||
func (m *Metadata) Supported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// List retrieves all instances belonging to the current Constellation.
|
||||
func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
uid, err := readInstanceTag(ctx, m.imds, cloud.TagUID)
|
||||
@ -142,11 +137,6 @@ func (m *Metadata) UID(ctx context.Context) (string, error) {
|
||||
return readInstanceTag(ctx, m.imds, cloud.TagUID)
|
||||
}
|
||||
|
||||
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
|
||||
func (m *Metadata) SupportsLoadBalancer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
|
||||
func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
uid, err := readInstanceTag(ctx, m.imds, cloud.TagUID)
|
||||
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ccmMetadata interface {
|
||||
GetNetworkSecurityGroupName(ctx context.Context) (string, error)
|
||||
GetLoadBalancerName(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// CloudControllerManager holds the Azure cloud-controller-manager configuration.
|
||||
type CloudControllerManager struct {
|
||||
metadata ccmMetadata
|
||||
}
|
||||
|
||||
func NewCloudControllerManager(metadata ccmMetadata) *CloudControllerManager {
|
||||
return &CloudControllerManager{
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
|
||||
func (c *CloudControllerManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return versions.VersionConfigs[k8sVersion].CloudControllerManagerImageAzure, nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-controller-manager executable within the container image.
|
||||
func (c *CloudControllerManager) Path() string {
|
||||
return "cloud-controller-manager"
|
||||
}
|
||||
|
||||
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
|
||||
func (c *CloudControllerManager) Name() string {
|
||||
return "azure"
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
|
||||
func (c *CloudControllerManager) ExtraArgs() []string {
|
||||
return []string{
|
||||
"--controllers=*,-cloud-node",
|
||||
"--cloud-config=/etc/azure/azure.json",
|
||||
"--allocate-node-cidrs=false",
|
||||
"--configure-cloud-routes=true",
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
|
||||
func (c *CloudControllerManager) ConfigMaps() (kubernetes.ConfigMaps, error) {
|
||||
return kubernetes.ConfigMaps{}, nil
|
||||
}
|
||||
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
|
||||
func (c *CloudControllerManager) Secrets(ctx context.Context, providerID string, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
// Azure CCM expects cloud provider config to contain cluster configuration and service principal client secrets
|
||||
// reference: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs/
|
||||
|
||||
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
creds, err := azureshared.ApplicationCredentialsFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
|
||||
vmType := "standard"
|
||||
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(providerID); err == nil {
|
||||
vmType = "vmss"
|
||||
}
|
||||
|
||||
securityGroupName, err := c.metadata.GetNetworkSecurityGroupName(ctx)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
|
||||
loadBalancerName, err := c.metadata.GetLoadBalancerName(ctx)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
|
||||
config := cloudConfig{
|
||||
Cloud: "AzurePublicCloud",
|
||||
TenantID: creds.TenantID,
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
LoadBalancerSku: "standard",
|
||||
SecurityGroupName: securityGroupName,
|
||||
LoadBalancerName: loadBalancerName,
|
||||
UseInstanceMetadata: true,
|
||||
VMType: vmType,
|
||||
Location: creds.Location,
|
||||
AADClientID: creds.AppClientID,
|
||||
AADClientSecret: creds.ClientSecretValue,
|
||||
}
|
||||
|
||||
rawConfig, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
|
||||
return kubernetes.Secrets{
|
||||
&k8s.Secret{
|
||||
TypeMeta: meta.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: meta.ObjectMeta{
|
||||
Name: "azureconfig",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"azure.json": rawConfig,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
|
||||
func (c *CloudControllerManager) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{
|
||||
{
|
||||
Name: "azureconfig",
|
||||
VolumeSource: k8s.VolumeSource{
|
||||
Secret: &k8s.SecretVolumeSource{
|
||||
SecretName: "azureconfig",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
|
||||
func (c *CloudControllerManager) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{
|
||||
{
|
||||
Name: "azureconfig",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/azure",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
|
||||
func (c *CloudControllerManager) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
|
||||
func (c *CloudControllerManager) Supported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type cloudConfig struct {
|
||||
Cloud string `json:"cloud,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
ResourceGroup string `json:"resourceGroup,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
SubnetName string `json:"subnetName,omitempty"`
|
||||
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
||||
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
||||
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
||||
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
||||
VNetName string `json:"vnetName,omitempty"`
|
||||
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
||||
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
||||
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
||||
VMType string `json:"vmType,omitempty"`
|
||||
AADClientID string `json:"aadClientId,omitempty"`
|
||||
AADClientSecret string `json:"aadClientSecret,omitempty"`
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
someErr := errors.New("some error")
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
metadata ccmMetadata
|
||||
cloudServiceAccountURI string
|
||||
wantSecrets kubernetes.Secrets
|
||||
wantErr bool
|
||||
}{
|
||||
"Secrets works for scale sets": {
|
||||
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
|
||||
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
|
||||
metadata: &ccmMetadataStub{loadBalancerName: "load-balancer-name", networkSecurityGroupName: "network-security-group-name"},
|
||||
wantSecrets: kubernetes.Secrets{
|
||||
&k8s.Secret{
|
||||
TypeMeta: meta.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: meta.ObjectMeta{
|
||||
Name: "azureconfig",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"azure.json": []byte(`{"cloud":"AzurePublicCloud","tenantId":"tenant-id","subscriptionId":"subscription-id","resourceGroup":"resource-group","location":"location","securityGroupName":"network-security-group-name","loadBalancerName":"load-balancer-name","loadBalancerSku":"standard","useInstanceMetadata":true,"vmType":"vmss","aadClientId":"client-id","aadClientSecret":"client-secret"}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cannot get load balancer Name": {
|
||||
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
|
||||
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
|
||||
metadata: &ccmMetadataStub{getLoadBalancerNameErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"cannot get network security group name": {
|
||||
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
|
||||
cloudServiceAccountURI: "serviceaccount://azure?tenant_id=tenant-id&client_id=client-id&client_secret=client-secret&location=location",
|
||||
metadata: &ccmMetadataStub{getNetworkSecurityGroupNameErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid providerID fails": {
|
||||
providerID: "invalid",
|
||||
metadata: &ccmMetadataStub{},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid cloudServiceAccountURI fails": {
|
||||
providerID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/instance-name",
|
||||
metadata: &ccmMetadataStub{},
|
||||
cloudServiceAccountURI: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := NewCloudControllerManager(tc.metadata)
|
||||
secrets, err := cloud.Secrets(context.Background(), tc.providerID, tc.cloudServiceAccountURI)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantSecrets, secrets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrivialCCMFunctions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cloud := CloudControllerManager{}
|
||||
|
||||
assert.NotEmpty(cloud.Image(versions.Default))
|
||||
assert.NotEmpty(cloud.Path())
|
||||
assert.NotEmpty(cloud.Name())
|
||||
assert.NotEmpty(cloud.ExtraArgs())
|
||||
assert.Empty(cloud.ConfigMaps())
|
||||
assert.NotEmpty(cloud.Volumes())
|
||||
assert.NotEmpty(cloud.VolumeMounts())
|
||||
assert.Empty(cloud.Env())
|
||||
assert.True(cloud.Supported())
|
||||
}
|
||||
|
||||
type ccmMetadataStub struct {
|
||||
networkSecurityGroupName string
|
||||
loadBalancerName string
|
||||
|
||||
getNetworkSecurityGroupNameErr error
|
||||
getLoadBalancerNameErr error
|
||||
}
|
||||
|
||||
func (c *ccmMetadataStub) GetNetworkSecurityGroupName(ctx context.Context) (string, error) {
|
||||
return c.networkSecurityGroupName, c.getNetworkSecurityGroupNameErr
|
||||
}
|
||||
|
||||
func (c *ccmMetadataStub) GetLoadBalancerName(ctx context.Context) (string, error) {
|
||||
return c.loadBalancerName, c.getLoadBalancerNameErr
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
)
|
||||
|
||||
// CloudNodeManager holds the Azure cloud-node-manager configuration.
|
||||
// reference: https://raw.githubusercontent.com/kubernetes-sigs/cloud-provider-azure/master/examples/out-of-tree/cloud-node-manager.yaml .
|
||||
type CloudNodeManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
|
||||
func (c *CloudNodeManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return versions.VersionConfigs[k8sVersion].CloudNodeManagerImageAzure, nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-node-manager executable within the container image.
|
||||
func (c *CloudNodeManager) Path() string {
|
||||
return "cloud-node-manager"
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
|
||||
func (c *CloudNodeManager) ExtraArgs() []string {
|
||||
return []string{
|
||||
"--wait-routes=true",
|
||||
}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
|
||||
func (c *CloudNodeManager) Supported() bool {
|
||||
return true
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTrivialCNMFunctions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cloud := CloudNodeManager{}
|
||||
|
||||
assert.NotEmpty(cloud.Image(versions.Default))
|
||||
assert.NotEmpty(cloud.Path())
|
||||
assert.NotEmpty(cloud.ExtraArgs())
|
||||
assert.True(cloud.Supported())
|
||||
}
|
@ -8,6 +8,7 @@ package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@ -18,6 +19,7 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
)
|
||||
|
||||
@ -201,11 +203,6 @@ func (m *Metadata) getLoadBalancer(ctx context.Context) (*armnetwork.LoadBalance
|
||||
return nil, fmt.Errorf("could not get any load balancer")
|
||||
}
|
||||
|
||||
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
|
||||
func (m *Metadata) SupportsLoadBalancer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLoadBalancerName returns the load balancer name of the resource group.
|
||||
func (m *Metadata) GetLoadBalancerName(ctx context.Context) (string, error) {
|
||||
lb, err := m.getLoadBalancer(ctx)
|
||||
@ -265,9 +262,48 @@ func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error)
|
||||
return *resp.Properties.IPAddress, nil
|
||||
}
|
||||
|
||||
// Supported is used to determine if metadata API is implemented for this cloud provider.
|
||||
func (m *Metadata) Supported() bool {
|
||||
return true
|
||||
// GetCCMConfig returns the configuration needed for the CCM on Azure.
|
||||
func (m *Metadata) GetCCMConfig(ctx context.Context, providerID string, cloudServiceAccountURI string) ([]byte, error) {
|
||||
subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(providerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds, err := azureshared.ApplicationCredentialsFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmType := "standard"
|
||||
if _, _, _, _, err := azureshared.ScaleSetInformationFromProviderID(providerID); err == nil {
|
||||
vmType = "vmss"
|
||||
}
|
||||
|
||||
securityGroupName, err := m.GetNetworkSecurityGroupName(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loadBalancerName, err := m.GetLoadBalancerName(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := cloudConfig{
|
||||
Cloud: "AzurePublicCloud",
|
||||
TenantID: creds.TenantID,
|
||||
SubscriptionID: subscriptionID,
|
||||
ResourceGroup: resourceGroup,
|
||||
LoadBalancerSku: "standard",
|
||||
SecurityGroupName: securityGroupName,
|
||||
LoadBalancerName: loadBalancerName,
|
||||
UseInstanceMetadata: true,
|
||||
VMType: vmType,
|
||||
Location: creds.Location,
|
||||
AADClientID: creds.AppClientID,
|
||||
AADClientSecret: creds.ClientSecretValue,
|
||||
}
|
||||
|
||||
return json.Marshal(config)
|
||||
}
|
||||
|
||||
// providerID retrieves the current instances providerID.
|
||||
@ -343,3 +379,23 @@ func extractSSHKeys(sshConfig armcomputev2.SSHConfiguration) map[string][]string
|
||||
}
|
||||
return sshKeys
|
||||
}
|
||||
|
||||
type cloudConfig struct {
|
||||
Cloud string `json:"cloud,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
SubscriptionID string `json:"subscriptionId,omitempty"`
|
||||
ResourceGroup string `json:"resourceGroup,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
SubnetName string `json:"subnetName,omitempty"`
|
||||
SecurityGroupName string `json:"securityGroupName,omitempty"`
|
||||
SecurityGroupResourceGroup string `json:"securityGroupResourceGroup,omitempty"`
|
||||
LoadBalancerName string `json:"loadBalancerName,omitempty"`
|
||||
LoadBalancerSku string `json:"loadBalancerSku,omitempty"`
|
||||
VNetName string `json:"vnetName,omitempty"`
|
||||
VNetResourceGroup string `json:"vnetResourceGroup,omitempty"`
|
||||
CloudProviderBackoff bool `json:"cloudProviderBackoff,omitempty"`
|
||||
UseInstanceMetadata bool `json:"useInstanceMetadata,omitempty"`
|
||||
VMType string `json:"vmType,omitempty"`
|
||||
AADClientID string `json:"aadClientId,omitempty"`
|
||||
AADClientSecret string `json:"aadClientSecret,omitempty"`
|
||||
}
|
||||
|
@ -463,12 +463,6 @@ func TestGetLoadBalancerEndpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataSupported(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
metadata := Metadata{}
|
||||
assert.True(metadata.Supported())
|
||||
}
|
||||
|
||||
func TestProviderID(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
imdsAPI imdsAPI
|
||||
|
@ -15,8 +15,8 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
armcomputev2 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
|
||||
"github.com/edgelesssys/constellation/v2/internal/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
)
|
||||
@ -35,12 +35,8 @@ func (m *Metadata) getScaleSetVM(ctx context.Context, providerID string) (metada
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
publicIPAddress, err := m.getScaleSetVMPublicIPAddress(ctx, resourceGroup, scaleSet, instanceID, networkInterfaces)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
|
||||
return convertScaleSetVMToCoreInstance(vmResp.VirtualMachineScaleSetVM, networkInterfaces, publicIPAddress)
|
||||
return convertScaleSetVMToCoreInstance(vmResp.VirtualMachineScaleSetVM, networkInterfaces)
|
||||
}
|
||||
|
||||
// listScaleSetVMs lists all scale set VMs in the current resource group.
|
||||
@ -70,7 +66,7 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance, err := convertScaleSetVMToCoreInstance(*vm, interfaces, "")
|
||||
instance, err := convertScaleSetVMToCoreInstance(*vm, interfaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -84,7 +80,6 @@ func (m *Metadata) listScaleSetVMs(ctx context.Context, resourceGroup string) ([
|
||||
|
||||
// convertScaleSetVMToCoreInstance converts an azure scale set virtual machine with interface configurations into a core.Instance.
|
||||
func convertScaleSetVMToCoreInstance(vm armcomputev2.VirtualMachineScaleSetVM, networkInterfaces []armnetwork.Interface,
|
||||
publicIPAddress string,
|
||||
) (metadata.InstanceMetadata, error) {
|
||||
if vm.ID == nil {
|
||||
return metadata.InstanceMetadata{}, errors.New("retrieving instance from armcompute API client returned no instance ID")
|
||||
@ -108,7 +103,6 @@ func convertScaleSetVMToCoreInstance(vm armcomputev2.VirtualMachineScaleSetVM, n
|
||||
ProviderID: "azure://" + *vm.ID,
|
||||
Role: extractScaleSetVMRole(vm.Tags),
|
||||
VPCIP: extractVPCIP(networkInterfaces),
|
||||
PublicIP: publicIPAddress,
|
||||
SSHKeys: sshKeys,
|
||||
}, nil
|
||||
}
|
||||
|
@ -155,7 +155,6 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
inVM armcomputev2.VirtualMachineScaleSetVM
|
||||
inInterface []armnetwork.Interface
|
||||
inPublicIP string
|
||||
wantErr bool
|
||||
wantInstance metadata.InstanceMetadata
|
||||
}{
|
||||
@ -186,12 +185,10 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
inPublicIP: "192.0.2.100",
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
Name: "scale-set-name-instance-id",
|
||||
ProviderID: "azure:///subscriptions/subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/scale-set-name/virtualMachines/instance-id",
|
||||
VPCIP: "192.0.2.0",
|
||||
PublicIP: "192.0.2.100",
|
||||
SSHKeys: map[string][]string{},
|
||||
},
|
||||
},
|
||||
@ -206,7 +203,7 @@ func TestConvertScaleSetVMToCoreInstance(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
instance, err := convertScaleSetVMToCoreInstance(tc.inVM, tc.inInterface, tc.inPublicIP)
|
||||
instance, err := convertScaleSetVMToCoreInstance(tc.inVM, tc.inInterface)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
16
internal/cloud/azureshared/doc.go
Normal file
16
internal/cloud/azureshared/doc.go
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package gcpshared contains code to parse and define data types
|
||||
relevant for Microsoft Azure.
|
||||
|
||||
This package is intended to have a minimal size and surface. If you
|
||||
have Azure related code that is not shared by multiple applications,
|
||||
or if the code interacts with the GCP API, please keep the code in
|
||||
the application's internal package or add it to the Azure cloud package.
|
||||
*/
|
||||
package azureshared
|
@ -1,183 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// CloudControllerManager holds the gcp cloud-controller-manager configuration.
|
||||
type CloudControllerManager struct {
|
||||
uid string
|
||||
projectID string
|
||||
}
|
||||
|
||||
// NewCloudControllerManager returns an initialized cloud controller manager configuration struct for GCP.
|
||||
func NewCloudControllerManager(ctx context.Context, metadata *Metadata) (*CloudControllerManager, error) {
|
||||
uid, err := metadata.api.UID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting uid from metadata: %w", err)
|
||||
}
|
||||
projectID, err := metadata.api.RetrieveProjectID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting project id from metadata: %w", err)
|
||||
}
|
||||
|
||||
return &CloudControllerManager{
|
||||
uid: uid,
|
||||
projectID: projectID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
|
||||
func (c *CloudControllerManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return versions.VersionConfigs[k8sVersion].CloudControllerManagerImageGCP, nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-controller-manager executable within the container image.
|
||||
func (c *CloudControllerManager) Path() string {
|
||||
return "/cloud-controller-manager"
|
||||
}
|
||||
|
||||
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
|
||||
func (c *CloudControllerManager) Name() string {
|
||||
return "gce"
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
|
||||
func (c *CloudControllerManager) ExtraArgs() []string {
|
||||
return []string{
|
||||
"--use-service-account-credentials",
|
||||
"--controllers=cloud-node,cloud-node-lifecycle,nodeipam,service,route",
|
||||
"--cloud-config=/etc/gce/gce.conf",
|
||||
"--cidr-allocator-type=CloudAllocator",
|
||||
"--allocate-node-cidrs=true",
|
||||
"--configure-cloud-routes=false",
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
|
||||
func (c *CloudControllerManager) ConfigMaps() (kubernetes.ConfigMaps, error) {
|
||||
// GCP CCM expects cloud config to contain the GCP project-id and other configuration.
|
||||
// reference: https://github.com/kubernetes/cloud-provider-gcp/blob/master/cluster/gce/gci/configure-helper.sh#L791-L892
|
||||
var config strings.Builder
|
||||
config.WriteString("[global]\n")
|
||||
config.WriteString(fmt.Sprintf("project-id = %s\n", c.projectID))
|
||||
config.WriteString("use-metadata-server = true\n")
|
||||
config.WriteString(fmt.Sprintf("node-tags = constellation-%s\n", c.uid))
|
||||
|
||||
return kubernetes.ConfigMaps{
|
||||
&k8s.ConfigMap{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "gceconf",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"gce.conf": config.String(),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
|
||||
func (c *CloudControllerManager) Secrets(_ context.Context, _ string, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
rawKey, err := json.Marshal(serviceAccountKey)
|
||||
if err != nil {
|
||||
return kubernetes.Secrets{}, err
|
||||
}
|
||||
|
||||
return kubernetes.Secrets{
|
||||
&k8s.Secret{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "gcekey",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key.json": rawKey,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
|
||||
func (c *CloudControllerManager) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{
|
||||
{
|
||||
Name: "gceconf",
|
||||
VolumeSource: k8s.VolumeSource{
|
||||
ConfigMap: &k8s.ConfigMapVolumeSource{
|
||||
LocalObjectReference: k8s.LocalObjectReference{
|
||||
Name: "gceconf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gcekey",
|
||||
VolumeSource: k8s.VolumeSource{
|
||||
Secret: &k8s.SecretVolumeSource{
|
||||
SecretName: "gcekey",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeMounts returns a list of volume mounts to deploy together with the k8s cloud-controller-manager.
|
||||
func (c *CloudControllerManager) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{
|
||||
{
|
||||
Name: "gceconf",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/gce",
|
||||
},
|
||||
{
|
||||
Name: "gcekey",
|
||||
ReadOnly: true,
|
||||
MountPath: "/var/secrets/google",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
|
||||
func (c *CloudControllerManager) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{
|
||||
{
|
||||
Name: "GOOGLE_APPLICATION_CREDENTIALS",
|
||||
Value: "/var/secrets/google/key.json",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
|
||||
func (c *CloudControllerManager) Supported() bool {
|
||||
return true
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestConfigMaps(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
instance metadata.InstanceMetadata
|
||||
wantConfigMaps kubernetes.ConfigMaps
|
||||
wantErr bool
|
||||
}{
|
||||
"ConfigMaps works": {
|
||||
wantConfigMaps: kubernetes.ConfigMaps{
|
||||
&k8s.ConfigMap{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "gceconf",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"gce.conf": `[global]
|
||||
project-id = project-id
|
||||
use-metadata-server = true
|
||||
node-tags = constellation-UID
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := CloudControllerManager{
|
||||
projectID: "project-id",
|
||||
uid: "UID",
|
||||
}
|
||||
configMaps, err := cloud.ConfigMaps()
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantConfigMaps, configMaps)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
serviceAccountKey := gcpshared.ServiceAccountKey{
|
||||
Type: "type",
|
||||
ProjectID: "project-id",
|
||||
PrivateKeyID: "private-key-id",
|
||||
PrivateKey: "private-key",
|
||||
ClientEmail: "client-email",
|
||||
ClientID: "client-id",
|
||||
AuthURI: "auth-uri",
|
||||
TokenURI: "token-uri",
|
||||
AuthProviderX509CertURL: "auth-provider-x509-cert-url",
|
||||
ClientX509CertURL: "client-x509-cert-url",
|
||||
}
|
||||
rawKey, err := json.Marshal(serviceAccountKey)
|
||||
require.NoError(t, err)
|
||||
testCases := map[string]struct {
|
||||
instance metadata.InstanceMetadata
|
||||
cloudServiceAccountURI string
|
||||
wantSecrets kubernetes.Secrets
|
||||
wantErr bool
|
||||
}{
|
||||
"Secrets works": {
|
||||
cloudServiceAccountURI: "serviceaccount://gcp?type=type&project_id=project-id&private_key_id=private-key-id&private_key=private-key&client_email=client-email&client_id=client-id&auth_uri=auth-uri&token_uri=token-uri&auth_provider_x509_cert_url=auth-provider-x509-cert-url&client_x509_cert_url=client-x509-cert-url",
|
||||
wantSecrets: kubernetes.Secrets{
|
||||
&k8s.Secret{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "gcekey",
|
||||
Namespace: "kube-system",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key.json": rawKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid serviceAccountKey fails": {
|
||||
cloudServiceAccountURI: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := CloudControllerManager{}
|
||||
secrets, err := cloud.Secrets(context.Background(), tc.instance.ProviderID, tc.cloudServiceAccountURI)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantSecrets, secrets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrivialCCMFunctions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cloud := CloudControllerManager{}
|
||||
|
||||
assert.NotEmpty(cloud.Image(versions.Default))
|
||||
assert.NotEmpty(cloud.Path())
|
||||
assert.NotEmpty(cloud.Name())
|
||||
assert.NotEmpty(cloud.ExtraArgs())
|
||||
assert.NotEmpty(cloud.Volumes())
|
||||
assert.NotEmpty(cloud.VolumeMounts())
|
||||
assert.NotEmpty(cloud.Env())
|
||||
assert.True(cloud.Supported())
|
||||
}
|
@ -1,439 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"google.golang.org/api/iterator"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
gcpSSHMetadataKey = "ssh-keys"
|
||||
)
|
||||
|
||||
var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])")
|
||||
|
||||
// Client implements the gcp.API interface.
|
||||
type Client struct {
|
||||
instanceAPI
|
||||
subnetworkAPI
|
||||
metadataAPI
|
||||
forwardingRulesAPI
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(ctx context.Context) (*Client, error) {
|
||||
insAPI, err := compute.NewInstancesRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subnetAPI, err := compute.NewSubnetworksRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forwardingRulesAPI, err := compute.NewGlobalForwardingRulesRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
instanceAPI: &instanceClient{insAPI},
|
||||
subnetworkAPI: &subnetworkClient{subnetAPI},
|
||||
forwardingRulesAPI: &forwardingRulesClient{forwardingRulesAPI},
|
||||
metadataAPI: &metadataClient{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RetrieveInstances returns list of instances including their ips and metadata.
|
||||
func (c *Client) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) {
|
||||
uid, err := c.UID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &computepb.ListInstancesRequest{
|
||||
Filter: proto.String(fmt.Sprintf("labels.%s:%s", cloud.TagUID, uid)),
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
}
|
||||
instanceIterator := c.instanceAPI.List(ctx, req)
|
||||
|
||||
instances := []metadata.InstanceMetadata{}
|
||||
for {
|
||||
resp, err := instanceIterator.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving instance list from compute API client: %w", err)
|
||||
}
|
||||
instance, err := convertToCoreInstance(resp, project, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
// RetrieveInstance returns a an instance including ips and metadata.
|
||||
func (c *Client) RetrieveInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error) {
|
||||
instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
|
||||
return convertToCoreInstance(instance, project, zone)
|
||||
}
|
||||
|
||||
// RetrieveProjectID retrieves the GCP projectID containing the current instance.
|
||||
func (c *Client) RetrieveProjectID() (string, error) {
|
||||
value, err := c.metadataAPI.ProjectID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("requesting GCP projectID failed %w", err)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// RetrieveZone retrieves the GCP zone containing the current instance.
|
||||
func (c *Client) RetrieveZone() (string, error) {
|
||||
value, err := c.metadataAPI.Zone()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("requesting GCP zone failed %w", err)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (c *Client) RetrieveInstanceName() (string, error) {
|
||||
value, err := c.metadataAPI.InstanceName()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("requesting GCP instanceName failed %w", err)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (c *Client) RetrieveInstanceMetadata(attr string) (string, error) {
|
||||
value, err := c.metadataAPI.InstanceAttributeValue(attr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("requesting GCP instance metadata: %w", err)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SetInstanceMetadata modifies a key value pair of metadata for the instance specified by project, zone and instanceName.
|
||||
func (c *Client) SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error {
|
||||
instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving instance metadata: %w", err)
|
||||
}
|
||||
if instance == nil || instance.Metadata == nil {
|
||||
return fmt.Errorf("retrieving instance metadata returned invalid results")
|
||||
}
|
||||
|
||||
// convert instance metadata to map to handle duplicate keys correctly
|
||||
metadataMap := extractInstanceMetadata(instance.Metadata, key, false)
|
||||
metadataMap[key] = value
|
||||
// convert instance metadata back to flat list
|
||||
metadata := flattenInstanceMetadata(metadataMap, instance.Metadata.Fingerprint, instance.Metadata.Kind)
|
||||
|
||||
if err := c.updateInstanceMetadata(ctx, project, zone, instanceName, metadata); err != nil {
|
||||
return fmt.Errorf("setting instance metadata %v: %v: %w", key, value, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetInstanceMetadata modifies a key value pair of metadata for the instance specified by project, zone and instanceName.
|
||||
func (c *Client) UnsetInstanceMetadata(ctx context.Context, project, zone, instanceName, key string) error {
|
||||
instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving instance metadata: %w", err)
|
||||
}
|
||||
if instance == nil || instance.Metadata == nil {
|
||||
return fmt.Errorf("retrieving instance metadata returned invalid results")
|
||||
}
|
||||
|
||||
// convert instance metadata to map to handle duplicate keys correctly
|
||||
// and skip the key to be removed
|
||||
metadataMap := extractInstanceMetadata(instance.Metadata, key, true)
|
||||
|
||||
// convert instance metadata back to flat list
|
||||
metadata := flattenInstanceMetadata(metadataMap, instance.Metadata.Fingerprint, instance.Metadata.Kind)
|
||||
|
||||
if err := c.updateInstanceMetadata(ctx, project, zone, instanceName, metadata); err != nil {
|
||||
return fmt.Errorf("unsetting instance metadata key %v: %w", key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RetrieveSubnetworkAliasCIDR returns the alias CIDR of the subnetwork specified by project, zone and subnetworkName.
|
||||
func (c *Client) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error) {
|
||||
instance, err := c.getComputeInstance(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if instance == nil || instance.NetworkInterfaces == nil || len(instance.NetworkInterfaces) == 0 || instance.NetworkInterfaces[0].Subnetwork == nil {
|
||||
return "", fmt.Errorf("retrieving instance network interfaces failed")
|
||||
}
|
||||
subnetworkURL := *instance.NetworkInterfaces[0].Subnetwork
|
||||
subnetworkURLFragments := strings.Split(subnetworkURL, "/")
|
||||
subnetworkName := subnetworkURLFragments[len(subnetworkURLFragments)-1]
|
||||
|
||||
// convert:
|
||||
// zone --> region
|
||||
// europe-west3-b --> europe-west3
|
||||
region := zoneFromRegionRegex.FindString(zone)
|
||||
if region == "" {
|
||||
return "", fmt.Errorf("invalid zone %s", zone)
|
||||
}
|
||||
|
||||
req := &computepb.GetSubnetworkRequest{
|
||||
Project: project,
|
||||
Region: region,
|
||||
Subnetwork: subnetworkName,
|
||||
}
|
||||
subnetwork, err := c.subnetworkAPI.Get(ctx, req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving subnetwork alias CIDR failed: %w", err)
|
||||
}
|
||||
if subnetwork == nil || len(subnetwork.SecondaryIpRanges) == 0 || (subnetwork.SecondaryIpRanges[0]).IpCidrRange == nil {
|
||||
return "", fmt.Errorf("retrieving subnetwork alias CIDR returned invalid results")
|
||||
}
|
||||
|
||||
return *(subnetwork.SecondaryIpRanges[0]).IpCidrRange, nil
|
||||
}
|
||||
|
||||
// RetrieveLoadBalancerEndpoint returns the endpoint of the load balancer with the constellation-uid tag.
|
||||
func (c *Client) RetrieveLoadBalancerEndpoint(ctx context.Context, project string) (string, error) {
|
||||
uid, err := c.UID(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req := &computepb.ListGlobalForwardingRulesRequest{
|
||||
Project: project,
|
||||
}
|
||||
iter := c.forwardingRulesAPI.List(ctx, req)
|
||||
for {
|
||||
resp, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving load balancer IP failed: %w", err)
|
||||
}
|
||||
if resp.Labels[cloud.TagUID] == uid && resp.Labels["constellation-use"] == "kubernetes" {
|
||||
if resp.PortRange == nil {
|
||||
return "", errors.New("load balancer with searched UID has no ports")
|
||||
}
|
||||
portRange := strings.Split(*resp.PortRange, "-")
|
||||
return net.JoinHostPort(*resp.IPAddress, portRange[0]), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("retrieving load balancer IP failed: load balancer not found")
|
||||
}
|
||||
|
||||
// Close closes the instanceAPI client.
|
||||
func (c *Client) Close() error {
|
||||
if err := c.subnetworkAPI.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.forwardingRulesAPI.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.instanceAPI.Close()
|
||||
}
|
||||
|
||||
func (c *Client) getComputeInstance(ctx context.Context, project, zone, instanceName string) (*computepb.Instance, error) {
|
||||
instanceGetReq := &computepb.GetInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceName,
|
||||
}
|
||||
instance, err := c.instanceAPI.Get(ctx, instanceGetReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving compute instance: %w", err)
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// updateInstanceMetadata updates all instance metadata key-value pairs.
|
||||
func (c *Client) updateInstanceMetadata(ctx context.Context, project, zone, instanceName string, metadata *computepb.Metadata) error {
|
||||
setMetadataReq := &computepb.SetMetadataInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceName,
|
||||
MetadataResource: metadata,
|
||||
}
|
||||
|
||||
if _, err := c.instanceAPI.SetMetadata(ctx, setMetadataReq); err != nil {
|
||||
return fmt.Errorf("updating instance metadata: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UID retrieves the current instances uid.
|
||||
func (c *Client) UID(ctx context.Context) (string, error) {
|
||||
// API endpoint: http://metadata.google.internal/computeMetadata/v1/instance/attributes/constellation-uid
|
||||
instanceID, err := c.InstanceID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving instance ID: %w", err)
|
||||
}
|
||||
project, err := c.ProjectID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving project ID: %w", err)
|
||||
}
|
||||
zone, err := c.Zone()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving zone: %w", err)
|
||||
}
|
||||
|
||||
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceID,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving instance labels: %w", err)
|
||||
}
|
||||
return instance.Labels[cloud.TagUID], nil
|
||||
}
|
||||
|
||||
// extractVPCIP extracts the primary private IP from a list of interfaces.
|
||||
func extractVPCIP(interfaces []*computepb.NetworkInterface) string {
|
||||
for _, interf := range interfaces {
|
||||
if interf == nil || interf.NetworkIP == nil || interf.Name == nil || *interf.Name != "nic0" {
|
||||
continue
|
||||
}
|
||||
// return private IP from the default interface
|
||||
return *interf.NetworkIP
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractPublicIP extracts a public IP from a list of interfaces.
|
||||
func extractPublicIP(interfaces []*computepb.NetworkInterface) string {
|
||||
for _, interf := range interfaces {
|
||||
if interf == nil || interf.AccessConfigs == nil || interf.Name == nil || *interf.Name != "nic0" {
|
||||
continue
|
||||
}
|
||||
|
||||
// return public IP from the default interface
|
||||
// GCP only supports one type of access config, so returning the first IP should result in a valid public IP
|
||||
for _, accessConfig := range interf.AccessConfigs {
|
||||
if accessConfig == nil || accessConfig.NatIP == nil {
|
||||
continue
|
||||
}
|
||||
return *accessConfig.NatIP
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractAliasIPRanges extracts alias interface IPs from a list of interfaces.
|
||||
func extractAliasIPRanges(interfaces []*computepb.NetworkInterface) []string {
|
||||
ips := []string{}
|
||||
for _, interf := range interfaces {
|
||||
if interf == nil || interf.AliasIpRanges == nil {
|
||||
continue
|
||||
}
|
||||
for _, aliasIP := range interf.AliasIpRanges {
|
||||
if aliasIP == nil || aliasIP.IpCidrRange == nil {
|
||||
continue
|
||||
}
|
||||
ips = append(ips, *aliasIP.IpCidrRange)
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
// extractSSHKeys extracts SSH keys from GCP instance metadata.
|
||||
// reference: https://cloud.google.com/compute/docs/connect/add-ssh-keys .
|
||||
func extractSSHKeys(metadata map[string]string) map[string][]string {
|
||||
sshKeysRaw, ok := metadata[gcpSSHMetadataKey]
|
||||
if !ok {
|
||||
// ignore missing metadata entry
|
||||
return map[string][]string{}
|
||||
}
|
||||
|
||||
sshKeyLines := strings.Split(sshKeysRaw, "\n")
|
||||
keys := map[string][]string{}
|
||||
for _, sshKeyRaw := range sshKeyLines {
|
||||
keyParts := strings.SplitN(sshKeyRaw, ":", 2)
|
||||
if len(keyParts) != 2 {
|
||||
continue
|
||||
}
|
||||
username := keyParts[0]
|
||||
keyParts = strings.SplitN(keyParts[1], " ", 3)
|
||||
if len(keyParts) < 2 {
|
||||
continue
|
||||
}
|
||||
keyValue := fmt.Sprintf("%s %s", keyParts[0], keyParts[1])
|
||||
keys[username] = append(keys[username], keyValue)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// convertToCoreInstance converts a *computepb.Instance to a core.Instance.
|
||||
func convertToCoreInstance(in *computepb.Instance, project string, zone string) (metadata.InstanceMetadata, error) {
|
||||
if in.Name == nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("retrieving instance from compute API client returned invalid instance Name: %v", in.Name)
|
||||
}
|
||||
mdata := extractInstanceMetadata(in.Metadata, "", false)
|
||||
return metadata.InstanceMetadata{
|
||||
Name: *in.Name,
|
||||
ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name),
|
||||
Role: role.FromString(in.Labels[cloud.TagRole]),
|
||||
VPCIP: extractVPCIP(in.NetworkInterfaces),
|
||||
PublicIP: extractPublicIP(in.NetworkInterfaces),
|
||||
AliasIPRanges: extractAliasIPRanges(in.NetworkInterfaces),
|
||||
SSHKeys: extractSSHKeys(mdata),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractInstanceMetadata will extract the list of instance metadata key-value pairs into a map.
|
||||
// If "skipKey" is true, "key" will be skipped.
|
||||
func extractInstanceMetadata(in *computepb.Metadata, key string, skipKey bool) map[string]string {
|
||||
metadataMap := map[string]string{}
|
||||
for _, item := range in.Items {
|
||||
if item == nil || item.Key == nil || item.Value == nil {
|
||||
continue
|
||||
}
|
||||
if skipKey && *item.Key == key {
|
||||
continue
|
||||
}
|
||||
metadataMap[*item.Key] = *item.Value
|
||||
}
|
||||
return metadataMap
|
||||
}
|
||||
|
||||
// flattenInstanceMetadata takes a map of metadata key-value pairs and returns a flat list of computepb.Items inside computepb.Metadata.
|
||||
func flattenInstanceMetadata(metadataMap map[string]string, fingerprint, kind *string) *computepb.Metadata {
|
||||
metadata := &computepb.Metadata{
|
||||
Fingerprint: fingerprint,
|
||||
Kind: kind,
|
||||
Items: make([]*computepb.Items, len(metadataMap)),
|
||||
}
|
||||
i := 0
|
||||
for mapKey, mapValue := range metadataMap {
|
||||
metadata.Items[i] = &computepb.Items{Key: proto.String(mapKey), Value: proto.String(mapValue)}
|
||||
i++
|
||||
}
|
||||
return metadata
|
||||
}
|
File diff suppressed because it is too large
Load Diff
319
internal/cloud/gcp/cloud.go
Normal file
319
internal/cloud/gcp/cloud.go
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
imds "cloud.google.com/go/compute/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"google.golang.org/api/iterator"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// tagUsage is a label key used to indicate the use of the resource.
|
||||
tagUsage = "constellation-use"
|
||||
)
|
||||
|
||||
var zoneFromRegionRegex = regexp.MustCompile("([a-z]*-[a-z]*[0-9])")
|
||||
|
||||
// Cloud provides GCP cloud metadata information and API access.
|
||||
type Cloud struct {
|
||||
forwardingRulesAPI forwardingRulesAPI
|
||||
imds imdsAPI
|
||||
instanceAPI instanceAPI
|
||||
subnetAPI subnetAPI
|
||||
|
||||
closers []func() error
|
||||
}
|
||||
|
||||
// New creates and initializes Cloud.
|
||||
// The Close method should be called when Cloud is no longer needed.
|
||||
func New(ctx context.Context) (cloud *Cloud, err error) {
|
||||
var closers []func() error
|
||||
|
||||
insAPI, err := compute.NewInstancesRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closers = append(closers, insAPI.Close)
|
||||
forwardingRulesAPI, err := compute.NewGlobalForwardingRulesRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closers = append(closers, forwardingRulesAPI.Close)
|
||||
subnetAPI, err := compute.NewSubnetworksRESTClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closers = append(closers, subnetAPI.Close)
|
||||
|
||||
return &Cloud{
|
||||
imds: imds.NewClient(nil),
|
||||
instanceAPI: &instanceClient{insAPI},
|
||||
forwardingRulesAPI: &forwardingRulesClient{forwardingRulesAPI},
|
||||
subnetAPI: subnetAPI,
|
||||
closers: closers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes all connections to the GCP API server.
|
||||
func (c *Cloud) Close() {
|
||||
for _, close := range c.closers {
|
||||
_ = close()
|
||||
}
|
||||
}
|
||||
|
||||
// GetInstance retrieves an instance using its providerID.
|
||||
func (c *Cloud) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
|
||||
project, zone, instanceName, err := gcpshared.SplitProviderID(providerID)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("invalid providerID: %w", err)
|
||||
}
|
||||
|
||||
return c.getInstance(ctx, project, zone, instanceName)
|
||||
}
|
||||
|
||||
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
|
||||
func (c *Cloud) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uid, err := c.uid(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var resp *computepb.ForwardingRule
|
||||
iter := c.forwardingRulesAPI.List(ctx, &computepb.ListGlobalForwardingRulesRequest{
|
||||
Project: project,
|
||||
Filter: proto.String(fmt.Sprintf("(labels.%s:%s) AND (labels.%s:kubernetes)", cloud.TagUID, uid, tagUsage)),
|
||||
})
|
||||
for resp, err = iter.Next(); err == nil; resp, err = iter.Next() {
|
||||
if resp.PortRange == nil {
|
||||
continue
|
||||
}
|
||||
if resp.IPAddress == nil {
|
||||
continue
|
||||
}
|
||||
portRange := strings.Split(*resp.PortRange, "-")
|
||||
return net.JoinHostPort(*resp.IPAddress, portRange[0]), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("kubernetes load balancer with UID %s not found: %w", uid, err)
|
||||
}
|
||||
|
||||
// List retrieves all instances belonging to the current constellation.
|
||||
func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uid, err := c.uid(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var instances []metadata.InstanceMetadata
|
||||
var resp *computepb.Instance
|
||||
iter := c.instanceAPI.List(ctx, &computepb.ListInstancesRequest{
|
||||
Filter: proto.String(fmt.Sprintf("labels.%s:%s", cloud.TagUID, uid)),
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
})
|
||||
for resp, err = iter.Next(); err == nil; resp, err = iter.Next() {
|
||||
instance, err := convertToInstanceMetadata(resp, project, zone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving instance list from GCP: failed to convert instance: %w", err)
|
||||
}
|
||||
|
||||
// convertToInstanceMetadata already checks for nil resp.NetworkInterfaces
|
||||
if len(resp.NetworkInterfaces) == 0 || resp.NetworkInterfaces[0] == nil ||
|
||||
resp.NetworkInterfaces[0].Subnetwork == nil {
|
||||
return nil, errors.New("retrieving compute instance: received invalid instance")
|
||||
}
|
||||
|
||||
subnetCIDR, err := c.retrieveSubnetworkAliasCIDR(ctx, project, zone, *resp.NetworkInterfaces[0].Subnetwork)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving compute instance: failed to retrieve subnet CIDR: %w", err)
|
||||
}
|
||||
instance.SecondaryIPRange = subnetCIDR
|
||||
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
if errors.Is(err, iterator.Done) {
|
||||
return instances, nil
|
||||
}
|
||||
return nil, fmt.Errorf("retrieving instance list from GCP: %w", err)
|
||||
}
|
||||
|
||||
// ProviderID returns the providerID of the current instance.
|
||||
func (c *Cloud) ProviderID(ctx context.Context) (string, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gcpshared.JoinProviderID(project, zone, instanceName), nil
|
||||
}
|
||||
|
||||
// Self retrieves the current instance.
|
||||
func (c *Cloud) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
return c.getInstance(ctx, project, zone, instanceName)
|
||||
}
|
||||
|
||||
// UID retrieves the UID of the constellation.
|
||||
func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.uid(ctx, project, zone, instanceName)
|
||||
}
|
||||
|
||||
// getInstance retrieves an instance using its project, zone and name, and parses it to metadata.InstanceMetadata.
|
||||
func (c *Cloud) getInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error) {
|
||||
gcpInstance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceName,
|
||||
})
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("retrieving compute instance: %w", err)
|
||||
}
|
||||
|
||||
if gcpInstance == nil || gcpInstance.NetworkInterfaces == nil || len(gcpInstance.NetworkInterfaces) == 0 ||
|
||||
gcpInstance.NetworkInterfaces[0] == nil || gcpInstance.NetworkInterfaces[0].Subnetwork == nil {
|
||||
return metadata.InstanceMetadata{}, errors.New("retrieving compute instance: received invalid instance")
|
||||
}
|
||||
subnetCIDR, err := c.retrieveSubnetworkAliasCIDR(ctx, project, zone, *gcpInstance.NetworkInterfaces[0].Subnetwork)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
|
||||
instance, err := convertToInstanceMetadata(gcpInstance, project, zone)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("converting instance: %w", err)
|
||||
}
|
||||
instance.SecondaryIPRange = subnetCIDR
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// retrieveInstanceInfo retrieves the project, zone and instance name of the current instance using the imds API.
|
||||
func (c *Cloud) retrieveInstanceInfo() (project, zone, instanceName string, err error) {
|
||||
project, err = c.imds.ProjectID()
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("retrieving project ID from imds: %w", err)
|
||||
}
|
||||
zone, err = c.imds.Zone()
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("retrieving zone from imds: %w", err)
|
||||
}
|
||||
instanceName, err = c.imds.InstanceName()
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("retrieving instance name from imds: %w", err)
|
||||
}
|
||||
return project, zone, instanceName, nil
|
||||
}
|
||||
|
||||
// retrieveSubnetworkAliasCIDR retrieves the secondary IP range CIDR of the subnetwork,
|
||||
// identified by project, zone and subnetworkURI.
|
||||
func (c *Cloud) retrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, subnetworkURI string) (string, error) {
|
||||
// convert:
|
||||
// zone --> region
|
||||
// europe-west3-b --> europe-west3
|
||||
region := zoneFromRegionRegex.FindString(zone)
|
||||
if region == "" {
|
||||
return "", fmt.Errorf("invalid zone %s", zone)
|
||||
}
|
||||
|
||||
req := &computepb.GetSubnetworkRequest{
|
||||
Project: project,
|
||||
Region: region,
|
||||
Subnetwork: path.Base(subnetworkURI),
|
||||
}
|
||||
subnetwork, err := c.subnetAPI.Get(ctx, req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving subnetwork alias CIDR failed: %w", err)
|
||||
}
|
||||
if subnetwork == nil || len(subnetwork.SecondaryIpRanges) == 0 ||
|
||||
subnetwork.SecondaryIpRanges[0] == nil || subnetwork.SecondaryIpRanges[0].IpCidrRange == nil {
|
||||
return "", fmt.Errorf("retrieving subnetwork alias CIDR failed: received invalid subnetwork")
|
||||
}
|
||||
|
||||
return *subnetwork.SecondaryIpRanges[0].IpCidrRange, nil
|
||||
}
|
||||
|
||||
// uid retrieves the UID of the instance identified by project, zone and instanceName.
|
||||
// The UID is retrieved from the instance's labels.
|
||||
func (c *Cloud) uid(ctx context.Context, project, zone, instanceName string) (string, error) {
|
||||
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving compute instance: %w", err)
|
||||
}
|
||||
if instance == nil || instance.Labels == nil {
|
||||
return "", errors.New("retrieving compute instance: received instance with invalid labels")
|
||||
}
|
||||
return instance.Labels[cloud.TagUID], nil
|
||||
}
|
||||
|
||||
// convertToInstanceMetadata converts a *computepb.Instance to a metadata.InstanceMetadata.
|
||||
func convertToInstanceMetadata(in *computepb.Instance, project string, zone string) (metadata.InstanceMetadata, error) {
|
||||
if in.Name == nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("missing instance name")
|
||||
}
|
||||
|
||||
var vpcIP string
|
||||
var ips []string
|
||||
for _, interf := range in.NetworkInterfaces {
|
||||
if interf == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// use private IP from the default interface
|
||||
if interf.NetworkIP != nil && interf.Name != nil && *interf.Name == "nic0" {
|
||||
vpcIP = *interf.NetworkIP
|
||||
}
|
||||
|
||||
if interf.AliasIpRanges == nil {
|
||||
continue
|
||||
}
|
||||
for _, aliasIP := range interf.AliasIpRanges {
|
||||
if aliasIP != nil && aliasIP.IpCidrRange != nil {
|
||||
ips = append(ips, *aliasIP.IpCidrRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata.InstanceMetadata{
|
||||
Name: *in.Name,
|
||||
ProviderID: gcpshared.JoinProviderID(project, zone, *in.Name),
|
||||
Role: role.FromString(in.Labels[cloud.TagRole]),
|
||||
VPCIP: vpcIP,
|
||||
AliasIPRanges: ips,
|
||||
}, nil
|
||||
}
|
898
internal/cloud/gcp/cloud_test.go
Normal file
898
internal/cloud/gcp/cloud_test.go
Normal file
@ -0,0 +1,898 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/api/iterator"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetInstance(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
goodInstance := &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
Name: proto.String("nic0"),
|
||||
NetworkIP: proto.String("192.0.2.0"),
|
||||
AliasIpRanges: []*computepb.AliasIpRange{
|
||||
{
|
||||
IpCidrRange: proto.String("192.0.3.0/8"),
|
||||
},
|
||||
},
|
||||
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
projectID, instanceName, zone string
|
||||
instanceAPI stubInstanceAPI
|
||||
subnetAPI stubSubnetAPI
|
||||
wantErr bool
|
||||
wantInstance metadata.InstanceMetadata
|
||||
}{
|
||||
"success": {
|
||||
instanceName: "someInstance",
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
Name: "someInstance",
|
||||
Role: role.ControlPlane,
|
||||
ProviderID: "gce://someProject/someZone-west3-b/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
AliasIPRanges: []string{"192.0.3.0/8"},
|
||||
SecondaryIPRange: "198.51.100.0/24",
|
||||
},
|
||||
},
|
||||
"get instance error": {
|
||||
instanceName: "someInstance",
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instanceErr: someErr,
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get subnet error": {
|
||||
instanceName: "someInstance",
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnetErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid instance": {
|
||||
instanceName: "someInstance",
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: nil,
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid zone": {
|
||||
instanceName: "someInstance",
|
||||
projectID: "someProject",
|
||||
zone: "invalidZone",
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
instanceAPI: &tc.instanceAPI,
|
||||
subnetAPI: &tc.subnetAPI,
|
||||
}
|
||||
instance, err := cloud.getInstance(context.Background(), tc.projectID, tc.zone, tc.instanceName)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantInstance, instance)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLoadbalancerEndpoint(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
goodInstance := &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
Name: proto.String("nic0"),
|
||||
NetworkIP: proto.String("192.0.2.0"),
|
||||
AliasIpRanges: []*computepb.AliasIpRange{
|
||||
{
|
||||
IpCidrRange: proto.String("192.0.3.0/8"),
|
||||
},
|
||||
},
|
||||
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds stubIMDS
|
||||
instanceAPI stubInstanceAPI
|
||||
forwardingRulesAPI stubForwardingRulesAPI
|
||||
wantEndpoint string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
PortRange: proto.String("6443"),
|
||||
IPAddress: proto.String("192.0.2.255"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantEndpoint: "192.0.2.255:6443",
|
||||
},
|
||||
"imds error": {
|
||||
imds: stubIMDS{
|
||||
projectIDErr: someErr,
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
PortRange: proto.String("6443"),
|
||||
IPAddress: proto.String("192.0.2.255"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"iterator error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
err: someErr,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no forwarding rules": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"missing port range": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
IPAddress: proto.String("192.0.2.255"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"missing IP address": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
PortRange: proto.String("6443"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get instance error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instanceErr: someErr,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
PortRange: proto.String("6443"),
|
||||
IPAddress: proto.String("192.0.2.255"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid instance": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: nil,
|
||||
},
|
||||
forwardingRulesAPI: stubForwardingRulesAPI{
|
||||
iterator: &stubForwardingRulesIterator{
|
||||
forwardingRules: []*computepb.ForwardingRule{
|
||||
{
|
||||
PortRange: proto.String("6443"),
|
||||
IPAddress: proto.String("192.0.2.255"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &tc.imds,
|
||||
instanceAPI: &tc.instanceAPI,
|
||||
forwardingRulesAPI: &tc.forwardingRulesAPI,
|
||||
}
|
||||
|
||||
endpoint, err := cloud.GetLoadBalancerEndpoint(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantEndpoint, endpoint)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
goodInstance := &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
Name: proto.String("nic0"),
|
||||
NetworkIP: proto.String("192.0.2.0"),
|
||||
AliasIpRanges: []*computepb.AliasIpRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||
},
|
||||
},
|
||||
}
|
||||
goodSubnet := &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds stubIMDS
|
||||
instanceAPI stubInstanceAPI
|
||||
subnetAPI stubSubnetAPI
|
||||
wantErr bool
|
||||
wantInstances []metadata.InstanceMetadata
|
||||
}{
|
||||
"success": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
iterator: &stubInstanceIterator{
|
||||
instances: []*computepb.Instance{
|
||||
goodInstance,
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: goodSubnet,
|
||||
},
|
||||
wantInstances: []metadata.InstanceMetadata{
|
||||
{
|
||||
Name: "someInstance",
|
||||
Role: role.ControlPlane,
|
||||
ProviderID: "gce://someProject/someZone-west3-b/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
AliasIPRanges: []string{"198.51.100.0/24"},
|
||||
SecondaryIPRange: "198.51.100.0/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
"list multiple instances": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
iterator: &stubInstanceIterator{
|
||||
instances: []*computepb.Instance{
|
||||
goodInstance,
|
||||
{
|
||||
Name: proto.String("anotherInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.Worker.String(),
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
Name: proto.String("nic0"),
|
||||
NetworkIP: proto.String("192.0.2.1"),
|
||||
AliasIpRanges: []*computepb.AliasIpRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: goodSubnet,
|
||||
},
|
||||
wantInstances: []metadata.InstanceMetadata{
|
||||
{
|
||||
Name: "someInstance",
|
||||
Role: role.ControlPlane,
|
||||
ProviderID: "gce://someProject/someZone-west3-b/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
AliasIPRanges: []string{"198.51.100.0/24"},
|
||||
SecondaryIPRange: "198.51.100.0/24",
|
||||
},
|
||||
{
|
||||
Name: "anotherInstance",
|
||||
Role: role.Worker,
|
||||
ProviderID: "gce://someProject/someZone-west3-b/anotherInstance",
|
||||
VPCIP: "192.0.2.1",
|
||||
AliasIPRanges: []string{"198.51.100.0/24"},
|
||||
SecondaryIPRange: "198.51.100.0/24",
|
||||
},
|
||||
},
|
||||
},
|
||||
"imds error": {
|
||||
imds: stubIMDS{
|
||||
projectIDErr: someErr,
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
iterator: &stubInstanceIterator{
|
||||
instances: []*computepb.Instance{
|
||||
goodInstance,
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: goodSubnet,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"iterator error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
iterator: &stubInstanceIterator{
|
||||
err: someErr,
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: goodSubnet,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get instance error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instanceErr: someErr,
|
||||
iterator: &stubInstanceIterator{
|
||||
instances: []*computepb.Instance{
|
||||
goodInstance,
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnet: goodSubnet,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get subnet error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: goodInstance,
|
||||
iterator: &stubInstanceIterator{
|
||||
instances: []*computepb.Instance{
|
||||
goodInstance,
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: stubSubnetAPI{
|
||||
subnetErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &tc.imds,
|
||||
instanceAPI: &tc.instanceAPI,
|
||||
subnetAPI: &tc.subnetAPI,
|
||||
}
|
||||
|
||||
instances, err := cloud.List(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.ElementsMatch(tc.wantInstances, instances)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetrieveInstanceInfo(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds stubIMDS
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
},
|
||||
"get project id error": {
|
||||
imds: stubIMDS{
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
projectIDErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get zone error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
instanceName: "someInstance",
|
||||
zoneErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"get instance name error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceNameErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &tc.imds,
|
||||
}
|
||||
|
||||
project, zone, instance, err := cloud.retrieveInstanceInfo()
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.imds.projectID, project)
|
||||
assert.Equal(tc.imds.zone, zone)
|
||||
assert.Equal(tc.imds.instanceName, instance)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUID(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds stubIMDS
|
||||
instanceAPI stubInstanceAPI
|
||||
wantUID string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantUID: "1234",
|
||||
},
|
||||
"imds error": {
|
||||
imds: stubIMDS{
|
||||
projectIDErr: someErr,
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"instance error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instanceErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid instance": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &tc.imds,
|
||||
instanceAPI: &tc.instanceAPI,
|
||||
}
|
||||
|
||||
uid, err := cloud.UID(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantUID, uid)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfGetInstance(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: &stubInstanceAPI{
|
||||
instance: &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
Name: proto.String("nic0"),
|
||||
NetworkIP: proto.String("192.0.2.0"),
|
||||
AliasIpRanges: []*computepb.AliasIpRange{
|
||||
{
|
||||
IpCidrRange: proto.String("192.0.3.0/8"),
|
||||
},
|
||||
},
|
||||
Subnetwork: proto.String("projects/someProject/regions/someRegion/subnetworks/someSubnetwork"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
subnetAPI: &stubSubnetAPI{
|
||||
subnet: &computepb.Subnetwork{
|
||||
SecondaryIpRanges: []*computepb.SubnetworkSecondaryRange{
|
||||
{
|
||||
IpCidrRange: proto.String("198.51.100.0/24"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
self, err := cloud.Self(context.Background())
|
||||
require.NoError(err)
|
||||
|
||||
instance, err := cloud.GetInstance(context.Background(), self.ProviderID)
|
||||
require.NoError(err)
|
||||
|
||||
assert.Equal(self, instance)
|
||||
}
|
||||
|
||||
type stubForwardingRulesAPI struct {
|
||||
iterator forwardingRuleIterator
|
||||
}
|
||||
|
||||
func (s *stubForwardingRulesAPI) List(
|
||||
ctx context.Context, req *computepb.ListGlobalForwardingRulesRequest, opts ...gax.CallOption,
|
||||
) forwardingRuleIterator {
|
||||
return s.iterator
|
||||
}
|
||||
|
||||
func (s *stubForwardingRulesAPI) Close() error { return nil }
|
||||
|
||||
type stubForwardingRulesIterator struct {
|
||||
ctr int
|
||||
forwardingRules []*computepb.ForwardingRule
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubForwardingRulesIterator) Next() (*computepb.ForwardingRule, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
if s.ctr >= len(s.forwardingRules) {
|
||||
return nil, iterator.Done
|
||||
}
|
||||
s.ctr++
|
||||
return s.forwardingRules[s.ctr-1], nil
|
||||
}
|
||||
|
||||
type stubIMDS struct {
|
||||
instanceID string
|
||||
projectID string
|
||||
zone string
|
||||
instanceName string
|
||||
instanceIDErr error
|
||||
projectIDErr error
|
||||
zoneErr error
|
||||
instanceNameErr error
|
||||
}
|
||||
|
||||
func (s *stubIMDS) InstanceID() (string, error) { return s.instanceID, s.instanceIDErr }
|
||||
|
||||
func (s *stubIMDS) ProjectID() (string, error) { return s.projectID, s.projectIDErr }
|
||||
|
||||
func (s *stubIMDS) Zone() (string, error) { return s.zone, s.zoneErr }
|
||||
|
||||
func (s *stubIMDS) InstanceName() (string, error) { return s.instanceName, s.instanceNameErr }
|
||||
|
||||
type stubInstanceAPI struct {
|
||||
instance *computepb.Instance
|
||||
instanceErr error
|
||||
iterator *stubInstanceIterator
|
||||
}
|
||||
|
||||
func (s *stubInstanceAPI) Get(
|
||||
ctx context.Context, req *computepb.GetInstanceRequest, opts ...gax.CallOption,
|
||||
) (*computepb.Instance, error) {
|
||||
return s.instance, s.instanceErr
|
||||
}
|
||||
|
||||
func (s *stubInstanceAPI) List(
|
||||
ctx context.Context, req *computepb.ListInstancesRequest, opts ...gax.CallOption,
|
||||
) instanceIterator {
|
||||
return s.iterator
|
||||
}
|
||||
|
||||
func (s *stubInstanceAPI) Close() error { return nil }
|
||||
|
||||
type stubInstanceIterator struct {
|
||||
ctr int
|
||||
instances []*computepb.Instance
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubInstanceIterator) Next() (*computepb.Instance, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
if s.ctr >= len(s.instances) {
|
||||
return nil, iterator.Done
|
||||
}
|
||||
s.ctr++
|
||||
return s.instances[s.ctr-1], nil
|
||||
}
|
||||
|
||||
type stubSubnetAPI struct {
|
||||
subnet *computepb.Subnetwork
|
||||
subnetErr error
|
||||
}
|
||||
|
||||
func (s *stubSubnetAPI) Get(
|
||||
ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption,
|
||||
) (*computepb.Subnetwork, error) {
|
||||
return s.subnet, s.subnetErr
|
||||
}
|
||||
func (s *stubSubnetAPI) Close() error { return nil }
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import "github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
|
||||
// CloudNodeManager holds the GCP cloud-node-manager configuration.
|
||||
type CloudNodeManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
|
||||
// Not used on GCP.
|
||||
func (c *CloudNodeManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-node-manager executable within the container image.
|
||||
// Not used on GCP.
|
||||
func (c *CloudNodeManager) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
|
||||
// Not used on GCP.
|
||||
func (c *CloudNodeManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
|
||||
func (c *CloudNodeManager) Supported() bool {
|
||||
return false
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTrivialCNMFunctions(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cloud := CloudNodeManager{}
|
||||
|
||||
assert.Empty(cloud.Image(""))
|
||||
assert.Empty(cloud.Path())
|
||||
assert.Empty(cloud.ExtraArgs())
|
||||
assert.False(cloud.Supported())
|
||||
}
|
@ -9,50 +9,29 @@ package gcp
|
||||
import (
|
||||
"context"
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
)
|
||||
|
||||
type instanceAPI interface {
|
||||
Get(ctx context.Context, req *computepb.GetInstanceRequest, opts ...gax.CallOption) (*computepb.Instance, error)
|
||||
List(ctx context.Context, req *computepb.ListInstancesRequest, opts ...gax.CallOption) InstanceIterator
|
||||
SetMetadata(ctx context.Context, req *computepb.SetMetadataInstanceRequest, opts ...gax.CallOption) (*compute.Operation, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type subnetworkAPI interface {
|
||||
List(ctx context.Context, req *computepb.ListSubnetworksRequest, opts ...gax.CallOption) SubnetworkIterator
|
||||
Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type forwardingRulesAPI interface {
|
||||
List(ctx context.Context, req *computepb.ListGlobalForwardingRulesRequest, opts ...gax.CallOption) ForwardingRuleIterator
|
||||
List(ctx context.Context, req *computepb.ListGlobalForwardingRulesRequest, opts ...gax.CallOption) forwardingRuleIterator
|
||||
Close() error
|
||||
}
|
||||
|
||||
type metadataAPI interface {
|
||||
InstanceAttributeValue(attr string) (string, error)
|
||||
type imdsAPI interface {
|
||||
InstanceID() (string, error)
|
||||
ProjectID() (string, error)
|
||||
Zone() (string, error)
|
||||
InstanceName() (string, error)
|
||||
}
|
||||
|
||||
type Operation interface {
|
||||
Proto() *computepb.Operation
|
||||
type instanceAPI interface {
|
||||
Get(ctx context.Context, req *computepb.GetInstanceRequest, opts ...gax.CallOption) (*computepb.Instance, error)
|
||||
List(ctx context.Context, req *computepb.ListInstancesRequest, opts ...gax.CallOption) instanceIterator
|
||||
Close() error
|
||||
}
|
||||
|
||||
type InstanceIterator interface {
|
||||
Next() (*computepb.Instance, error)
|
||||
}
|
||||
|
||||
type SubnetworkIterator interface {
|
||||
Next() (*computepb.Subnetwork, error)
|
||||
}
|
||||
|
||||
type ForwardingRuleIterator interface {
|
||||
Next() (*computepb.ForwardingRule, error)
|
||||
type subnetAPI interface {
|
||||
Get(ctx context.Context, req *computepb.GetSubnetworkRequest, opts ...gax.CallOption) (*computepb.Subnetwork, error)
|
||||
Close() error
|
||||
}
|
@ -8,10 +8,11 @@ package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"cloud.google.com/go/logging"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
@ -21,11 +22,12 @@ type Logger struct {
|
||||
|
||||
// NewLogger creates a new Cloud Logger for GCP.
|
||||
// https://cloud.google.com/logging/docs/setup/go
|
||||
func NewLogger(ctx context.Context, providerID string, logName string) (*Logger, error) {
|
||||
projectID, _, _, err := gcpshared.SplitProviderID(providerID)
|
||||
func NewLogger(ctx context.Context, logName string) (*Logger, error) {
|
||||
projectID, err := metadata.NewClient(nil).ProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("retrieving project ID from imds: %w", err)
|
||||
}
|
||||
|
||||
client, err := logging.NewClient(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/gcpshared"
|
||||
)
|
||||
|
||||
// API handles all GCP API requests.
|
||||
type API interface {
|
||||
// UID retrieves the current instances uid.
|
||||
UID(context.Context) (string, error)
|
||||
// RetrieveInstances retrieves a list of all accessible GCP instances with their metadata.
|
||||
RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error)
|
||||
// RetrieveInstances retrieves a single GCP instances with its metadata.
|
||||
RetrieveInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error)
|
||||
// RetrieveInstanceMetadata retrieves the GCP instance metadata of the current instance.
|
||||
RetrieveInstanceMetadata(attr string) (string, error)
|
||||
// RetrieveProjectID retrieves the GCP projectID containing the current instance.
|
||||
RetrieveProjectID() (string, error)
|
||||
// RetrieveZone retrieves the GCP zone containing the current instance.
|
||||
RetrieveZone() (string, error)
|
||||
// RetrieveInstanceName retrieves the instance name of the current instance.
|
||||
RetrieveInstanceName() (string, error)
|
||||
// RetrieveSubnetworkAliasCIDR retrieves the subnetwork CIDR of the current instance.
|
||||
RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error)
|
||||
// RetrieveLoadBalancerEndpoint retrieves the load balancer endpoint of the current instance.
|
||||
RetrieveLoadBalancerEndpoint(ctx context.Context, project string) (string, error)
|
||||
// SetInstanceMetadata sets metadata key: value of the instance specified by project, zone and instanceName.
|
||||
SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error
|
||||
// UnsetInstanceMetadata removes a metadata key-value pair of the instance specified by project, zone and instanceName.
|
||||
UnsetInstanceMetadata(ctx context.Context, project, zone, instanceName, key string) error
|
||||
}
|
||||
|
||||
// Metadata implements core.ProviderMetadata interface.
|
||||
type Metadata struct {
|
||||
api API
|
||||
}
|
||||
|
||||
// New creates a new Provider with real API and FS.
|
||||
func New(api API) *Metadata {
|
||||
return &Metadata{
|
||||
api: api,
|
||||
}
|
||||
}
|
||||
|
||||
// List retrieves all instances belonging to the current constellation.
|
||||
func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
project, err := m.api.RetrieveProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zone, err := m.api.RetrieveZone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instances, err := m.api.RetrieveInstances(ctx, project, zone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving instances list from GCP api: %w", err)
|
||||
}
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
// Self retrieves the current instance.
|
||||
func (m *Metadata) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
|
||||
project, err := m.api.RetrieveProjectID()
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
zone, err := m.api.RetrieveZone()
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
instanceName, err := m.api.RetrieveInstanceName()
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
subnetCIDR, err := m.api.RetrieveSubnetworkAliasCIDR(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
|
||||
instance, err := m.api.RetrieveInstance(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, err
|
||||
}
|
||||
instance.SecondaryIPRange = subnetCIDR
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// GetInstance retrieves an instance using its providerID.
|
||||
func (m *Metadata) GetInstance(ctx context.Context, providerID string) (metadata.InstanceMetadata, error) {
|
||||
project, zone, instanceName, err := gcpshared.SplitProviderID(providerID)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("invalid providerID: %w", err)
|
||||
}
|
||||
return m.api.RetrieveInstance(ctx, project, zone, instanceName)
|
||||
}
|
||||
|
||||
// SupportsLoadBalancer returns true if the cloud provider supports load balancers.
|
||||
func (m *Metadata) SupportsLoadBalancer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
|
||||
func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
project, err := m.api.RetrieveProjectID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.api.RetrieveLoadBalancerEndpoint(ctx, project)
|
||||
}
|
||||
|
||||
// UID retrieves the UID of the constellation.
|
||||
func (m *Metadata) UID(ctx context.Context) (string, error) {
|
||||
return m.api.UID(ctx)
|
||||
}
|
||||
|
||||
// Supported is used to determine if metadata API is implemented for this cloud provider.
|
||||
func (m *Metadata) Supported() bool {
|
||||
return true
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
err := errors.New("some err")
|
||||
uid := "1234"
|
||||
instancesGenerator := func() *[]metadata.InstanceMetadata {
|
||||
return &[]metadata.InstanceMetadata{
|
||||
{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
client stubGCPClient
|
||||
instancesGenerator func() *[]metadata.InstanceMetadata
|
||||
instancesMutator func(*[]metadata.InstanceMetadata)
|
||||
wantErr bool
|
||||
wantInstances []metadata.InstanceMetadata
|
||||
}{
|
||||
"retrieve works": {
|
||||
client: stubGCPClient{
|
||||
projectID: "someProjectID",
|
||||
zone: "someZone",
|
||||
retrieveInstanceMetadaValues: map[string]string{
|
||||
cloud.TagUID: uid,
|
||||
},
|
||||
},
|
||||
instancesGenerator: instancesGenerator,
|
||||
wantInstances: []metadata.InstanceMetadata{
|
||||
{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"retrieve error is detected": {
|
||||
client: stubGCPClient{
|
||||
projectID: "someProjectID",
|
||||
zone: "someZone",
|
||||
retrieveInstanceMetadaValues: map[string]string{
|
||||
cloud.TagUID: uid,
|
||||
},
|
||||
retrieveInstancesErr: err,
|
||||
},
|
||||
instancesGenerator: instancesGenerator,
|
||||
wantErr: true,
|
||||
},
|
||||
"project metadata retrieval error is detected": {
|
||||
client: stubGCPClient{
|
||||
retrieveProjectIDErr: err,
|
||||
},
|
||||
instancesGenerator: instancesGenerator,
|
||||
wantErr: true,
|
||||
},
|
||||
"zone retrieval error is detected": {
|
||||
client: stubGCPClient{
|
||||
retrieveZoneErr: err,
|
||||
},
|
||||
instancesGenerator: instancesGenerator,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
tc.client.retrieveInstancesValues = *tc.instancesGenerator()
|
||||
if tc.instancesMutator != nil {
|
||||
tc.instancesMutator(&tc.client.retrieveInstancesValues)
|
||||
}
|
||||
metadata := New(&tc.client)
|
||||
instances, err := metadata.List(context.Background())
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.ElementsMatch(tc.wantInstances, instances)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelf(t *testing.T) {
|
||||
err := errors.New("some err")
|
||||
uid := "1234"
|
||||
|
||||
testCases := map[string]struct {
|
||||
client stubGCPClient
|
||||
wantErr bool
|
||||
wantInstance metadata.InstanceMetadata
|
||||
}{
|
||||
"retrieve works": {
|
||||
client: stubGCPClient{
|
||||
projectID: "someProjectID",
|
||||
zone: "someZone",
|
||||
retrieveInstanceValue: metadata.InstanceMetadata{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
},
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
},
|
||||
"retrieve error is detected": {
|
||||
client: stubGCPClient{
|
||||
projectID: "someProjectID",
|
||||
zone: "someZone",
|
||||
retrieveInstanceMetadaValues: map[string]string{
|
||||
cloud.TagUID: uid,
|
||||
},
|
||||
retrieveInstanceErr: err,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"project id retrieval error is detected": {
|
||||
client: stubGCPClient{
|
||||
retrieveProjectIDErr: err,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"zone retrieval error is detected": {
|
||||
client: stubGCPClient{
|
||||
retrieveZoneErr: err,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"instance name retrieval error is detected": {
|
||||
client: stubGCPClient{
|
||||
retrieveInstanceNameErr: err,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := New(&tc.client)
|
||||
instance, err := cloud.Self(context.Background())
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantInstance, instance)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInstance(t *testing.T) {
|
||||
err := errors.New("some err")
|
||||
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
||||
client stubGCPClient
|
||||
wantErr bool
|
||||
wantInstance metadata.InstanceMetadata
|
||||
}{
|
||||
"retrieve works": {
|
||||
providerID: "gce://someProject/someZone/someInstance",
|
||||
client: stubGCPClient{
|
||||
retrieveInstanceValue: metadata.InstanceMetadata{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
},
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
Name: "someInstance",
|
||||
ProviderID: "gce://someProject/someZone/someInstance",
|
||||
VPCIP: "192.0.2.0",
|
||||
},
|
||||
},
|
||||
"retrieve error is detected": {
|
||||
providerID: "gce://someProject/someZone/someInstance",
|
||||
client: stubGCPClient{
|
||||
retrieveInstanceErr: err,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"malformed providerID with too many fields is detected": {
|
||||
providerID: "gce://someProject/someZone/someInstance/tooMany/fields",
|
||||
wantErr: true,
|
||||
},
|
||||
"malformed providerID with too few fields is detected": {
|
||||
providerID: "gce://someProject",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cloud := New(&tc.client)
|
||||
instance, err := cloud.GetInstance(context.Background(), tc.providerID)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantInstance, instance)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubGCPClient struct {
|
||||
retrieveUIDValue string
|
||||
retrieveUIDErr error
|
||||
retrieveInstanceValue metadata.InstanceMetadata
|
||||
retrieveInstanceErr error
|
||||
retrieveInstancesValues []metadata.InstanceMetadata
|
||||
retrieveInstancesErr error
|
||||
retrieveInstanceMetadaValues map[string]string
|
||||
retrieveInstanceMetadataErr error
|
||||
retrieveSubnetworkAliasErr error
|
||||
projectID string
|
||||
zone string
|
||||
instanceName string
|
||||
loadBalancerIP string
|
||||
retrieveProjectIDErr error
|
||||
retrieveZoneErr error
|
||||
retrieveInstanceNameErr error
|
||||
setInstanceMetadataErr error
|
||||
unsetInstanceMetadataErr error
|
||||
retrieveLoadBalancerErr error
|
||||
|
||||
instanceMetadataProjects []string
|
||||
instanceMetadataZones []string
|
||||
instanceMetadataInstanceNames []string
|
||||
instanceMetadataKeys []string
|
||||
instanceMetadataValues []string
|
||||
|
||||
unsetMetadataProjects []string
|
||||
unsetMetadataZones []string
|
||||
unsetMetadataInstanceNames []string
|
||||
unsetMetadataKeys []string
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveInstances(ctx context.Context, project, zone string) ([]metadata.InstanceMetadata, error) {
|
||||
return s.retrieveInstancesValues, s.retrieveInstancesErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveInstance(ctx context.Context, project, zone string, instanceName string) (metadata.InstanceMetadata, error) {
|
||||
return s.retrieveInstanceValue, s.retrieveInstanceErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveInstanceMetadata(attr string) (string, error) {
|
||||
return s.retrieveInstanceMetadaValues[attr], s.retrieveInstanceMetadataErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveProjectID() (string, error) {
|
||||
return s.projectID, s.retrieveProjectIDErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveZone() (string, error) {
|
||||
return s.zone, s.retrieveZoneErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveInstanceName() (string, error) {
|
||||
return s.instanceName, s.retrieveInstanceNameErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveLoadBalancerEndpoint(ctx context.Context, project string) (string, error) {
|
||||
return s.loadBalancerIP, s.retrieveLoadBalancerErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) UID(context.Context) (string, error) {
|
||||
return s.retrieveUIDValue, s.retrieveUIDErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) SetInstanceMetadata(ctx context.Context, project, zone, instanceName, key, value string) error {
|
||||
s.instanceMetadataProjects = append(s.instanceMetadataProjects, project)
|
||||
s.instanceMetadataZones = append(s.instanceMetadataZones, zone)
|
||||
s.instanceMetadataInstanceNames = append(s.instanceMetadataInstanceNames, instanceName)
|
||||
s.instanceMetadataKeys = append(s.instanceMetadataKeys, key)
|
||||
s.instanceMetadataValues = append(s.instanceMetadataValues, value)
|
||||
|
||||
return s.setInstanceMetadataErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) UnsetInstanceMetadata(ctx context.Context, project, zone, instanceName, key string) error {
|
||||
s.unsetMetadataProjects = append(s.unsetMetadataProjects, project)
|
||||
s.unsetMetadataZones = append(s.unsetMetadataZones, zone)
|
||||
s.unsetMetadataInstanceNames = append(s.unsetMetadataInstanceNames, instanceName)
|
||||
s.unsetMetadataKeys = append(s.unsetMetadataKeys, key)
|
||||
|
||||
return s.unsetInstanceMetadataErr
|
||||
}
|
||||
|
||||
func (s *stubGCPClient) RetrieveSubnetworkAliasCIDR(ctx context.Context, project, zone, instanceName string) (string, error) {
|
||||
return "", s.retrieveSubnetworkAliasErr
|
||||
}
|
@ -10,43 +10,16 @@ import (
|
||||
"context"
|
||||
|
||||
compute "cloud.google.com/go/compute/apiv1"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
)
|
||||
|
||||
type instanceClient struct {
|
||||
*compute.InstancesClient
|
||||
type forwardingRuleIterator interface {
|
||||
Next() (*computepb.ForwardingRule, error)
|
||||
}
|
||||
|
||||
func (c *instanceClient) Close() error {
|
||||
return c.InstancesClient.Close()
|
||||
}
|
||||
|
||||
func (c *instanceClient) List(ctx context.Context, req *computepb.ListInstancesRequest,
|
||||
opts ...gax.CallOption,
|
||||
) InstanceIterator {
|
||||
return c.InstancesClient.List(ctx, req)
|
||||
}
|
||||
|
||||
type subnetworkClient struct {
|
||||
*compute.SubnetworksClient
|
||||
}
|
||||
|
||||
func (c *subnetworkClient) Close() error {
|
||||
return c.SubnetworksClient.Close()
|
||||
}
|
||||
|
||||
func (c *subnetworkClient) List(ctx context.Context, req *computepb.ListSubnetworksRequest,
|
||||
opts ...gax.CallOption,
|
||||
) SubnetworkIterator {
|
||||
return c.SubnetworksClient.List(ctx, req)
|
||||
}
|
||||
|
||||
func (c *subnetworkClient) Get(ctx context.Context, req *computepb.GetSubnetworkRequest,
|
||||
opts ...gax.CallOption,
|
||||
) (*computepb.Subnetwork, error) {
|
||||
return c.SubnetworksClient.Get(ctx, req)
|
||||
type instanceIterator interface {
|
||||
Next() (*computepb.Instance, error)
|
||||
}
|
||||
|
||||
type forwardingRulesClient struct {
|
||||
@ -59,32 +32,20 @@ func (c *forwardingRulesClient) Close() error {
|
||||
|
||||
func (c *forwardingRulesClient) List(ctx context.Context, req *computepb.ListGlobalForwardingRulesRequest,
|
||||
opts ...gax.CallOption,
|
||||
) ForwardingRuleIterator {
|
||||
) forwardingRuleIterator {
|
||||
return c.GlobalForwardingRulesClient.List(ctx, req)
|
||||
}
|
||||
|
||||
type metadataClient struct{}
|
||||
|
||||
func (c *metadataClient) InstanceAttributeValue(attr string) (string, error) {
|
||||
return metadata.InstanceAttributeValue(attr)
|
||||
type instanceClient struct {
|
||||
*compute.InstancesClient
|
||||
}
|
||||
|
||||
func (c *metadataClient) InstanceID() (string, error) {
|
||||
return metadata.InstanceID()
|
||||
func (c *instanceClient) Close() error {
|
||||
return c.InstancesClient.Close()
|
||||
}
|
||||
|
||||
func (c *metadataClient) ProjectID() (string, error) {
|
||||
return metadata.ProjectID()
|
||||
}
|
||||
|
||||
func (c *metadataClient) Zone() (string, error) {
|
||||
return metadata.Zone()
|
||||
}
|
||||
|
||||
func (c *metadataClient) InstanceName() (string, error) {
|
||||
return metadata.InstanceName()
|
||||
}
|
||||
|
||||
func (c *metadataClient) ProjectAttributeValue(attr string) (string, error) {
|
||||
return metadata.ProjectAttributeValue(attr)
|
||||
func (c *instanceClient) List(ctx context.Context, req *computepb.ListInstancesRequest,
|
||||
opts ...gax.CallOption,
|
||||
) instanceIterator {
|
||||
return c.InstancesClient.List(ctx, req)
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Writer implements ConfigWriter.
|
||||
type Writer struct {
|
||||
fs afero.Afero
|
||||
}
|
||||
|
||||
// WriteGCEConf persists the GCE config on disk.
|
||||
func (w *Writer) WriteGCEConf(config string) error {
|
||||
if err := w.fs.WriteFile("/etc/gce.conf", []byte(config), 0o644); err != nil {
|
||||
return fmt.Errorf("writing gce config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWriteGCEConf(t *testing.T) {
|
||||
config := "someConfig"
|
||||
|
||||
testCases := map[string]struct {
|
||||
fs afero.Afero
|
||||
wantValue string
|
||||
wantErr bool
|
||||
}{
|
||||
"write works": {
|
||||
fs: afero.Afero{
|
||||
Fs: afero.NewMemMapFs(),
|
||||
},
|
||||
wantValue: config,
|
||||
wantErr: false,
|
||||
},
|
||||
"write fails": {
|
||||
fs: afero.Afero{
|
||||
Fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
writer := Writer{
|
||||
fs: tc.fs,
|
||||
}
|
||||
err := writer.WriteGCEConf(config)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
value, err := tc.fs.ReadFile("/etc/gce.conf")
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantValue, string(value))
|
||||
})
|
||||
}
|
||||
}
|
15
internal/cloud/gcpshared/doc.go
Normal file
15
internal/cloud/gcpshared/doc.go
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
Package gcpshared contains code to parse and define data types
|
||||
relevant for Google Cloud Platform.
|
||||
|
||||
This package is intended to have a minimal size and surface. If you
|
||||
have GCP related code that is not shared by multiple applications,
|
||||
or if the code interacts with the GCP API, please keep the code in
|
||||
the application's internal package or add it to the GCP cloud package.
|
||||
*/
|
||||
package gcpshared
|
@ -13,8 +13,8 @@ import (
|
||||
|
||||
var providerIDRegex = regexp.MustCompile(`^gce://([^/]+)/([^/]+)/([^/]+)$`)
|
||||
|
||||
// SplitProviderID splits a provider's id into core components.
|
||||
// A providerID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
|
||||
// SplitProviderID splits a k8s provider ID for GCP instances into its core components.
|
||||
// A provider ID is build after the schema 'gce://<project-id>/<zone>/<instance-name>'
|
||||
func SplitProviderID(providerID string) (project, zone, instance string, err error) {
|
||||
matches := providerIDRegex.FindStringSubmatch(providerID)
|
||||
if len(matches) != 4 {
|
@ -10,13 +10,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestSplitProviderID(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
providerID string
|
@ -23,11 +23,9 @@ type InstanceMetadata struct {
|
||||
Role role.Role
|
||||
// VPCIP is the primary IP address of the instance in the VPC.
|
||||
VPCIP string
|
||||
// PublicIP is the primary public IP of the instance, if available, empty string otherwise.
|
||||
PublicIP string
|
||||
// SSHKeys maps usernames to ssh public keys.
|
||||
// TODO: remove everywhere.
|
||||
SSHKeys map[string][]string
|
||||
|
||||
// SecondaryIPRange is the VPC wide CIDR from which subnets are attached to VMs as AliasIPRanges.
|
||||
// May be empty on certain CSPs.
|
||||
SecondaryIPRange string
|
||||
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
k8s "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// CloudControllerManager holds the QEMU cloud-controller-manager configuration.
|
||||
type CloudControllerManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-controller-manager for the cloud-provider.
|
||||
func (c CloudControllerManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-controller-manager executable within the container image.
|
||||
func (c CloudControllerManager) Path() string {
|
||||
return "/qemu-cloud-controller-manager"
|
||||
}
|
||||
|
||||
// Name returns the cloud-provider name as used by k8s cloud-controller-manager (k8s.gcr.io/cloud-controller-manager).
|
||||
func (c CloudControllerManager) Name() string {
|
||||
return "qemu"
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-controller-manager command.
|
||||
func (c CloudControllerManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// ConfigMaps returns a list of ConfigMaps to deploy together with the k8s cloud-controller-manager
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/configmap/ .
|
||||
func (c CloudControllerManager) ConfigMaps() (kubernetes.ConfigMaps, error) {
|
||||
return kubernetes.ConfigMaps{}, nil
|
||||
}
|
||||
|
||||
// Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/configuration/secret/ .
|
||||
func (c CloudControllerManager) Secrets(ctx context.Context, providerID, cloudServiceAccountURI string) (kubernetes.Secrets, error) {
|
||||
return kubernetes.Secrets{}, nil
|
||||
}
|
||||
|
||||
// Volumes returns a list of volumes to deploy together with the k8s cloud-controller-manager.
|
||||
// Reference: https://kubernetes.io/docs/concepts/storage/volumes/ .
|
||||
func (c CloudControllerManager) Volumes() []k8s.Volume {
|
||||
return []k8s.Volume{}
|
||||
}
|
||||
|
||||
// VolumeMounts a list of of volume mounts to deploy together with the k8s cloud-controller-manager.
|
||||
func (c CloudControllerManager) VolumeMounts() []k8s.VolumeMount {
|
||||
return []k8s.VolumeMount{}
|
||||
}
|
||||
|
||||
// Env returns a list of k8s environment key-value pairs to deploy together with the k8s cloud-controller-manager.
|
||||
func (c CloudControllerManager) Env() []k8s.EnvVar {
|
||||
return []k8s.EnvVar{}
|
||||
}
|
||||
|
||||
// PrepareInstance is called on every instance before deploying the cloud-controller-manager.
|
||||
// Allows for cloud-provider specific hooks.
|
||||
func (c CloudControllerManager) PrepareInstance(instance metadata.InstanceMetadata, vpnIP string) error {
|
||||
// no specific hook required.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud controller manager is implemented for this cloud provider.
|
||||
func (c CloudControllerManager) Supported() bool {
|
||||
return false
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package qemu
|
||||
|
||||
import "github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
|
||||
// CloudNodeManager holds the QEMU cloud-node-manager configuration.
|
||||
type CloudNodeManager struct{}
|
||||
|
||||
// Image returns the container image used to provide cloud-node-manager for the cloud-provider.
|
||||
// Not used on QEMU.
|
||||
func (c *CloudNodeManager) Image(k8sVersion versions.ValidK8sVersion) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Path returns the path used by cloud-node-manager executable within the container image.
|
||||
// Not used on QEMU.
|
||||
func (c *CloudNodeManager) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtraArgs returns a list of arguments to append to the cloud-node-manager command.
|
||||
// Not used on QEMU.
|
||||
func (c *CloudNodeManager) ExtraArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Supported is used to determine if cloud node manager is implemented for this cloud provider.
|
||||
func (c *CloudNodeManager) Supported() bool {
|
||||
return false
|
||||
}
|
@ -22,11 +22,6 @@ const qemuMetadataEndpoint = "10.42.0.1:8080"
|
||||
// Metadata implements core.ProviderMetadata interface for QEMU.
|
||||
type Metadata struct{}
|
||||
|
||||
// Supported is used to determine if metadata API is implemented for this cloud provider.
|
||||
func (m *Metadata) Supported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// List retrieves all instances belonging to the current constellation.
|
||||
func (m *Metadata) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
instancesRaw, err := m.retrieveMetadata(ctx, "/peers")
|
||||
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package gcpshared
|
||||
|
||||
/*
|
||||
Package gcpshared contains code that is related to Google Cloud Platform
|
||||
and is used by multiple microservices.
|
||||
|
||||
This package is intended to have a minimal size and surface. If you
|
||||
have GCP related code that is not shared by multiple microservices,
|
||||
please keep the code in the microservice's internal package.
|
||||
*/
|
@ -130,11 +130,12 @@ func getVPCIP(ctx context.Context, provider string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
case cloudprovider.GCP:
|
||||
gcpClient, err := gcpcloud.NewClient(ctx)
|
||||
gcpMeta, err := gcpcloud.New(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
metadata = gcpcloud.New(gcpClient)
|
||||
defer gcpMeta.Close()
|
||||
metadata = gcpMeta
|
||||
case cloudprovider.QEMU:
|
||||
metadata = &qemucloud.Metadata{}
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user