mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-27 07:47:02 -05:00
AB#2579 Add constellation iam create command (#624)
This commit is contained in:
parent
be01cf7129
commit
286803fb97
@ -52,6 +52,7 @@ func NewRootCmd() *cobra.Command {
|
|||||||
rootCmd.AddCommand(cmd.NewRecoverCmd())
|
rootCmd.AddCommand(cmd.NewRecoverCmd())
|
||||||
rootCmd.AddCommand(cmd.NewTerminateCmd())
|
rootCmd.AddCommand(cmd.NewTerminateCmd())
|
||||||
rootCmd.AddCommand(cmd.NewVersionCmd())
|
rootCmd.AddCommand(cmd.NewVersionCmd())
|
||||||
|
rootCmd.AddCommand(cmd.NewIAMCmd())
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type terraformClient interface {
|
type terraformClient interface {
|
||||||
PrepareWorkspace(provider cloudprovider.Provider, input terraform.Variables) error
|
PrepareWorkspace(path string, input terraform.Variables) error
|
||||||
CreateCluster(ctx context.Context) (string, string, error)
|
CreateCluster(ctx context.Context) (string, string, error)
|
||||||
|
CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error)
|
||||||
DestroyCluster(ctx context.Context) error
|
DestroyCluster(ctx context.Context) error
|
||||||
CleanUpWorkspace() error
|
CleanUpWorkspace() error
|
||||||
RemoveInstaller()
|
RemoveInstaller()
|
||||||
|
@ -28,6 +28,7 @@ func TestMain(m *testing.M) {
|
|||||||
type stubTerraformClient struct {
|
type stubTerraformClient struct {
|
||||||
ip string
|
ip string
|
||||||
initSecret string
|
initSecret string
|
||||||
|
iamOutput terraform.IAMOutput
|
||||||
cleanUpWorkspaceCalled bool
|
cleanUpWorkspaceCalled bool
|
||||||
removeInstallerCalled bool
|
removeInstallerCalled bool
|
||||||
destroyClusterCalled bool
|
destroyClusterCalled bool
|
||||||
@ -35,13 +36,18 @@ type stubTerraformClient struct {
|
|||||||
destroyClusterErr error
|
destroyClusterErr error
|
||||||
prepareWorkspaceErr error
|
prepareWorkspaceErr error
|
||||||
cleanUpWorkspaceErr error
|
cleanUpWorkspaceErr error
|
||||||
|
iamOutputErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (string, string, error) {
|
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (string, string, error) {
|
||||||
return c.ip, c.initSecret, c.createClusterErr
|
return c.ip, c.initSecret, c.createClusterErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *stubTerraformClient) PrepareWorkspace(provider cloudprovider.Provider, input terraform.Variables) error {
|
func (c *stubTerraformClient) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error) {
|
||||||
|
return c.iamOutput, c.iamOutputErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *stubTerraformClient) PrepareWorkspace(path string, input terraform.Variables) error {
|
||||||
return c.prepareWorkspaceErr
|
return c.prepareWorkspaceErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -100,7 +101,7 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
|
|||||||
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int, image string,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.AWSVariables{
|
vars := terraform.AWSClusterVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
Name: name,
|
Name: name,
|
||||||
CountControlPlanes: controlPlaneCount,
|
CountControlPlanes: controlPlaneCount,
|
||||||
@ -117,7 +118,7 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
|
|||||||
Debug: config.IsDebugCluster(),
|
Debug: config.IsDebugCluster(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cl.PrepareWorkspace(cloudprovider.AWS, &vars); err != nil {
|
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +138,7 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
|
|||||||
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int, image string,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.GCPVariables{
|
vars := terraform.GCPClusterVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
Name: name,
|
Name: name,
|
||||||
CountControlPlanes: controlPlaneCount,
|
CountControlPlanes: controlPlaneCount,
|
||||||
@ -154,7 +155,7 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
|
|||||||
Debug: config.IsDebugCluster(),
|
Debug: config.IsDebugCluster(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cl.PrepareWorkspace(cloudprovider.GCP, &vars); err != nil {
|
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +175,7 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
|
|||||||
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
|
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
|
||||||
name, insType string, controlPlaneCount, workerCount int, image string,
|
name, insType string, controlPlaneCount, workerCount int, image string,
|
||||||
) (idFile clusterid.File, retErr error) {
|
) (idFile clusterid.File, retErr error) {
|
||||||
vars := terraform.AzureVariables{
|
vars := terraform.AzureClusterVariables{
|
||||||
CommonVariables: terraform.CommonVariables{
|
CommonVariables: terraform.CommonVariables{
|
||||||
Name: name,
|
Name: name,
|
||||||
CountControlPlanes: controlPlaneCount,
|
CountControlPlanes: controlPlaneCount,
|
||||||
@ -194,7 +195,7 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
|
|||||||
|
|
||||||
vars = normalizeAzureURIs(vars)
|
vars = normalizeAzureURIs(vars)
|
||||||
|
|
||||||
if err := cl.PrepareWorkspace(cloudprovider.Azure, &vars); err != nil {
|
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +225,7 @@ var (
|
|||||||
caseInsensitiveVersionsRegExp = regexp.MustCompile(`(?i)\/versions\/`)
|
caseInsensitiveVersionsRegExp = regexp.MustCompile(`(?i)\/versions\/`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func normalizeAzureURIs(vars terraform.AzureVariables) terraform.AzureVariables {
|
func normalizeAzureURIs(vars terraform.AzureClusterVariables) terraform.AzureClusterVariables {
|
||||||
vars.UserAssignedIdentity = caseInsensitiveSubscriptionsRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/subscriptions/")
|
vars.UserAssignedIdentity = caseInsensitiveSubscriptionsRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/subscriptions/")
|
||||||
vars.UserAssignedIdentity = caseInsensitiveResourceGroupRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/resourceGroups/")
|
vars.UserAssignedIdentity = caseInsensitiveResourceGroupRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/resourceGroups/")
|
||||||
vars.UserAssignedIdentity = caseInsensitiveProvidersRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/providers/")
|
vars.UserAssignedIdentity = caseInsensitiveProvidersRegexp.ReplaceAllString(vars.UserAssignedIdentity, "/providers/")
|
||||||
@ -305,7 +306,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
|
|||||||
Firmware: config.Provider.QEMU.Firmware,
|
Firmware: config.Provider.QEMU.Firmware,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cl.PrepareWorkspace(cloudprovider.QEMU, &vars); err != nil {
|
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), &vars); err != nil {
|
||||||
return clusterid.File{}, err
|
return clusterid.File{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,43 +139,43 @@ func TestCreator(t *testing.T) {
|
|||||||
|
|
||||||
func TestNormalizeAzureURIs(t *testing.T) {
|
func TestNormalizeAzureURIs(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
in terraform.AzureVariables
|
in terraform.AzureClusterVariables
|
||||||
want terraform.AzureVariables
|
want terraform.AzureClusterVariables
|
||||||
}{
|
}{
|
||||||
"empty": {
|
"empty": {
|
||||||
in: terraform.AzureVariables{},
|
in: terraform.AzureClusterVariables{},
|
||||||
want: terraform.AzureVariables{},
|
want: terraform.AzureClusterVariables{},
|
||||||
},
|
},
|
||||||
"no change": {
|
"no change": {
|
||||||
in: terraform.AzureVariables{
|
in: terraform.AzureClusterVariables{
|
||||||
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
||||||
},
|
},
|
||||||
want: terraform.AzureVariables{
|
want: terraform.AzureClusterVariables{
|
||||||
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"fix image id": {
|
"fix image id": {
|
||||||
in: terraform.AzureVariables{
|
in: terraform.AzureClusterVariables{
|
||||||
ImageID: "/CommunityGalleries/foo/Images/constellation/Versions/2.1.0",
|
ImageID: "/CommunityGalleries/foo/Images/constellation/Versions/2.1.0",
|
||||||
},
|
},
|
||||||
want: terraform.AzureVariables{
|
want: terraform.AzureClusterVariables{
|
||||||
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"fix resource group": {
|
"fix resource group": {
|
||||||
in: terraform.AzureVariables{
|
in: terraform.AzureClusterVariables{
|
||||||
UserAssignedIdentity: "/subscriptions/foo/resourcegroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
UserAssignedIdentity: "/subscriptions/foo/resourcegroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
||||||
},
|
},
|
||||||
want: terraform.AzureVariables{
|
want: terraform.AzureClusterVariables{
|
||||||
UserAssignedIdentity: "/subscriptions/foo/resourceGroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
UserAssignedIdentity: "/subscriptions/foo/resourceGroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"fix arbitrary casing": {
|
"fix arbitrary casing": {
|
||||||
in: terraform.AzureVariables{
|
in: terraform.AzureClusterVariables{
|
||||||
ImageID: "/CoMMUnitygaLLeries/foo/iMAges/constellation/vERsions/2.1.0",
|
ImageID: "/CoMMUnitygaLLeries/foo/iMAges/constellation/vERsions/2.1.0",
|
||||||
UserAssignedIdentity: "/subsCRiptions/foo/resoURCegroups/test/proViDers/MICROsoft.mANAgedIdentity/USerASsignediDENtities/uai",
|
UserAssignedIdentity: "/subsCRiptions/foo/resoURCegroups/test/proViDers/MICROsoft.mANAgedIdentity/USerASsignediDENtities/uai",
|
||||||
},
|
},
|
||||||
want: terraform.AzureVariables{
|
want: terraform.AzureClusterVariables{
|
||||||
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
ImageID: "/communityGalleries/foo/images/constellation/versions/2.1.0",
|
||||||
UserAssignedIdentity: "/subscriptions/foo/resourceGroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
UserAssignedIdentity: "/subscriptions/foo/resourceGroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai",
|
||||||
},
|
},
|
||||||
|
178
cli/internal/cloudcmd/iam.go
Normal file
178
cli/internal/cloudcmd/iam.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cloudcmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IAMCreator creates the IAM configuration on the cloud provider.
|
||||||
|
type IAMCreator struct {
|
||||||
|
out io.Writer
|
||||||
|
newTerraformClient func(ctx context.Context) (terraformClient, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IAMConfig holds the necessary values for IAM configuration.
|
||||||
|
type IAMConfig struct {
|
||||||
|
GCP GCPIAMConfig
|
||||||
|
Azure AzureIAMConfig
|
||||||
|
AWS AWSIAMConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCPIAMConfig holds the necessary values for GCP IAM configuration.
|
||||||
|
type GCPIAMConfig struct {
|
||||||
|
Region string
|
||||||
|
Zone string
|
||||||
|
ProjectID string
|
||||||
|
ServiceAccountID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AzureIAMConfig holds the necessary values for Azure IAM configuration.
|
||||||
|
type AzureIAMConfig struct {
|
||||||
|
Region string
|
||||||
|
ServicePrincipal string
|
||||||
|
ResourceGroup string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSIAMConfig holds the necessary values for AWS IAM configuration.
|
||||||
|
type AWSIAMConfig struct {
|
||||||
|
Region string
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIAMCreator creates a new IAM creator.
|
||||||
|
func NewIAMCreator(out io.Writer) *IAMCreator {
|
||||||
|
return &IAMCreator{
|
||||||
|
out: out,
|
||||||
|
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
||||||
|
return terraform.New(ctx, constants.TerraformIAMWorkingDir)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create prepares and hands over the corresponding providers IAM creator.
|
||||||
|
func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, iamConfig *IAMConfig) (iamid.File, error) {
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
cl, err := c.newTerraformClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
defer cl.RemoveInstaller()
|
||||||
|
return c.createGCP(ctx, cl, iamConfig)
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
cl, err := c.newTerraformClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
defer cl.RemoveInstaller()
|
||||||
|
return c.createAzure(ctx, cl, iamConfig)
|
||||||
|
case cloudprovider.AWS:
|
||||||
|
cl, err := c.newTerraformClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
defer cl.RemoveInstaller()
|
||||||
|
return c.createAWS(ctx, cl, iamConfig)
|
||||||
|
default:
|
||||||
|
return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGCP creates the IAM configuration on GCP.
|
||||||
|
func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
|
||||||
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||||
|
|
||||||
|
vars := terraform.GCPIAMVariables{
|
||||||
|
ServiceAccountID: iamConfig.GCP.ServiceAccountID,
|
||||||
|
Project: iamConfig.GCP.ProjectID,
|
||||||
|
Region: iamConfig.GCP.Region,
|
||||||
|
Zone: iamConfig.GCP.Zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.GCP)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: iamOutput.GCP.SaKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createAzure creates the IAM configuration on Azure.
|
||||||
|
func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
|
||||||
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||||
|
|
||||||
|
vars := terraform.AzureIAMVariables{
|
||||||
|
Region: iamConfig.Azure.Region,
|
||||||
|
ResourceGroup: iamConfig.Azure.ResourceGroup,
|
||||||
|
ServicePrincipal: iamConfig.Azure.ServicePrincipal,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.Azure)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iamid.File{
|
||||||
|
CloudProvider: cloudprovider.Azure,
|
||||||
|
AzureOutput: iamid.AzureFile{
|
||||||
|
ApplicationID: iamOutput.Azure.ApplicationID,
|
||||||
|
ApplicationClientSecretValue: iamOutput.Azure.ApplicationClientSecretValue,
|
||||||
|
SubscriptionID: iamOutput.Azure.SubscriptionID,
|
||||||
|
TenantID: iamOutput.Azure.TenantID,
|
||||||
|
UAMIID: iamOutput.Azure.UAMIID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createAWS creates the IAM configuration on AWS.
|
||||||
|
func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
|
||||||
|
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||||
|
|
||||||
|
vars := terraform.AWSIAMVariables{
|
||||||
|
Region: iamConfig.AWS.Region,
|
||||||
|
Prefix: iamConfig.AWS.Prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.AWS)
|
||||||
|
if err != nil {
|
||||||
|
return iamid.File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iamid.File{
|
||||||
|
CloudProvider: cloudprovider.AWS,
|
||||||
|
AWSOutput: iamid.AWSFile{
|
||||||
|
WorkerNodeInstanceProfile: iamOutput.AWS.WorkerNodeInstanceProfile,
|
||||||
|
ControlPlaneInstanceProfile: iamOutput.AWS.ControlPlaneInstanceProfile,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
150
cli/internal/cloudcmd/iam_test.go
Normal file
150
cli/internal/cloudcmd/iam_test.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cloudcmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIAMCreator(t *testing.T) {
|
||||||
|
someErr := errors.New("failed")
|
||||||
|
|
||||||
|
validGCPIAMConfig := GCPIAMConfig{
|
||||||
|
Region: "europe-west1",
|
||||||
|
Zone: "europe-west1-a",
|
||||||
|
ProjectID: "project-1234",
|
||||||
|
ServiceAccountID: "const-test",
|
||||||
|
}
|
||||||
|
validGCPIAMOutput := terraform.IAMOutput{
|
||||||
|
GCP: terraform.GCPIAMOutput{
|
||||||
|
SaKey: "not_a_secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
validGCPIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: "not_a_secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validAzureIAMConfig := AzureIAMConfig{
|
||||||
|
Region: "westus",
|
||||||
|
ServicePrincipal: "constell-test",
|
||||||
|
ResourceGroup: "constell-test",
|
||||||
|
}
|
||||||
|
validAzureIAMOutput := terraform.IAMOutput{
|
||||||
|
Azure: terraform.AzureIAMOutput{
|
||||||
|
SubscriptionID: "test_subscription_id",
|
||||||
|
TenantID: "test_tenant_id",
|
||||||
|
ApplicationID: "test_application_id",
|
||||||
|
ApplicationClientSecretValue: "test_application_client_secret_value",
|
||||||
|
UAMIID: "test_uami_id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
validAzureIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.Azure,
|
||||||
|
AzureOutput: iamid.AzureFile{
|
||||||
|
SubscriptionID: "test_subscription_id",
|
||||||
|
TenantID: "test_tenant_id",
|
||||||
|
ApplicationID: "test_application_id",
|
||||||
|
ApplicationClientSecretValue: "test_application_client_secret_value",
|
||||||
|
UAMIID: "test_uami_id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validAWSIAMConfig := AWSIAMConfig{
|
||||||
|
Region: "us-east-2",
|
||||||
|
Prefix: "test",
|
||||||
|
}
|
||||||
|
validAWSIAMOutput := terraform.IAMOutput{
|
||||||
|
AWS: terraform.AWSIAMOutput{
|
||||||
|
WorkerNodeInstanceProfile: "test_worker_node_instance_profile",
|
||||||
|
ControlPlaneInstanceProfile: "test_control_plane_instance_profile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
validAWSIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.AWS,
|
||||||
|
AWSOutput: iamid.AWSFile{
|
||||||
|
ControlPlaneInstanceProfile: "test_control_plane_instance_profile",
|
||||||
|
WorkerNodeInstanceProfile: "test_worker_node_instance_profile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
tfClient terraformClient
|
||||||
|
newTfClientErr error
|
||||||
|
config *IAMConfig
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
wantIAMIDFile iamid.File
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"new terraform client err": {
|
||||||
|
tfClient: &stubTerraformClient{},
|
||||||
|
newTfClientErr: someErr,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"create iam config err": {
|
||||||
|
tfClient: &stubTerraformClient{iamOutputErr: someErr},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp": {
|
||||||
|
tfClient: &stubTerraformClient{iamOutput: validGCPIAMOutput},
|
||||||
|
wantIAMIDFile: validGCPIAMIDFile,
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
config: &IAMConfig{GCP: validGCPIAMConfig},
|
||||||
|
},
|
||||||
|
"azure": {
|
||||||
|
tfClient: &stubTerraformClient{iamOutput: validAzureIAMOutput},
|
||||||
|
wantIAMIDFile: validAzureIAMIDFile,
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
config: &IAMConfig{Azure: validAzureIAMConfig},
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
tfClient: &stubTerraformClient{iamOutput: validAWSIAMOutput},
|
||||||
|
wantIAMIDFile: validAWSIAMIDFile,
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
config: &IAMConfig{AWS: validAWSIAMConfig},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
creator := &IAMCreator{
|
||||||
|
out: &bytes.Buffer{},
|
||||||
|
newTerraformClient: func(ctx context.Context) (terraformClient, error) {
|
||||||
|
return tc.tfClient, tc.newTfClientErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
idFile, err := creator.Create(context.Background(), tc.provider, tc.config)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.provider, idFile.CloudProvider)
|
||||||
|
switch tc.provider {
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
assert.Equal(tc.wantIAMIDFile.GCPOutput, idFile.GCPOutput)
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
assert.Equal(tc.wantIAMIDFile.AzureOutput, idFile.AzureOutput)
|
||||||
|
case cloudprovider.AWS:
|
||||||
|
assert.Equal(tc.wantIAMIDFile.AWSOutput, idFile.AWSOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
)
|
)
|
||||||
@ -24,6 +26,14 @@ type cloudCreator interface {
|
|||||||
) (clusterid.File, error)
|
) (clusterid.File, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type iamCreator interface {
|
||||||
|
Create(
|
||||||
|
ctx context.Context,
|
||||||
|
provider cloudprovider.Provider,
|
||||||
|
iamConfig *cloudcmd.IAMConfig,
|
||||||
|
) (iamid.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
type cloudTerminator interface {
|
type cloudTerminator interface {
|
||||||
Terminate(context.Context) error
|
Terminate(context.Context) error
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"go.uber.org/goleak"
|
"go.uber.org/goleak"
|
||||||
@ -54,3 +56,19 @@ func (c *stubCloudTerminator) Terminate(context.Context) error {
|
|||||||
func (c *stubCloudTerminator) Called() bool {
|
func (c *stubCloudTerminator) Called() bool {
|
||||||
return c.called
|
return c.called
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubIAMCreator struct {
|
||||||
|
createCalled bool
|
||||||
|
id iamid.File
|
||||||
|
createErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *stubIAMCreator) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
provider cloudprovider.Provider,
|
||||||
|
iamConfig *cloudcmd.IAMConfig,
|
||||||
|
) (iamid.File, error) {
|
||||||
|
c.createCalled = true
|
||||||
|
c.id.CloudProvider = provider
|
||||||
|
return c.id, c.createErr
|
||||||
|
}
|
||||||
|
@ -27,10 +27,8 @@ func NewCreateCmd() *cobra.Command {
|
|||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "Create instances on a cloud platform for your Constellation cluster",
|
Short: "Create instances on a cloud platform for your Constellation cluster",
|
||||||
Long: "Create instances on a cloud platform for your Constellation cluster.",
|
Long: "Create instances on a cloud platform for your Constellation cluster.",
|
||||||
Args: cobra.MatchAll(
|
Args: cobra.ExactArgs(0),
|
||||||
cobra.ExactArgs(0),
|
RunE: runCreate,
|
||||||
),
|
|
||||||
RunE: runCreate,
|
|
||||||
}
|
}
|
||||||
cmd.Flags().String("name", "constell", "create the cluster with the specified name")
|
cmd.Flags().String("name", "constell", "create the cluster with the specified name")
|
||||||
cmd.Flags().BoolP("yes", "y", false, "create the cluster without further confirmation")
|
cmd.Flags().BoolP("yes", "y", false, "create the cluster without further confirmation")
|
||||||
|
40
cli/internal/cmd/iam.go
Normal file
40
cli/internal/cmd/iam.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewIAMCmd returns a new cobra.Command for the iam parent command. It needs another verb and does nothing on its own.
|
||||||
|
func NewIAMCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "iam",
|
||||||
|
Short: "Work with the IAM configuration on your cloud provider",
|
||||||
|
Long: "Work with the IAM configuration on your cloud provider.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(newIAMCreateCmd())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIAMCreateCmd returns a new cobra.Command for the iam create parent command. It needs another verb, and does nothing on its own.
|
||||||
|
func newIAMCreateCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create IAM configuration on a cloud platform for your Constellation cluster",
|
||||||
|
Long: "Create IAM configuration on a cloud platform for your Constellation cluster.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(newIAMCreateAWSCmd())
|
||||||
|
cmd.AddCommand(newIAMCreateAzureCmd())
|
||||||
|
cmd.AddCommand(newIAMCreateGCPCmd())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
128
cli/internal/cmd/iamcreateaws.go
Normal file
128
cli/internal/cmd/iamcreateaws.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newIAMCreateAWSCmd returns a new cobra.Command for the iam create aws command.
|
||||||
|
func newIAMCreateAWSCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "aws",
|
||||||
|
Short: "Create IAM configuration on AWS for your Constellation cluster",
|
||||||
|
Long: "Create IAM configuration on AWS for your Constellation cluster.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runIAMCreateAWS,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().String("prefix", "", "Name prefix for all resources.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "prefix"))
|
||||||
|
cmd.Flags().String("zone", "", "AWS availability zone the resources will be created in (e.g. us-east-2a). Find available zones here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones. Note that we do not support every zone / region. You can find a list of all supported regions in our docs.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "zone"))
|
||||||
|
cmd.Flags().Bool("yes", false, "Create the IAM configuration without further confirmation")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIAMCreateAWS(cmd *cobra.Command, args []string) error {
|
||||||
|
spinner := newSpinner(cmd.ErrOrStderr())
|
||||||
|
defer spinner.Stop()
|
||||||
|
creator := cloudcmd.NewIAMCreator(spinner)
|
||||||
|
|
||||||
|
return iamCreateAWS(cmd, spinner, creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iamCreateAWS(cmd *cobra.Command, spinner spinnerInterf, creator iamCreator) error {
|
||||||
|
// Get input variables.
|
||||||
|
awsFlags, err := parseAWSFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmation.
|
||||||
|
if !awsFlags.yesFlag {
|
||||||
|
cmd.Printf("The following IAM configuration will be created:\n")
|
||||||
|
cmd.Printf("Region:\t%s\n", awsFlags.region)
|
||||||
|
cmd.Printf("Name Prefix:\t%s\n", awsFlags.prefix)
|
||||||
|
ok, err := askToConfirm(cmd, "Do you want to create the configuration?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
cmd.Println("The creation of the configuration was aborted.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation.
|
||||||
|
spinner.Start("Creating", false)
|
||||||
|
iamFile, err := creator.Create(cmd.Context(), cloudprovider.AWS, &cloudcmd.IAMConfig{
|
||||||
|
AWS: cloudcmd.AWSIAMConfig{
|
||||||
|
Region: awsFlags.region,
|
||||||
|
Prefix: awsFlags.prefix,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
spinner.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Printf("region:\t%s\n", awsFlags.region)
|
||||||
|
cmd.Printf("zone:\t%s\n", awsFlags.zone)
|
||||||
|
cmd.Printf("iamProfileControlPlane:\t%s\n", iamFile.AWSOutput.ControlPlaneInstanceProfile)
|
||||||
|
cmd.Printf("iamProfileWorkerNodes:\t%s\n", iamFile.AWSOutput.WorkerNodeInstanceProfile)
|
||||||
|
cmd.Println("Your IAM configuration was created successfully. Please fill the above values into your configuration file.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAWSFlags parses and validates the flags of the iam create aws command.
|
||||||
|
func parseAWSFlags(cmd *cobra.Command) (awsFlags, error) {
|
||||||
|
var region string
|
||||||
|
|
||||||
|
prefix, err := cmd.Flags().GetString("prefix")
|
||||||
|
if err != nil {
|
||||||
|
return awsFlags{}, fmt.Errorf("parsing prefix string: %w", err)
|
||||||
|
}
|
||||||
|
zone, err := cmd.Flags().GetString("zone")
|
||||||
|
if err != nil {
|
||||||
|
return awsFlags{}, fmt.Errorf("parsing zone string: %w", err)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(zone, "eu-central-1") {
|
||||||
|
region = "eu-central-1"
|
||||||
|
} else if strings.HasPrefix(zone, "us-east-2") {
|
||||||
|
region = "us-east-2"
|
||||||
|
} else if strings.HasPrefix(zone, "ap-south-1") {
|
||||||
|
region = "ap-south-1"
|
||||||
|
} else {
|
||||||
|
return awsFlags{}, fmt.Errorf("invalid AWS region, to find a correct region please refer to our docs and https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones")
|
||||||
|
}
|
||||||
|
|
||||||
|
yesFlag, err := cmd.Flags().GetBool("yes")
|
||||||
|
if err != nil {
|
||||||
|
return awsFlags{}, fmt.Errorf("parsing yes bool: %w", err)
|
||||||
|
}
|
||||||
|
return awsFlags{
|
||||||
|
zone: zone,
|
||||||
|
prefix: prefix,
|
||||||
|
region: region,
|
||||||
|
yesFlag: yesFlag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// awsFlags contains the parsed flags of the iam create aws command.
|
||||||
|
type awsFlags struct {
|
||||||
|
prefix string
|
||||||
|
region string
|
||||||
|
zone string
|
||||||
|
yesFlag bool
|
||||||
|
}
|
118
cli/internal/cmd/iamcreateaws_test.go
Normal file
118
cli/internal/cmd/iamcreateaws_test.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIAMCreateAWS(t *testing.T) {
|
||||||
|
fsWithDefaultConfig := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file := file.NewHandler(fs)
|
||||||
|
require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider)))
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
validIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.AWS,
|
||||||
|
AWSOutput: iamid.AWSFile{
|
||||||
|
ControlPlaneInstanceProfile: "test_control_plane_instance_profile",
|
||||||
|
WorkerNodeInstanceProfile: "test_worker_nodes_instance_profile",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
setupFs func(*require.Assertions, cloudprovider.Provider) afero.Fs
|
||||||
|
creator *stubIAMCreator
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
zoneFlag string
|
||||||
|
prefixFlag string
|
||||||
|
yesFlag bool
|
||||||
|
stdin string
|
||||||
|
wantAbort bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"iam create aws": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
zoneFlag: "us-east-2a",
|
||||||
|
prefixFlag: "test",
|
||||||
|
yesFlag: true,
|
||||||
|
},
|
||||||
|
"interactive": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
zoneFlag: "us-east-2a",
|
||||||
|
prefixFlag: "test",
|
||||||
|
stdin: "yes\n",
|
||||||
|
},
|
||||||
|
"interactive abort": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
zoneFlag: "us-east-2a",
|
||||||
|
prefixFlag: "test",
|
||||||
|
stdin: "no\n",
|
||||||
|
wantAbort: true,
|
||||||
|
},
|
||||||
|
"invalid zone": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
zoneFlag: "us-west-5b",
|
||||||
|
prefixFlag: "test",
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
cmd := newIAMCreateAWSCmd()
|
||||||
|
cmd.SetOut(&bytes.Buffer{})
|
||||||
|
cmd.SetErr(&bytes.Buffer{})
|
||||||
|
cmd.SetIn(bytes.NewBufferString(tc.stdin))
|
||||||
|
if tc.zoneFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
|
||||||
|
}
|
||||||
|
if tc.prefixFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("prefix", tc.prefixFlag))
|
||||||
|
}
|
||||||
|
if tc.yesFlag {
|
||||||
|
require.NoError(cmd.Flags().Set("yes", "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := iamCreateAWS(cmd, nopSpinner{}, tc.creator)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
if tc.wantAbort {
|
||||||
|
assert.False(tc.creator.createCalled)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.True(tc.creator.createCalled)
|
||||||
|
assert.Equal(tc.creator.id.AWSOutput, validIAMIDFile.AWSOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
126
cli/internal/cmd/iamcreateazure.go
Normal file
126
cli/internal/cmd/iamcreateazure.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newIAMCreateAzureCmd returns a new cobra.Command for the iam create azure command.
|
||||||
|
func newIAMCreateAzureCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "azure",
|
||||||
|
Short: "Create IAM configuration on Microsoft Azure for your Constellation cluster",
|
||||||
|
Long: "Create IAM configuration on Microsoft Azure for your Constellation cluster.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runIAMCreateAzure,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().String("resourceGroup", "", "Name of the resource group your IAM resources will be created in.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "resourceGroup"))
|
||||||
|
cmd.Flags().String("region", "", "Region the resources will be created in. (e.g. westus)")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "region"))
|
||||||
|
cmd.Flags().String("servicePrincipal", "", "Name of the service principal that will be created.")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "servicePrincipal"))
|
||||||
|
cmd.Flags().Bool("yes", false, "Create the IAM configuration without further confirmation")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIAMCreateAzure(cmd *cobra.Command, args []string) error {
|
||||||
|
spinner := newSpinner(cmd.ErrOrStderr())
|
||||||
|
defer spinner.Stop()
|
||||||
|
creator := cloudcmd.NewIAMCreator(spinner)
|
||||||
|
|
||||||
|
return iamCreateAzure(cmd, spinner, creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iamCreateAzure(cmd *cobra.Command, spinner spinnerInterf, creator iamCreator) error {
|
||||||
|
// Get input variables.
|
||||||
|
azureFlags, err := parseAzureFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmation.
|
||||||
|
if !azureFlags.yesFlag {
|
||||||
|
cmd.Printf("The following IAM configuration will be created:\n")
|
||||||
|
cmd.Printf("Region:\t%s\n", azureFlags.region)
|
||||||
|
cmd.Printf("Resource Group:\t%s\n", azureFlags.resourceGroup)
|
||||||
|
cmd.Printf("Service Principal:\t%s\n", azureFlags.servicePrincipal)
|
||||||
|
ok, err := askToConfirm(cmd, "Do you want to create the configuration?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
cmd.Println("The creation of the configuration was aborted.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation.
|
||||||
|
spinner.Start("Creating", false)
|
||||||
|
iamFile, err := creator.Create(cmd.Context(), cloudprovider.Azure, &cloudcmd.IAMConfig{
|
||||||
|
Azure: cloudcmd.AzureIAMConfig{
|
||||||
|
Region: azureFlags.region,
|
||||||
|
ServicePrincipal: azureFlags.servicePrincipal,
|
||||||
|
ResourceGroup: azureFlags.resourceGroup,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
spinner.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Printf("subscription:\t%s\n", iamFile.AzureOutput.SubscriptionID)
|
||||||
|
cmd.Printf("tenant:\t%s\n", iamFile.AzureOutput.TenantID)
|
||||||
|
cmd.Printf("location:\t%s\n", azureFlags.region)
|
||||||
|
cmd.Printf("resourceGroup:\t%s\n", azureFlags.resourceGroup)
|
||||||
|
cmd.Printf("userAssignedIdentity:\t%s\n", iamFile.AzureOutput.UAMIID)
|
||||||
|
cmd.Printf("appClientID:\t%s\n", iamFile.AzureOutput.ApplicationID)
|
||||||
|
cmd.Printf("appClientSecretValue:\t%s\n", iamFile.AzureOutput.ApplicationClientSecretValue)
|
||||||
|
cmd.Println("Your IAM configuration was created successfully. Please fill the above values into your configuration file.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAzureFlags parses and validates the flags of the iam create azure command.
|
||||||
|
func parseAzureFlags(cmd *cobra.Command) (azureFlags, error) {
|
||||||
|
region, err := cmd.Flags().GetString("region")
|
||||||
|
if err != nil {
|
||||||
|
return azureFlags{}, fmt.Errorf("parsing region string: %w", err)
|
||||||
|
}
|
||||||
|
resourceGroup, err := cmd.Flags().GetString("resourceGroup")
|
||||||
|
if err != nil {
|
||||||
|
return azureFlags{}, fmt.Errorf("parsing resourceGroup string: %w", err)
|
||||||
|
}
|
||||||
|
servicePrincipal, err := cmd.Flags().GetString("servicePrincipal")
|
||||||
|
if err != nil {
|
||||||
|
return azureFlags{}, fmt.Errorf("parsing servicePrincipal string: %w", err)
|
||||||
|
}
|
||||||
|
yesFlag, err := cmd.Flags().GetBool("yes")
|
||||||
|
if err != nil {
|
||||||
|
return azureFlags{}, fmt.Errorf("parsing yes bool: %w", err)
|
||||||
|
}
|
||||||
|
return azureFlags{
|
||||||
|
servicePrincipal: servicePrincipal,
|
||||||
|
resourceGroup: resourceGroup,
|
||||||
|
region: region,
|
||||||
|
yesFlag: yesFlag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// azureFlags contains the parsed flags of the iam create azure command.
|
||||||
|
type azureFlags struct {
|
||||||
|
region string
|
||||||
|
resourceGroup string
|
||||||
|
servicePrincipal string
|
||||||
|
yesFlag bool
|
||||||
|
}
|
119
cli/internal/cmd/iamcreateazure_test.go
Normal file
119
cli/internal/cmd/iamcreateazure_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIAMCreateAzure(t *testing.T) {
|
||||||
|
fsWithDefaultConfig := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file := file.NewHandler(fs)
|
||||||
|
require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider)))
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
validIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.Azure,
|
||||||
|
AzureOutput: iamid.AzureFile{
|
||||||
|
SubscriptionID: "test_subscription_id",
|
||||||
|
TenantID: "test_tenant_id",
|
||||||
|
ApplicationID: "test_application_id",
|
||||||
|
ApplicationClientSecretValue: "test_application_client_secret_value",
|
||||||
|
UAMIID: "test_uami_id",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
setupFs func(*require.Assertions, cloudprovider.Provider) afero.Fs
|
||||||
|
creator *stubIAMCreator
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
regionFlag string
|
||||||
|
servicePrincipalFlag string
|
||||||
|
resourceGroupFlag string
|
||||||
|
yesFlag bool
|
||||||
|
stdin string
|
||||||
|
wantAbort bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"iam create azure": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
regionFlag: "westus",
|
||||||
|
servicePrincipalFlag: "constell-test-sp",
|
||||||
|
resourceGroupFlag: "constell-test-rg",
|
||||||
|
yesFlag: true,
|
||||||
|
},
|
||||||
|
"interactive": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
regionFlag: "westus",
|
||||||
|
servicePrincipalFlag: "constell-test-sp",
|
||||||
|
resourceGroupFlag: "constell-test-rg",
|
||||||
|
stdin: "yes\n",
|
||||||
|
},
|
||||||
|
"interactive abort": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
regionFlag: "westus",
|
||||||
|
servicePrincipalFlag: "constell-test-sp",
|
||||||
|
resourceGroupFlag: "constell-test-rg",
|
||||||
|
stdin: "no\n",
|
||||||
|
wantAbort: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
cmd := newIAMCreateAzureCmd()
|
||||||
|
cmd.SetOut(&bytes.Buffer{})
|
||||||
|
cmd.SetErr(&bytes.Buffer{})
|
||||||
|
cmd.SetIn(bytes.NewBufferString(tc.stdin))
|
||||||
|
if tc.regionFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("region", tc.regionFlag))
|
||||||
|
}
|
||||||
|
if tc.resourceGroupFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("resourceGroup", tc.resourceGroupFlag))
|
||||||
|
}
|
||||||
|
if tc.servicePrincipalFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("servicePrincipal", tc.servicePrincipalFlag))
|
||||||
|
}
|
||||||
|
if tc.yesFlag {
|
||||||
|
require.NoError(cmd.Flags().Set("yes", "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := iamCreateAzure(cmd, nopSpinner{}, tc.creator)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
if tc.wantAbort {
|
||||||
|
assert.False(tc.creator.createCalled)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.True(tc.creator.createCalled)
|
||||||
|
assert.Equal(tc.creator.id.AzureOutput, validIAMIDFile.AzureOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
183
cli/internal/cmd/iamcreategcp.go
Normal file
183
cli/internal/cmd/iamcreategcp.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
zoneRegex = regexp.MustCompile(`^\w+-\w+-[abc]$`)
|
||||||
|
regionRegex = regexp.MustCompile(`^\w+-\w+[0-9]$`)
|
||||||
|
projectIDRegex = regexp.MustCompile(`^[a-z][-a-z0-9]{4,28}[a-z0-9]{1}$`)
|
||||||
|
serviceAccIDRegex = regexp.MustCompile(`^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewIAMCreateGCPCmd returns a new cobra.Command for the iam create gcp command.
|
||||||
|
func newIAMCreateGCPCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "gcp",
|
||||||
|
Short: "Create IAM configuration on GCP for your Constellation cluster",
|
||||||
|
Long: "Create IAM configuration on GCP for your Constellation cluster.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runIAMCreateGCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().String("zone", "", "GCP zone the cluster will be deployed in. Find a list of available zones here: https://cloud.google.com/compute/docs/regions-zones#available")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "zone"))
|
||||||
|
cmd.Flags().String("serviceAccountID", "", "ID for the service account that will be created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "serviceAccountID"))
|
||||||
|
cmd.Flags().String("projectID", "", "ID of the GCP project the configuration will be created in. Find it on the welcome screen of your project: https://console.cloud.google.com/welcome")
|
||||||
|
must(cobra.MarkFlagRequired(cmd.Flags(), "projectID"))
|
||||||
|
cmd.Flags().Bool("yes", false, "Create the IAM configuration without further confirmation")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIAMCreateGCP(cmd *cobra.Command, args []string) error {
|
||||||
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
|
spinner := newSpinner(cmd.ErrOrStderr())
|
||||||
|
defer spinner.Stop()
|
||||||
|
creator := cloudcmd.NewIAMCreator(spinner)
|
||||||
|
|
||||||
|
return iamCreateGCP(cmd, spinner, fileHandler, creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iamCreateGCP(cmd *cobra.Command, spinner spinnerInterf, fileHandler file.Handler, creator iamCreator) error {
|
||||||
|
// Get input variables.
|
||||||
|
gcpFlags, err := parseGCPFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmation.
|
||||||
|
if !gcpFlags.yesFlag {
|
||||||
|
cmd.Printf("The following IAM configuration will be created:\n")
|
||||||
|
cmd.Printf("Project ID:\t%s\n", gcpFlags.projectID)
|
||||||
|
cmd.Printf("Service Account ID:\t%s\n", gcpFlags.serviceAccountID)
|
||||||
|
cmd.Printf("Region:\t%s\n", gcpFlags.region)
|
||||||
|
cmd.Printf("Zone:\t%s\n", gcpFlags.zone)
|
||||||
|
ok, err := askToConfirm(cmd, "Do you want to create the configuration?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
cmd.Println("The creation of the configuration was aborted.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation.
|
||||||
|
spinner.Start("Creating", false)
|
||||||
|
iamFile, err := creator.Create(cmd.Context(), cloudprovider.GCP, &cloudcmd.IAMConfig{
|
||||||
|
GCP: cloudcmd.GCPIAMConfig{
|
||||||
|
ServiceAccountID: gcpFlags.serviceAccountID,
|
||||||
|
Region: gcpFlags.region,
|
||||||
|
Zone: gcpFlags.zone,
|
||||||
|
ProjectID: gcpFlags.projectID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
spinner.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back values.
|
||||||
|
tmpOut, err := parseIDFile(iamFile.GCPOutput.ServiceAccountKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fileHandler.WriteJSON(constants.GCPServiceAccountKeyFile, tmpOut, file.OptNone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println(fmt.Sprintf("serviceAccountKeyPath:\t%s", constants.GCPServiceAccountKeyFile))
|
||||||
|
cmd.Println("Your IAM configuration was created successfully. Please fill the above values into your configuration file.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIDFile(serviceAccountKeyBase64 string) (map[string]string, error) {
|
||||||
|
dec, err := base64.StdEncoding.DecodeString(serviceAccountKeyBase64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]string)
|
||||||
|
if err = json.Unmarshal(dec, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGCPFlags parses and validates the flags of the iam create gcp command.
|
||||||
|
func parseGCPFlags(cmd *cobra.Command) (gcpFlags, error) {
|
||||||
|
zone, err := cmd.Flags().GetString("zone")
|
||||||
|
if err != nil {
|
||||||
|
return gcpFlags{}, fmt.Errorf("parsing zone string: %w", err)
|
||||||
|
}
|
||||||
|
if !zoneRegex.MatchString(zone) {
|
||||||
|
return gcpFlags{}, fmt.Errorf("invalid zone string: %s", zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer region from zone.
|
||||||
|
zoneParts := strings.Split(zone, "-")
|
||||||
|
region := fmt.Sprintf("%s-%s", zoneParts[0], zoneParts[1])
|
||||||
|
if !regionRegex.MatchString(region) {
|
||||||
|
return gcpFlags{}, fmt.Errorf("invalid region string: %s", region)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectID, err := cmd.Flags().GetString("projectID")
|
||||||
|
if err != nil {
|
||||||
|
return gcpFlags{}, fmt.Errorf("parsing projectID string: %w", err)
|
||||||
|
}
|
||||||
|
// Source for regex: https://cloud.google.com/resource-manager/reference/rest/v1/projects.
|
||||||
|
if !projectIDRegex.MatchString(projectID) {
|
||||||
|
return gcpFlags{}, fmt.Errorf("invalid projectID string: %s", projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceAccID, err := cmd.Flags().GetString("serviceAccountID")
|
||||||
|
if err != nil {
|
||||||
|
return gcpFlags{}, fmt.Errorf("parsing serviceAccountID string: %w", err)
|
||||||
|
}
|
||||||
|
if !serviceAccIDRegex.MatchString(serviceAccID) {
|
||||||
|
return gcpFlags{}, fmt.Errorf("invalid serviceAccountID string: %s", serviceAccID)
|
||||||
|
}
|
||||||
|
|
||||||
|
yesFlag, err := cmd.Flags().GetBool("yes")
|
||||||
|
if err != nil {
|
||||||
|
return gcpFlags{}, fmt.Errorf("parsing yes bool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gcpFlags{
|
||||||
|
serviceAccountID: serviceAccID,
|
||||||
|
zone: zone,
|
||||||
|
region: region,
|
||||||
|
projectID: projectID,
|
||||||
|
yesFlag: yesFlag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcpFlags contains the parsed flags of the iam create gcp command.
|
||||||
|
type gcpFlags struct {
|
||||||
|
serviceAccountID string
|
||||||
|
zone string
|
||||||
|
region string
|
||||||
|
projectID string
|
||||||
|
yesFlag bool
|
||||||
|
}
|
184
cli/internal/cmd/iamcreategcp_test.go
Normal file
184
cli/internal/cmd/iamcreategcp_test.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIAMCreateGCP(t *testing.T) {
|
||||||
|
fsWithDefaultConfig := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
file := file.NewHandler(fs)
|
||||||
|
require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider)))
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
validIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: "eyJwcml2YXRlX2tleV9pZCI6Im5vdF9hX3NlY3JldCJ9Cg==", // {"private_key_id":"not_a_secret"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
invalidIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: "ey_Jwcml2YXRlX2tleV9pZCI6Im5vdF9hX3NlY3JldCJ9Cg==", // invalid b64
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
setupFs func(*require.Assertions, cloudprovider.Provider) afero.Fs
|
||||||
|
creator *stubIAMCreator
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
zoneFlag string
|
||||||
|
serviceAccountIDFlag string
|
||||||
|
projectIDFlag string
|
||||||
|
yesFlag bool
|
||||||
|
stdin string
|
||||||
|
wantAbort bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"iam create gcp": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
zoneFlag: "europe-west1-a",
|
||||||
|
serviceAccountIDFlag: "constell-test",
|
||||||
|
projectIDFlag: "constell-1234",
|
||||||
|
yesFlag: true,
|
||||||
|
},
|
||||||
|
"iam create gcp invalid flags": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
zoneFlag: "-a",
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"iam create gcp invalid b64": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: invalidIAMIDFile},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
zoneFlag: "europe-west1-a",
|
||||||
|
serviceAccountIDFlag: "constell-test",
|
||||||
|
projectIDFlag: "constell-1234",
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"interactive": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
zoneFlag: "europe-west1-a",
|
||||||
|
serviceAccountIDFlag: "constell-test",
|
||||||
|
projectIDFlag: "constell-1234",
|
||||||
|
stdin: "yes\n",
|
||||||
|
},
|
||||||
|
"interactive abort": {
|
||||||
|
setupFs: fsWithDefaultConfig,
|
||||||
|
creator: &stubIAMCreator{id: validIAMIDFile},
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
zoneFlag: "europe-west1-a",
|
||||||
|
serviceAccountIDFlag: "constell-test",
|
||||||
|
projectIDFlag: "constell-1234",
|
||||||
|
stdin: "no\n",
|
||||||
|
wantAbort: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
cmd := newIAMCreateGCPCmd()
|
||||||
|
cmd.SetOut(&bytes.Buffer{})
|
||||||
|
cmd.SetErr(&bytes.Buffer{})
|
||||||
|
cmd.SetIn(bytes.NewBufferString(tc.stdin))
|
||||||
|
if tc.zoneFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
|
||||||
|
}
|
||||||
|
if tc.serviceAccountIDFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("serviceAccountID", tc.serviceAccountIDFlag))
|
||||||
|
}
|
||||||
|
if tc.projectIDFlag != "" {
|
||||||
|
require.NoError(cmd.Flags().Set("projectID", tc.projectIDFlag))
|
||||||
|
}
|
||||||
|
if tc.yesFlag {
|
||||||
|
require.NoError(cmd.Flags().Set("yes", "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileHandler := file.NewHandler(tc.setupFs(require, tc.provider))
|
||||||
|
|
||||||
|
err := iamCreateGCP(cmd, nopSpinner{}, fileHandler, tc.creator)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
if tc.wantAbort {
|
||||||
|
assert.False(tc.creator.createCalled)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.True(tc.creator.createCalled)
|
||||||
|
assert.Equal(tc.creator.id.GCPOutput, validIAMIDFile.GCPOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIDFile(t *testing.T) {
|
||||||
|
validIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: "eyJwcml2YXRlX2tleV9pZCI6Im5vdF9hX3NlY3JldCJ9Cg==", // {"private_key_id":"not_a_secret"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
invalidIAMIDFile := iamid.File{
|
||||||
|
CloudProvider: cloudprovider.GCP,
|
||||||
|
GCPOutput: iamid.GCPFile{
|
||||||
|
ServiceAccountKey: "ey_Jwcml2YXRlX2tleV9pZCI6Im5vdF9hX3NlY3JldCJ9Cg==", // invalid b64
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testCases := map[string]struct {
|
||||||
|
idFile iamid.File
|
||||||
|
wantPrivateKeyID string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"valid base64": {
|
||||||
|
idFile: validIAMIDFile,
|
||||||
|
wantPrivateKeyID: "not_a_secret",
|
||||||
|
},
|
||||||
|
"invalid base64": {
|
||||||
|
idFile: invalidIAMIDFile,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
outMap, err := parseIDFile(tc.idFile.GCPOutput.ServiceAccountKey)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantPrivateKeyID, outMap["private_key_id"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -38,9 +38,7 @@ func NewVerifyCmd() *cobra.Command {
|
|||||||
Long: `Verify the confidential properties of a Constellation cluster.
|
Long: `Verify the confidential properties of a Constellation cluster.
|
||||||
|
|
||||||
If arguments aren't specified, values are read from ` + "`" + constants.ClusterIDsFileName + "`.",
|
If arguments aren't specified, values are read from ` + "`" + constants.ClusterIDsFileName + "`.",
|
||||||
Args: cobra.MatchAll(
|
Args: cobra.ExactArgs(0),
|
||||||
cobra.ExactArgs(0),
|
|
||||||
),
|
|
||||||
RunE: runVerify,
|
RunE: runVerify,
|
||||||
}
|
}
|
||||||
cmd.Flags().String("cluster-id", "", "expected cluster identifier")
|
cmd.Flags().String("cluster-id", "", "expected cluster identifier")
|
||||||
|
42
cli/internal/iamid/id.go
Normal file
42
cli/internal/iamid/id.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package iamid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File contains output information of an IAM configuration.
|
||||||
|
type File struct {
|
||||||
|
// CloudProvider is the cloud provider of the cluster.
|
||||||
|
CloudProvider cloudprovider.Provider `json:"cloudprovider,omitempty"`
|
||||||
|
|
||||||
|
GCPOutput GCPFile `json:"gcpOutput,omitempty"`
|
||||||
|
|
||||||
|
AzureOutput AzureFile `json:"azureOutput,omitempty"`
|
||||||
|
|
||||||
|
AWSOutput AWSFile `json:"awsOutput,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCPFile contains the output information of a GCP IAM configuration.
|
||||||
|
type GCPFile struct {
|
||||||
|
ServiceAccountKey string `json:"serviceAccountID,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AzureFile contains the output information of a Microsoft Azure IAM configuration.
|
||||||
|
type AzureFile struct {
|
||||||
|
SubscriptionID string `json:"subscriptionID,omitempty"`
|
||||||
|
TenantID string `json:"tenantID,omitempty"`
|
||||||
|
ApplicationID string `json:"applicationID,omitempty"`
|
||||||
|
UAMIID string `json:"uamiID,omitempty"`
|
||||||
|
ApplicationClientSecretValue string `json:"applicationClientSecretValue,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSFile contains the output information of an AWS IAM configuration.
|
||||||
|
type AWSFile struct {
|
||||||
|
ControlPlaneInstanceProfile string `json:"controlPlaneInstanceProfile,omitempty"`
|
||||||
|
WorkerNodeInstanceProfile string `json:"workerNodeInstanceProfile,omitempty"`
|
||||||
|
}
|
@ -11,11 +11,9 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
@ -29,9 +27,8 @@ var terraformFS embed.FS
|
|||||||
|
|
||||||
// prepareWorkspace loads the embedded Terraform files,
|
// prepareWorkspace loads the embedded Terraform files,
|
||||||
// and writes them into the workspace.
|
// and writes them into the workspace.
|
||||||
func prepareWorkspace(fileHandler file.Handler, provider cloudprovider.Provider, workingDir string) error {
|
func prepareWorkspace(path string, fileHandler file.Handler, workingDir string) error {
|
||||||
// use path.Join to ensure no forward slashes are used to read the embedded FS
|
rootDir := path
|
||||||
rootDir := path.Join("terraform", strings.ToLower(provider.String()))
|
|
||||||
return fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error {
|
return fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -8,7 +8,9 @@ package terraform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
@ -21,11 +23,13 @@ import (
|
|||||||
|
|
||||||
func TestLoader(t *testing.T) {
|
func TestLoader(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
|
pathBase string
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
fileList []string
|
fileList []string
|
||||||
testAlreadyUnpacked bool
|
testAlreadyUnpacked bool
|
||||||
}{
|
}{
|
||||||
"aws": {
|
"awsCluster": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.AWS,
|
provider: cloudprovider.AWS,
|
||||||
fileList: []string{
|
fileList: []string{
|
||||||
"main.tf",
|
"main.tf",
|
||||||
@ -34,7 +38,8 @@ func TestLoader(t *testing.T) {
|
|||||||
"modules",
|
"modules",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"gcp": {
|
"gcpCluster": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.GCP,
|
provider: cloudprovider.GCP,
|
||||||
fileList: []string{
|
fileList: []string{
|
||||||
"main.tf",
|
"main.tf",
|
||||||
@ -43,7 +48,8 @@ func TestLoader(t *testing.T) {
|
|||||||
"modules",
|
"modules",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"qemu": {
|
"qemuCluster": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
fileList: []string{
|
fileList: []string{
|
||||||
"main.tf",
|
"main.tf",
|
||||||
@ -52,7 +58,35 @@ func TestLoader(t *testing.T) {
|
|||||||
"modules",
|
"modules",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"gcpIAM": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
fileList: []string{
|
||||||
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"azureIAM": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
fileList: []string{
|
||||||
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"awsIAM": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
fileList: []string{
|
||||||
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
|
},
|
||||||
|
},
|
||||||
"continue on (partially) unpacked": {
|
"continue on (partially) unpacked": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.AWS,
|
provider: cloudprovider.AWS,
|
||||||
fileList: []string{
|
fileList: []string{
|
||||||
"main.tf",
|
"main.tf",
|
||||||
@ -71,14 +105,16 @@ func TestLoader(t *testing.T) {
|
|||||||
|
|
||||||
file := file.NewHandler(afero.NewMemMapFs())
|
file := file.NewHandler(afero.NewMemMapFs())
|
||||||
|
|
||||||
err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir)
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
err := prepareWorkspace(path, file, constants.TerraformWorkingDir)
|
||||||
|
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
|
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
|
||||||
|
|
||||||
if tc.testAlreadyUnpacked {
|
if tc.testAlreadyUnpacked {
|
||||||
// Let's try the same again and check if we don't get a "file already exists" error.
|
// Let's try the same again and check if we don't get a "file already exists" error.
|
||||||
require.NoError(file.Remove(filepath.Join(constants.TerraformWorkingDir, "variables.tf")))
|
require.NoError(file.Remove(filepath.Join(constants.TerraformWorkingDir, "variables.tf")))
|
||||||
err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir)
|
err := prepareWorkspace(path, file, constants.TerraformWorkingDir)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
|
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,8 @@ func New(ctx context.Context, workingDir string) (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
|
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
|
||||||
func (c *Client) PrepareWorkspace(provider cloudprovider.Provider, vars Variables) error {
|
func (c *Client) PrepareWorkspace(path string, vars Variables) error {
|
||||||
if err := prepareWorkspace(c.file, provider, c.workingDir); err != nil {
|
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +109,141 @@ func (c *Client) CreateCluster(ctx context.Context) (string, string, error) {
|
|||||||
return ip, secret, nil
|
return ip, secret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IAMOutput contains the output information of the Terraform IAM operations.
|
||||||
|
type IAMOutput struct {
|
||||||
|
GCP GCPIAMOutput
|
||||||
|
Azure AzureIAMOutput
|
||||||
|
AWS AWSIAMOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCPIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||||
|
type GCPIAMOutput struct {
|
||||||
|
SaKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure.
|
||||||
|
type AzureIAMOutput struct {
|
||||||
|
SubscriptionID string
|
||||||
|
TenantID string
|
||||||
|
ApplicationID string
|
||||||
|
UAMIID string
|
||||||
|
ApplicationClientSecretValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||||
|
type AWSIAMOutput struct {
|
||||||
|
ControlPlaneInstanceProfile string
|
||||||
|
WorkerNodeInstanceProfile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateIAMConfig creates an IAM configuration using Terraform.
|
||||||
|
func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (IAMOutput, error) {
|
||||||
|
if err := c.tf.Init(ctx); err != nil {
|
||||||
|
return IAMOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.tf.Apply(ctx); err != nil {
|
||||||
|
return IAMOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tfState, err := c.tf.Show(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return IAMOutput{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
saKeyOutputRaw, ok := tfState.Values.Outputs["sa_key"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no service account key output found")
|
||||||
|
}
|
||||||
|
saKeyOutput, ok := saKeyOutputRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in service account key output: not a string")
|
||||||
|
}
|
||||||
|
return IAMOutput{
|
||||||
|
GCP: GCPIAMOutput{
|
||||||
|
SaKey: saKeyOutput,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
subscriptionIDRaw, ok := tfState.Values.Outputs["subscription_id"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no subscription id output found")
|
||||||
|
}
|
||||||
|
subscriptionIDOutput, ok := subscriptionIDRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in subscription id output: not a string")
|
||||||
|
}
|
||||||
|
tenantIDRaw, ok := tfState.Values.Outputs["tenant_id"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no tenant id output found")
|
||||||
|
}
|
||||||
|
tenantIDOutput, ok := tenantIDRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in tenant id output: not a string")
|
||||||
|
}
|
||||||
|
applicationIDRaw, ok := tfState.Values.Outputs["application_id"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no application id output found")
|
||||||
|
}
|
||||||
|
applicationIDOutput, ok := applicationIDRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in application id output: not a string")
|
||||||
|
}
|
||||||
|
uamiIDRaw, ok := tfState.Values.Outputs["uami_id"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no UAMI id output found")
|
||||||
|
}
|
||||||
|
uamiIDOutput, ok := uamiIDRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in UAMI id output: not a string")
|
||||||
|
}
|
||||||
|
appClientSecretRaw, ok := tfState.Values.Outputs["application_client_secret_value"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no application client secret value output found")
|
||||||
|
}
|
||||||
|
appClientSecretOutput, ok := appClientSecretRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in application client secret valueoutput: not a string")
|
||||||
|
}
|
||||||
|
return IAMOutput{
|
||||||
|
Azure: AzureIAMOutput{
|
||||||
|
SubscriptionID: subscriptionIDOutput,
|
||||||
|
TenantID: tenantIDOutput,
|
||||||
|
ApplicationID: applicationIDOutput,
|
||||||
|
UAMIID: uamiIDOutput,
|
||||||
|
ApplicationClientSecretValue: appClientSecretOutput,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case cloudprovider.AWS:
|
||||||
|
controlPlaneProfileRaw, ok := tfState.Values.Outputs["control_plane_instance_profile"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no control plane instance profile output found")
|
||||||
|
}
|
||||||
|
controlPlaneProfileOutput, ok := controlPlaneProfileRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in control plane instance profile output: not a string")
|
||||||
|
}
|
||||||
|
workerNodeProfileRaw, ok := tfState.Values.Outputs["worker_nodes_instance_profile"]
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("no worker node instance profile output found")
|
||||||
|
}
|
||||||
|
workerNodeProfileOutput, ok := workerNodeProfileRaw.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return IAMOutput{}, errors.New("invalid type in worker node instance profile output: not a string")
|
||||||
|
}
|
||||||
|
return IAMOutput{
|
||||||
|
AWS: AWSIAMOutput{
|
||||||
|
ControlPlaneInstanceProfile: controlPlaneProfileOutput,
|
||||||
|
WorkerNodeInstanceProfile: workerNodeProfileOutput,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return IAMOutput{}, errors.New("unsupported cloud provider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DestroyCluster destroys a Constellation cluster using Terraform.
|
// DestroyCluster destroys a Constellation cluster using Terraform.
|
||||||
func (c *Client) DestroyCluster(ctx context.Context) error {
|
func (c *Client) DestroyCluster(ctx context.Context) error {
|
||||||
if err := c.tf.Init(ctx); err != nil {
|
if err := c.tf.Init(ctx); err != nil {
|
||||||
|
@ -6,14 +6,6 @@ output "tenant_id" {
|
|||||||
value = data.azurerm_subscription.current.tenant_id
|
value = data.azurerm_subscription.current.tenant_id
|
||||||
}
|
}
|
||||||
|
|
||||||
output "region" {
|
|
||||||
value = var.region
|
|
||||||
}
|
|
||||||
|
|
||||||
output "base_resource_group_name" {
|
|
||||||
value = var.resource_group_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_id" {
|
output "application_id" {
|
||||||
value = azuread_application.base_application.application_id
|
value = azuread_application.base_application.application_id
|
||||||
}
|
}
|
42
cli/internal/terraform/terraform/iam/gcp/.terraform.lock.hcl
generated
Normal file
42
cli/internal/terraform/terraform/iam/gcp/.terraform.lock.hcl
generated
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/google" {
|
||||||
|
version = "4.44.0"
|
||||||
|
constraints = "4.44.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:JpShTtgnxpiIVnr0R2Lccrh84mnrf7Z1/v/yw0UZ1gI=",
|
||||||
|
"h1:JzDVTkBVHTw67N7S9bD/+u7U3aipjutF4MZx0eplONw=",
|
||||||
|
"h1:OTNLlWoTq+SdbbtgLK7uFVAn3aP9QVjIGYU2ndKsz+Q=",
|
||||||
|
"h1:PSIkDVwksHe9oZd+XP369N8U+6/+SPF8Z5wHkcwmWKw=",
|
||||||
|
"h1:d6Ds7aGeW5M8+AgWfkfbh3HTf3tP8ePjYGhoJnOxklU=",
|
||||||
|
"h1:enGPHgPHCbmLU8BIHLHcOwRlbQnPPQzdmXNoFVs1lIg=",
|
||||||
|
"h1:gmUUWhuuY/YRIllvVBRGl1kUHqsNBHQ/4BHdwKQbzXQ=",
|
||||||
|
"h1:hMhGerpfzsbfQ3DFsAGwJYpSIK6an+wMGx2I/lwaNuY=",
|
||||||
|
"h1:iotFu763eGWVC4zfXkNuo3RUeQLDvwVX2E7R/2sNxCg=",
|
||||||
|
"h1:mheXqRMLbMeTr8/E6UakMhWwIL0HqwIHYBE2u2Sbldg=",
|
||||||
|
"zh:0b424cab24856dc47177733145fa61b731f345a6a42a0c0b7910ccfcf4e8c8a2",
|
||||||
|
"zh:0c6b3049957b942e1dbc6f8c38de653a78ff1efe40a7cfc506e60a8da2775591",
|
||||||
|
"zh:1e1d282abd9dbd30602ca52050f875deeb4d79b3b06cda69535db4863f1b98a0",
|
||||||
|
"zh:35b7bc7c4da98e47bc7827fe1c39400811c9af459e03c99217195830f6bb0e78",
|
||||||
|
"zh:5ea45d229afad298bdac80324ec9d6968443d54ed1d1a01fad70b3c1bd16e53b",
|
||||||
|
"zh:652b740a7f75d716daf0fa9b2ef1964944eb4f8b0b26834dd8659a6ac2f3ed52",
|
||||||
|
"zh:75ee47f9a28826de301c3c6d2fb7915414f3841b1d0248cad6b94697fb8ee634",
|
||||||
|
"zh:89222d36d8060beb13df6758d6d9b2d075fa809e90a910a2ce1a867cfa6ff654",
|
||||||
|
"zh:a8c04acc69a65cb68b91ec08aa89c4953840dad33482c9acf4cc0272375b3bf4",
|
||||||
|
"zh:b71c10a8167cb6c7c3ae174c8c181a06dc82564f097f89602c3d74e8a7627e92",
|
||||||
|
"zh:bb9a92b640cf0596edcc510ddd20725637c1ff295054f727277108a4a3c9baec",
|
||||||
|
"zh:bccae696aa84ff7978c6a81b25d61f722fc87261e4b524ec062392916c8494ab",
|
||||||
|
"zh:bcd028cd233287420ecfbe4102e59e351e6fd22a4a14698e6896c45fb0509a1e",
|
||||||
|
"zh:bd9d096abdc42a3cf5849ae8adc9c8ca327c026e6f6f287fd436b6adfc8630dc",
|
||||||
|
"zh:c3577ea597c9f8707b86a3056f59ff28c35c77b17392dd75a5dbe46617f7f7c6",
|
||||||
|
"zh:d44915d60ffb54edf48ff9a1c2661b358497a9ecb900e8a3b3983f083ea198ce",
|
||||||
|
"zh:d5dfcc1949e12fcc2d9ac834296872bded5b4201f2c492eb127872a72080b56b",
|
||||||
|
"zh:d9dc91823cbcd60464d5b168d7bd34a5b5f13d5b37786110226bfe139ea24782",
|
||||||
|
"zh:e8647c8ab63144013446b73c695a01f6bef16712613f1461d1c0bc37e1ba80d6",
|
||||||
|
"zh:ecd0cd61f13d401240301691f3042abca874a5c481a90b4f791c0e4ea10b6448",
|
||||||
|
"zh:ed01ea31e457d6c4e01a5d6dfd6ad3d09a0a58ff7dc4de494bf559fbc34fa936",
|
||||||
|
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||||
|
"zh:ffb268a1b2b401175667a7e421100ce8964f6b32cab723766e8eb0b993b59e66",
|
||||||
|
]
|
||||||
|
}
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
google = {
|
google = {
|
||||||
source = "hashicorp/google"
|
source = "hashicorp/google"
|
||||||
version = "4.44.1"
|
version = "4.44.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
@ -40,6 +42,7 @@ func TestPrepareCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
|
pathBase string
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
vars Variables
|
vars Variables
|
||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
@ -47,17 +50,20 @@ func TestPrepareCluster(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"qemu": {
|
"qemu": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
fs: afero.NewMemMapFs(),
|
fs: afero.NewMemMapFs(),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
"no vars": {
|
"no vars": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
fs: afero.NewMemMapFs(),
|
fs: afero.NewMemMapFs(),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"continue on partially extracted": {
|
"continue on partially extracted": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
fs: afero.NewMemMapFs(),
|
fs: afero.NewMemMapFs(),
|
||||||
@ -65,6 +71,7 @@ func TestPrepareCluster(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
"prepare workspace fails": {
|
"prepare workspace fails": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
|
fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
|
||||||
@ -83,12 +90,107 @@ func TestPrepareCluster(t *testing.T) {
|
|||||||
workingDir: constants.TerraformWorkingDir,
|
workingDir: constants.TerraformWorkingDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.PrepareWorkspace(tc.provider, tc.vars)
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
err := c.PrepareWorkspace(path, tc.vars)
|
||||||
|
|
||||||
// Test case: Check if we can continue to create on an incomplete workspace.
|
// Test case: Check if we can continue to create on an incomplete workspace.
|
||||||
if tc.partiallyExtracted {
|
if tc.partiallyExtracted {
|
||||||
require.NoError(c.file.Remove(filepath.Join(c.workingDir, "main.tf")))
|
require.NoError(c.file.Remove(filepath.Join(c.workingDir, "main.tf")))
|
||||||
err = c.PrepareWorkspace(tc.provider, tc.vars)
|
err = c.PrepareWorkspace(path, tc.vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareIAM(t *testing.T) {
|
||||||
|
gcpVars := &GCPIAMVariables{
|
||||||
|
Project: "const-1234",
|
||||||
|
Region: "europe-west1",
|
||||||
|
Zone: "europe-west1-a",
|
||||||
|
ServiceAccountID: "const-test-case",
|
||||||
|
}
|
||||||
|
azureVars := &AzureIAMVariables{
|
||||||
|
Region: "westus",
|
||||||
|
ServicePrincipal: "constell-test-sp",
|
||||||
|
ResourceGroup: "constell-test-rg",
|
||||||
|
}
|
||||||
|
awsVars := &AWSIAMVariables{
|
||||||
|
Region: "eu-east-2a",
|
||||||
|
Prefix: "test",
|
||||||
|
}
|
||||||
|
testCases := map[string]struct {
|
||||||
|
pathBase string
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
vars Variables
|
||||||
|
fs afero.Fs
|
||||||
|
partiallyExtracted bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"no vars": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid path": {
|
||||||
|
pathBase: path.Join("abc", "123"),
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
"continue on partially extracted": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
partiallyExtracted: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
"azure": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
c := &Client{
|
||||||
|
tf: &stubTerraform{},
|
||||||
|
file: file.NewHandler(tc.fs),
|
||||||
|
workingDir: constants.TerraformIAMWorkingDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
err := c.PrepareWorkspace(path, tc.vars)
|
||||||
|
|
||||||
|
// Test case: Check if we can continue to create on an incomplete workspace.
|
||||||
|
if tc.partiallyExtracted {
|
||||||
|
require.NoError(c.file.Remove(filepath.Join(c.workingDir, "main.tf")))
|
||||||
|
err = c.PrepareWorkspace(path, tc.vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
@ -132,6 +234,7 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
|
pathBase string
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
vars Variables
|
vars Variables
|
||||||
tf *stubTerraform
|
tf *stubTerraform
|
||||||
@ -139,12 +242,14 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"works": {
|
"works": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{showState: newTestState()},
|
tf: &stubTerraform{showState: newTestState()},
|
||||||
fs: afero.NewMemMapFs(),
|
fs: afero.NewMemMapFs(),
|
||||||
},
|
},
|
||||||
"init fails": {
|
"init fails": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{initErr: someErr},
|
tf: &stubTerraform{initErr: someErr},
|
||||||
@ -152,6 +257,7 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"apply fails": {
|
"apply fails": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{applyErr: someErr},
|
tf: &stubTerraform{applyErr: someErr},
|
||||||
@ -159,6 +265,7 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"show fails": {
|
"show fails": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{showErr: someErr},
|
tf: &stubTerraform{showErr: someErr},
|
||||||
@ -166,6 +273,7 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"no ip": {
|
"no ip": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{
|
tf: &stubTerraform{
|
||||||
@ -179,6 +287,7 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"ip has wrong type": {
|
"ip has wrong type": {
|
||||||
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.QEMU,
|
provider: cloudprovider.QEMU,
|
||||||
vars: qemuVars,
|
vars: qemuVars,
|
||||||
tf: &stubTerraform{
|
tf: &stubTerraform{
|
||||||
@ -204,7 +313,8 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
workingDir: constants.TerraformWorkingDir,
|
workingDir: constants.TerraformWorkingDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(c.PrepareWorkspace(tc.provider, tc.vars))
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
require.NoError(c.PrepareWorkspace(path, tc.vars))
|
||||||
ip, initSecret, err := c.CreateCluster(context.Background())
|
ip, initSecret, err := c.CreateCluster(context.Background())
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
@ -218,6 +328,282 @@ func TestCreateCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateIAM(t *testing.T) {
|
||||||
|
someErr := errors.New("failed")
|
||||||
|
newTestState := func() *tfjson.State {
|
||||||
|
workingState := tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{
|
||||||
|
"sa_key": {
|
||||||
|
Value: "12345678_abcdefg",
|
||||||
|
},
|
||||||
|
"subscription_id": {
|
||||||
|
Value: "test_subscription_id",
|
||||||
|
},
|
||||||
|
"tenant_id": {
|
||||||
|
Value: "test_tenant_id",
|
||||||
|
},
|
||||||
|
"application_id": {
|
||||||
|
Value: "test_application_id",
|
||||||
|
},
|
||||||
|
"uami_id": {
|
||||||
|
Value: "test_uami_id",
|
||||||
|
},
|
||||||
|
"application_client_secret_value": {
|
||||||
|
Value: "test_application_client_secret_value",
|
||||||
|
},
|
||||||
|
"control_plane_instance_profile": {
|
||||||
|
Value: "test_control_plane_instance_profile",
|
||||||
|
},
|
||||||
|
"worker_nodes_instance_profile": {
|
||||||
|
Value: "test_worker_nodes_instance_profile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &workingState
|
||||||
|
}
|
||||||
|
gcpVars := &GCPIAMVariables{
|
||||||
|
Project: "const-1234",
|
||||||
|
Region: "europe-west1",
|
||||||
|
Zone: "europe-west1-a",
|
||||||
|
ServiceAccountID: "const-test-case",
|
||||||
|
}
|
||||||
|
azureVars := &AzureIAMVariables{
|
||||||
|
Region: "westus",
|
||||||
|
ServicePrincipal: "constell-test-sp",
|
||||||
|
ResourceGroup: "constell-test-rg",
|
||||||
|
}
|
||||||
|
awsVars := &AWSIAMVariables{
|
||||||
|
Region: "eu-east-2a",
|
||||||
|
Prefix: "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
pathBase string
|
||||||
|
provider cloudprovider.Provider
|
||||||
|
vars Variables
|
||||||
|
tf *stubTerraform
|
||||||
|
fs afero.Fs
|
||||||
|
wantErr bool
|
||||||
|
want IAMOutput
|
||||||
|
}{
|
||||||
|
"gcp works": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{showState: newTestState()},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}},
|
||||||
|
},
|
||||||
|
"gcp init fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{initErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp apply fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{applyErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp show fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{showErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp no sa_key": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"gcp sa_key has wrong type": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.GCP,
|
||||||
|
vars: gcpVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{"sa_key": {Value: 42}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure works": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{showState: newTestState()},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
want: IAMOutput{Azure: AzureIAMOutput{
|
||||||
|
SubscriptionID: "test_subscription_id",
|
||||||
|
TenantID: "test_tenant_id",
|
||||||
|
ApplicationID: "test_application_id",
|
||||||
|
ApplicationClientSecretValue: "test_application_client_secret_value",
|
||||||
|
UAMIID: "test_uami_id",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
"azure init fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{initErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure apply fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{applyErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure show fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{showErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure no subscription_id": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure subscription_id has wrong type": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.Azure,
|
||||||
|
vars: azureVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{"subscription_id": {Value: 42}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"aws works": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{showState: newTestState()},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
want: IAMOutput{AWS: AWSIAMOutput{
|
||||||
|
ControlPlaneInstanceProfile: "test_control_plane_instance_profile",
|
||||||
|
WorkerNodeInstanceProfile: "test_worker_nodes_instance_profile",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
"aws init fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{initErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"aws apply fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{applyErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"aws show fails": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{showErr: someErr},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"aws no control_plane_instance_profile": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"azure control_plane_instance_profile has wrong type": {
|
||||||
|
pathBase: path.Join("terraform", "iam"),
|
||||||
|
provider: cloudprovider.AWS,
|
||||||
|
vars: awsVars,
|
||||||
|
tf: &stubTerraform{
|
||||||
|
showState: &tfjson.State{
|
||||||
|
Values: &tfjson.StateValues{
|
||||||
|
Outputs: map[string]*tfjson.StateOutput{"control_plane_instance_profile": {Value: 42}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fs: afero.NewMemMapFs(),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
c := &Client{
|
||||||
|
tf: tc.tf,
|
||||||
|
file: file.NewHandler(tc.fs),
|
||||||
|
workingDir: constants.TerraformIAMWorkingDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
require.NoError(c.PrepareWorkspace(path, tc.vars))
|
||||||
|
IAMoutput, err := c.CreateIAMConfig(context.Background(), tc.provider)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.want, IAMoutput)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDestroyInstances(t *testing.T) {
|
func TestDestroyInstances(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
tf *stubTerraform
|
tf *stubTerraform
|
||||||
|
@ -39,8 +39,8 @@ func (v *CommonVariables) String() string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWSVariables is user configuration for creating a cluster with Terraform on GCP.
|
// AWSClusterVariables is user configuration for creating a cluster with Terraform on GCP.
|
||||||
type AWSVariables struct {
|
type AWSClusterVariables struct {
|
||||||
// CommonVariables contains common variables.
|
// CommonVariables contains common variables.
|
||||||
CommonVariables
|
CommonVariables
|
||||||
// Region is the AWS region to use.
|
// Region is the AWS region to use.
|
||||||
@ -61,8 +61,40 @@ type AWSVariables struct {
|
|||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCPVariables is user configuration for creating a cluster with Terraform on GCP.
|
func (v *AWSClusterVariables) String() string {
|
||||||
type GCPVariables struct {
|
b := &strings.Builder{}
|
||||||
|
b.WriteString(v.CommonVariables.String())
|
||||||
|
writeLinef(b, "region = %q", v.Region)
|
||||||
|
writeLinef(b, "zone = %q", v.Zone)
|
||||||
|
writeLinef(b, "ami = %q", v.AMIImageID)
|
||||||
|
writeLinef(b, "instance_type = %q", v.InstanceType)
|
||||||
|
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
|
||||||
|
writeLinef(b, "iam_instance_profile_control_plane = %q", v.IAMProfileControlPlane)
|
||||||
|
writeLinef(b, "iam_instance_profile_worker_nodes = %q", v.IAMProfileWorkerNodes)
|
||||||
|
writeLinef(b, "debug = %t", v.Debug)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AWSIAMVariables is user configuration for creating the IAM configuration with Terraform on Microsoft Azure.
|
||||||
|
type AWSIAMVariables struct {
|
||||||
|
// Region is the AWS location to use. (e.g. us-east-2)
|
||||||
|
Region string
|
||||||
|
// Prefix is the name prefix of the resources to use.
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
|
||||||
|
func (v *AWSIAMVariables) String() string {
|
||||||
|
b := &strings.Builder{}
|
||||||
|
writeLinef(b, "name_prefix = %q", v.Prefix)
|
||||||
|
writeLinef(b, "region = %q", v.Region)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCPClusterVariables is user configuration for creating resources with Terraform on GCP.
|
||||||
|
type GCPClusterVariables struct {
|
||||||
// CommonVariables contains common variables.
|
// CommonVariables contains common variables.
|
||||||
CommonVariables
|
CommonVariables
|
||||||
|
|
||||||
@ -84,23 +116,8 @@ type GCPVariables struct {
|
|||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *AWSVariables) String() string {
|
|
||||||
b := &strings.Builder{}
|
|
||||||
b.WriteString(v.CommonVariables.String())
|
|
||||||
writeLinef(b, "region = %q", v.Region)
|
|
||||||
writeLinef(b, "zone = %q", v.Zone)
|
|
||||||
writeLinef(b, "ami = %q", v.AMIImageID)
|
|
||||||
writeLinef(b, "instance_type = %q", v.InstanceType)
|
|
||||||
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
|
|
||||||
writeLinef(b, "iam_instance_profile_control_plane = %q", v.IAMProfileControlPlane)
|
|
||||||
writeLinef(b, "iam_instance_profile_worker_nodes = %q", v.IAMProfileWorkerNodes)
|
|
||||||
writeLinef(b, "debug = %t", v.Debug)
|
|
||||||
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of the variables, formatted as Terraform variables.
|
// String returns a string representation of the variables, formatted as Terraform variables.
|
||||||
func (v *GCPVariables) String() string {
|
func (v *GCPClusterVariables) String() string {
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
b.WriteString(v.CommonVariables.String())
|
b.WriteString(v.CommonVariables.String())
|
||||||
writeLinef(b, "project = %q", v.Project)
|
writeLinef(b, "project = %q", v.Project)
|
||||||
@ -114,8 +131,31 @@ func (v *GCPVariables) String() string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AzureVariables is user configuration for creating a cluster with Terraform on Azure.
|
// GCPIAMVariables is user configuration for creating the IAM confioguration with Terraform on GCP.
|
||||||
type AzureVariables struct {
|
type GCPIAMVariables struct {
|
||||||
|
// Project is the ID of the GCP project to use.
|
||||||
|
Project string
|
||||||
|
// Region is the GCP region to use.
|
||||||
|
Region string
|
||||||
|
// Zone is the GCP zone to use.
|
||||||
|
Zone string
|
||||||
|
// ServiceAccountID is the ID of the service account to use.
|
||||||
|
ServiceAccountID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
|
||||||
|
func (v *GCPIAMVariables) String() string {
|
||||||
|
b := &strings.Builder{}
|
||||||
|
writeLinef(b, "project_id = %q", v.Project)
|
||||||
|
writeLinef(b, "region = %q", v.Region)
|
||||||
|
writeLinef(b, "zone = %q", v.Zone)
|
||||||
|
writeLinef(b, "service_account_id = %q", v.ServiceAccountID)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AzureClusterVariables is user configuration for creating a cluster with Terraform on Azure.
|
||||||
|
type AzureClusterVariables struct {
|
||||||
// CommonVariables contains common variables.
|
// CommonVariables contains common variables.
|
||||||
CommonVariables
|
CommonVariables
|
||||||
|
|
||||||
@ -140,7 +180,7 @@ type AzureVariables struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string representation of the variables, formatted as Terraform variables.
|
// String returns a string representation of the variables, formatted as Terraform variables.
|
||||||
func (v *AzureVariables) String() string {
|
func (v *AzureClusterVariables) String() string {
|
||||||
b := &strings.Builder{}
|
b := &strings.Builder{}
|
||||||
b.WriteString(v.CommonVariables.String())
|
b.WriteString(v.CommonVariables.String())
|
||||||
writeLinef(b, "resource_group = %q", v.ResourceGroup)
|
writeLinef(b, "resource_group = %q", v.ResourceGroup)
|
||||||
@ -156,6 +196,26 @@ func (v *AzureVariables) String() string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AzureIAMVariables is user configuration for creating the IAM configuration with Terraform on Microsoft Azure.
|
||||||
|
type AzureIAMVariables struct {
|
||||||
|
// Region is the Azure region to use. (e.g. westus)
|
||||||
|
Region string
|
||||||
|
// ServicePrincipal is the name of the service principal to use.
|
||||||
|
ServicePrincipal string
|
||||||
|
// ResourceGroup is the name of the resource group to use.
|
||||||
|
ResourceGroup string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
|
||||||
|
func (v *AzureIAMVariables) String() string {
|
||||||
|
b := &strings.Builder{}
|
||||||
|
writeLinef(b, "service_principal_name = %q", v.ServicePrincipal)
|
||||||
|
writeLinef(b, "region = %q", v.Region)
|
||||||
|
writeLinef(b, "resource_group_name = %q", v.ResourceGroup)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
// QEMUVariables is user configuration for creating a QEMU cluster with Terraform.
|
// QEMUVariables is user configuration for creating a QEMU cluster with Terraform.
|
||||||
type QEMUVariables struct {
|
type QEMUVariables struct {
|
||||||
// CommonVariables contains common variables.
|
// CommonVariables contains common variables.
|
||||||
|
32
hack/terraform/gcp/iam/.terraform.lock.hcl
generated
32
hack/terraform/gcp/iam/.terraform.lock.hcl
generated
@ -1,32 +0,0 @@
|
|||||||
# This file is maintained automatically by "terraform init".
|
|
||||||
# Manual edits may be lost in future updates.
|
|
||||||
|
|
||||||
provider "registry.terraform.io/hashicorp/google" {
|
|
||||||
version = "4.44.1"
|
|
||||||
constraints = "4.44.1"
|
|
||||||
hashes = [
|
|
||||||
"h1:BF4teLn5CRL7mU/bx+Xt2WkwYefVnvOszBgOKSXriAs=",
|
|
||||||
"h1:CscOj39+trGfPFHhB/GZbkF1RIRuksg6HZw61W+MuDE=",
|
|
||||||
"h1:Cx7FXPPBZ+As+EPVypmgjwo8PXXV7pLhnk/6JDzxnro=",
|
|
||||||
"h1:EH0DUD1ntp8TWOuQWQoi2NZbSN+5FFgEwH8DtffslEA=",
|
|
||||||
"h1:GJMP+l9nhw+G1J/l4gyRxlUXYOAbmyKl//u8dNWmCk0=",
|
|
||||||
"h1:a7Ju1uXrlWfOCKFerwUej9KReUp78JbawOtVrOkBMwM=",
|
|
||||||
"h1:fL4NeRoxY9WGGCpO+SqIkLPm4sFbPykpN2DuCMvR2q4=",
|
|
||||||
"h1:l3jJuHRgwD/tLKfifUvkIP6wQgEIpWSnhOaMIZnal7c=",
|
|
||||||
"h1:m1mVJKnt+s+FbfwqSm6/kcGy6FakWqPV8xjGqwNu+Gg=",
|
|
||||||
"h1:raHEXJpQCHohqYupGPt56hD7LrO0uq3O/ve7x4AQ14I=",
|
|
||||||
"h1:uU1/bSQgQAhtXIcMOU1O7kpQK/1uuVpN3UeGJYjG/z8=",
|
|
||||||
"zh:0668252985a677707bf46e393a96ea63b58b93d730f4daf8357246f7e6dd8ba9",
|
|
||||||
"zh:1cbfa5c7dcf02acb90718474a6b0e6af6a7c839c964270feaf55cddb537ef762",
|
|
||||||
"zh:44d601bc4667158c45ab584e60662f69d38e9febbb65b9bb1c5a84fdccc8b91e",
|
|
||||||
"zh:452a2c703c5c6024696d818a1e008067fa29cdf99bdae6847d5f8eed7a0b4d75",
|
|
||||||
"zh:55a0a22fd03fabdfff7debb5840ffa68a93850c864f48a852d6eb1f74ecae915",
|
|
||||||
"zh:7f59721375d9bb05fcc6eb17991a7bf1aab1b0f180107515ec3b9e298c6c6152",
|
|
||||||
"zh:a09ce6734c8f2cfb1b5855f073105b2403305fee0f68d846b1303ca37d516a28",
|
|
||||||
"zh:ae8e413ee02824c44b85b4d0aa71335940757a7a33922f55c13a800bc5e15b45",
|
|
||||||
"zh:c2947aea252929ba608bcc5b892d045541ce7224f104a542c18c5487287df9ef",
|
|
||||||
"zh:df463ad9ef19641e4502879af051dc17c6880882d436026255d8f9103992dc42",
|
|
||||||
"zh:ee113c1f6e32fa4c41fda9191d7fd50a1137d1072d1a39627fe90e10941d48ea",
|
|
||||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
|
||||||
]
|
|
||||||
}
|
|
@ -73,6 +73,10 @@ const (
|
|||||||
MasterSecretFilename = "constellation-mastersecret.json"
|
MasterSecretFilename = "constellation-mastersecret.json"
|
||||||
// TerraformWorkingDir is the directory name for the TerraformClient workspace.
|
// TerraformWorkingDir is the directory name for the TerraformClient workspace.
|
||||||
TerraformWorkingDir = "constellation-terraform"
|
TerraformWorkingDir = "constellation-terraform"
|
||||||
|
// TerraformIAMWorkingDir is the directory name for the Terraform IAM Client workspace.
|
||||||
|
TerraformIAMWorkingDir = "constellation-iam-terraform"
|
||||||
|
// GCPServiceAccountKeyFile is the file name for the GCP service account key file.
|
||||||
|
GCPServiceAccountKeyFile = "gcpServiceAccountKey.json"
|
||||||
// ControlPlaneAdminConfFilename filepath to control plane kubernetes admin config.
|
// ControlPlaneAdminConfFilename filepath to control plane kubernetes admin config.
|
||||||
ControlPlaneAdminConfFilename = "/etc/kubernetes/admin.conf"
|
ControlPlaneAdminConfFilename = "/etc/kubernetes/admin.conf"
|
||||||
// KubectlPath path to kubectl binary.
|
// KubectlPath path to kubectl binary.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user