Skip to content

Commit

Permalink
chore: add apply/destroy actions for all stages for the foundation de…
Browse files Browse the repository at this point in the history
…ploy helper (#986)
  • Loading branch information
daniel-cit authored Aug 24, 2023
1 parent 9af797c commit 9d8f633
Show file tree
Hide file tree
Showing 13 changed files with 958 additions and 76 deletions.
4 changes: 2 additions & 2 deletions 0-bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ locals {
org_admins_org_iam_permissions = var.org_policy_admin_role == true ? [
"roles/orgpolicy.policyAdmin", "roles/resourcemanager.organizationAdmin", "roles/billing.user"
] : ["roles/resourcemanager.organizationAdmin", "roles/billing.user"]
group_org_admins = var.groups.create_groups ? var.groups.required_groups.group_org_admins : var.group_org_admins
group_billing_admins = var.groups.create_groups ? var.groups.required_groups.group_billing_admins : var.group_billing_admins
group_org_admins = var.groups.create_groups ? module.required_group["group_org_admins"].id : var.group_org_admins
group_billing_admins = var.groups.create_groups ? module.required_group["group_billing_admins"].id : var.group_billing_admins
}

resource "google_folder" "bootstrap" {
Expand Down
55 changes: 50 additions & 5 deletions helpers/foundation-deployer/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# [WIP] Terraform Example Foundation deploy helper
# Terraform Example Foundation deploy helper

Helper tool to deploy the Terraform example foundation.

## Usage

### Validate required tools

- Check if required tools, Go 1.18+, Terraform 1.3.0+, gcloud 393.0.0+, and Git 2.28.0+, are installed:

```bash
Expand All @@ -16,6 +18,16 @@ Helper tool to deploy the Terraform example foundation.
git --version
```

- check if required components of `gcloud` are installed:

```bash
gcloud components list --filter="id=beta OR id=terraform-tools"
```

- Follow the instructions in the output of the command if components `beta` and `terraform-tools` are not installed to install them.

### Prepare the deploy environment

- Create a directory in the file system to host the Cloud Source repositories the will be created and a copy of the terraform example foundation.
- Clone the `terraform-example-foundation` repository on this directory.

Expand All @@ -33,7 +45,10 @@ Helper tool to deploy the Terraform example foundation.
```

- Update `global.tfvars` with values from your environment.
See the READMEs for the stages for additional information:
- The `0-bootstrap` README [prerequisites](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/0-bootstrap/README.md#prerequisites) section has additional prerequisites needed to run this helper.
- Variable `code_checkout_path` is the full path to `deploy-directory` directory.
- Variable `foundation_code_path` is the full path to `terraform-example-foundation` directory.
- See the READMEs for the stages for additional information:
- [0-bootstrap](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/0-bootstrap/README.md)
- [1-org](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/1-org/README.md)
- [2-environments](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/2-environments/README.md)
Expand All @@ -42,15 +57,45 @@ See the READMEs for the stages for additional information:
- [4-projects](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/4-projects)
- [5-app-infra](https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/5-app-infra)

- Variable `code_checkout_path` is the full path to `deploy-directory` directory.
- Variable `foundation_code_path` is the full path to `terraform-example-foundation` directory.
### Location

By default the foundation regional resources are deployed in `us-west1` and `us-central1` regions and multi-regional resources are deployed in the `US` multi-region.

Im addition to the variables declared in the file `global.tfvars` for configuring location, there are two locals, `default_region1` and `default_region2`, in each one of the environments (`production`, `non-production`, and `development`) in the network steps (`3-networks-dual-svpc` and `3-networks-hub-and-spoke`) . They are located in the [main.tf](../../3-networks-dual-svpc/envs/production/main.tf#L20-L21) files for each environments.

**Note:** the region used for the variable `default_region` in the file `global.tfvars` **MUST** be one of the regions used for the `default_region1` and `default_region2` locals.

### Application default credentials

- Set the billing quota project in the `gcloud` configuration

```
gcloud config set billing/quota_project <QUOTA-PROJECT>
gcloud services enable \
"cloudresourcemanager.googleapis.com" \
"iamcredentials.googleapis.com" \
"cloudbuild.googleapis.com" \
"securitycenter.googleapis.com" \
"accesscontextmanager.googleapis.com" \
--project <QUOTA-PROJECT>
```

- Configure [Application Default Credentials](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login)

```bash
gcloud auth application-default login
```

### Run the helper

- Install the helper:

```bash
go install
```

- Validate the tfvars file:
- Validate the tfvars file. If you configured a `validator_project_id` in the `global.tfvars` file `validate` will do additional checks for the Secure Command Center notification name and for the Tag Key name. For these extra check you need at least the roles *Security Center Notification Configurations Viewer* (`roles/securitycenter.notificationConfigViewer`) and *Tag Viewer* (`roles/resourcemanager.tagViewer`):

```bash
$HOME/go/bin/foundation-deployer -tfvars_file <PATH TO 'global.tfvars' FILE> -validate
Expand Down
33 changes: 33 additions & 0 deletions helpers/foundation-deployer/global.tfvars.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ default_region = "us-central1"

group_org_admins = "REPLACE_ME" # "[email protected]"
group_billing_admins = "REPLACE_ME" # "[email protected]"
org_project_creators = []

bucket_force_destroy = false
project_prefix = "prj"
Expand All @@ -50,6 +51,36 @@ folder_prefix = "fldr"

//parent_folder = "01234567890"

// Optional - for enabling the automatic groups creation, uncomment the groups
// variable and update the values with the desired group names
// https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/0-bootstrap/README.md#optional---automatic-creation-of-google-cloud-identity-groups

// After deploy, the Bootstrap service account will need to be granted "Group Admin" role in the
// Google Workspace by a Super Admin before Cloud Build builds can be executed by the Bootstrap workspace.
// https://github.com/terraform-google-modules/terraform-google-group/blob/main/README.md#google-workspace-formerly-known-as-g-suite-roles

//initial_group_config = "WITH_INITIAL_OWNER"
//groups = {
// create_groups = true,
// billing_project = "BILLING-PROJECT",
// required_groups = {
// group_org_admins = "[email protected]"
// group_billing_admins = "[email protected]"
// billing_data_users = "[email protected]"
// audit_data_users = "[email protected]"
// monitoring_workspace_users = "[email protected]"
// },
// optional_groups = { # fill in only the groups to be created
// gcp_platform_viewer = ""
// gcp_security_reviewer = ""
// gcp_network_viewer = ""
// gcp_scc_admin = ""
// gcp_global_secrets_admin = ""
// gcp_audit_viewer = ""
// }
//}
//


// 1-org inputs
// https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/1-org/envs/shared/README.md#inputs
Expand All @@ -63,6 +94,8 @@ essential_contacts_domains_to_allow = ["@example.com"]
scc_notification_name = "scc-notify"
audit_logs_table_delete_contents_on_destroy = false
log_export_storage_force_destroy = false
log_export_storage_location = "US"
billing_export_dataset_location = "US"

// Choose witch network architecture to use:
// Dual Shared VPC: https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/3-networks-dual-svpc/README.md
Expand Down
2 changes: 1 addition & 1 deletion helpers/foundation-deployer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test v0.5.0
github.com/gruntwork-io/terratest v0.41.12
github.com/hashicorp/hcl/v2 v2.16.1
github.com/mitchellh/go-testing-interface v1.14.2-0.20210217184823-a52172cd2f64
github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770
github.com/stretchr/testify v1.8.2
github.com/terraform-google-modules/terraform-example-foundation/test/integration v0.0.0-20230503230051-e9e2618ef515
github.com/tidwall/gjson v1.14.4
Expand Down
6 changes: 2 additions & 4 deletions helpers/foundation-deployer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-testing-interface v1.14.2-0.20210217184823-a52172cd2f64 h1:+9bM6qWXndPx7+czi9+Jj6zHPioFpfdhwVGOYOgujMY=
github.com/mitchellh/go-testing-interface v1.14.2-0.20210217184823-a52172cd2f64/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770 h1:drhDO54gdT/a15GBcMRmunZiNcLgPiFIJa23KzmcvcU=
github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770/go.mod h1:SO/iHr6q2EzbqRApt+8/E9wqebTwQn5y+UlB04bxzo0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
Expand Down Expand Up @@ -447,8 +447,6 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/terraform-google-modules/terraform-example-foundation/test/integration v0.0.0-20230329165204-e5db58ea752a h1:TYICMneChDJfEEOOmQzYUFwIh4GvoRKXjiJD9fii2EM=
github.com/terraform-google-modules/terraform-example-foundation/test/integration v0.0.0-20230329165204-e5db58ea752a/go.mod h1:4EkFeYb9/Cjbqc4uynVvcmV8MCK7VK5dGuR2yD4r8EU=
github.com/terraform-google-modules/terraform-example-foundation/test/integration v0.0.0-20230503230051-e9e2618ef515 h1:9WpwfiGRUEX5e3qeC93M/TWxmVeSD8vz1B8c5FumdAE=
github.com/terraform-google-modules/terraform-example-foundation/test/integration v0.0.0-20230503230051-e9e2618ef515/go.mod h1:4EkFeYb9/Cjbqc4uynVvcmV8MCK7VK5dGuR2yD4r8EU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
Expand Down
123 changes: 122 additions & 1 deletion helpers/foundation-deployer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func main() {
Logger: utils.GetLogger(cfg.quiet),
}

// only enable services if they are not already enabled
// only enable services if they are not already enabled
if globalTFVars.HasValidatorProj() {
conf.ValidatorProject = *globalTFVars.ValidatorProjectId
var apis []string
Expand Down Expand Up @@ -158,6 +158,61 @@ func main() {
// destroy stages
if cfg.destroy {
// Note: destroy is only terraform destroy, local directories are not deleted.
// 5-app-infra
msg.PrintStageMsg("Destroying 5-app-infra stage")
err = s.RunDestroyStep("bu1-example-app", func() error {
io := stages.GetInfraPipelineOutputs(t, conf.CheckoutPath, "bu1-example-app")
return stages.DestroyExampleAppStage(t, s, io, conf)
})
if err != nil {
fmt.Printf("# Example app step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 4-projects
msg.PrintStageMsg("Destroying 4-projects stage")
err = s.RunDestroyStep("gcp-projects", func() error {
bo := stages.GetBootstrapStepOutputs(t, conf.FoundationPath)
return stages.DestroyProjectsStage(t, s, bo, conf)
})
if err != nil {
fmt.Printf("# Projects step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 3-networks
msg.PrintStageMsg("Destroying 3-networks stage")
err = s.RunDestroyStep("gcp-networks", func() error {
bo := stages.GetBootstrapStepOutputs(t, conf.FoundationPath)
return stages.DestroyNetworksStage(t, s, bo, conf)
})
if err != nil {
fmt.Printf("# Networks step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 2-environments
msg.PrintStageMsg("Destroying 2-environments stage")
err = s.RunDestroyStep("gcp-environments", func() error {
bo := stages.GetBootstrapStepOutputs(t, conf.FoundationPath)
return stages.DestroyEnvStage(t, s, bo, conf)
})
if err != nil {
fmt.Printf("# Environments step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 1-org
msg.PrintStageMsg("Destroying 1-org stage")
err = s.RunDestroyStep("gcp-org", func() error {
bo := stages.GetBootstrapStepOutputs(t, conf.FoundationPath)
return stages.DestroyOrgStage(t, s, bo, conf)
})
if err != nil {
fmt.Printf("# Org step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 0-bootstrap
msg.PrintStageMsg("Destroying 0-bootstrap stage")
err = s.RunDestroyStep("gcp-bootstrap", func() error {
Expand All @@ -167,6 +222,13 @@ func main() {
fmt.Printf("# Bootstrap step destroy failed. Error: %s\n", err.Error())
os.Exit(3)
}

// clean up the steps file
err = steps.DeleteStepsFile(cfg.stepsFile)
if err != nil {
fmt.Printf("# failed to delete state file %s. Error: %s\n", cfg.stepsFile, err.Error())
os.Exit(3)
}
return
}

Expand All @@ -189,5 +251,64 @@ func main() {
msg.PrintBuildMsg(bo.CICDProject, bo.DefaultRegion, conf.DisablePrompt)
}
msg.PrintQuotaMsg(bo.ProjectsSA, conf.DisablePrompt)
if globalTFVars.HasGroupsCreation() {
msg.PrintAdminGroupPermissionMsg(bo.BootstrapSA, conf.DisablePrompt)
}

// 1-org
msg.PrintStageMsg("Deploying 1-org stage")
err = s.RunStep("gcp-org", func() error {
return stages.DeployOrgStage(t, s, globalTFVars, bo, conf)
})
if err != nil {
fmt.Printf("# Org step failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 2-environments
msg.PrintStageMsg("Deploying 2-environments stage")
err = s.RunStep("gcp-environments", func() error {
return stages.DeployEnvStage(t, s, globalTFVars, bo, conf)
})
if err != nil {
fmt.Printf("# Environments step failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 3-networks
msg.PrintStageMsg("Deploying 3-networks stage")
err = s.RunStep("gcp-networks", func() error {
return stages.DeployNetworksStage(t, s, globalTFVars, bo, conf)
})
if err != nil {
fmt.Printf("# Networks step failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 4-projects
msg.PrintStageMsg("Deploying 4-projects stage")
msg.ConfirmQuota(bo.ProjectsSA, conf.DisablePrompt)

err = s.RunStep("gcp-projects", func() error {
return stages.DeployProjectsStage(t, s, globalTFVars, bo, conf)
})
if err != nil {
fmt.Printf("# Projects step failed. Error: %s\n", err.Error())
os.Exit(3)
}

// 5-app-infra
msg.PrintStageMsg("Deploying 5-app-infra stage")
io := stages.GetInfraPipelineOutputs(t, conf.CheckoutPath, "bu1-example-app")
io.RemoteStateBucket = bo.RemoteStateBucketProjects

msg.PrintBuildMsg(io.InfraPipeProj, io.DefaultRegion, conf.DisablePrompt)

err = s.RunStep("bu1-example-app", func() error {
return stages.DeployExampleAppStage(t, s, globalTFVars, io, conf)
})
if err != nil {
fmt.Printf("# Example app step failed. Error: %s\n", err.Error())
os.Exit(3)
}
}
20 changes: 18 additions & 2 deletions helpers/foundation-deployer/msg/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
buildErrorURL = "https://console.cloud.google.com/cloud-build/builds;region=%s/%s?project=%s"
quotaURL = "https://support.google.com/code/contact/billing_quota_increase"
troubleQuotaURL = "https://github.com/terraform-google-modules/terraform-example-foundation/blob/master/docs/TROUBLESHOOTING.md#billing-quota-exceeded"
groupAdminURL = "https://cloud.google.com/identity/docs/how-to/setup#assigning_an_admin_role_to_the_service_account"
)

var (
Expand Down Expand Up @@ -67,10 +68,10 @@ func PrintStageMsg(msg string) {

func PrintBuildMsg(project, region string, disablePrompt bool) {
fmt.Println("")
fmt.Println("# Check build results in the Google console:")
fmt.Println("# Follow build execution and check build results in the Google console:")
fmt.Printf("# %s\n", CloudBuildURL(project, region))
if !disablePrompt {
PressEnter("")
PressEnter("# Press Enter to continue at any time")
fmt.Println("")
}
}
Expand All @@ -90,6 +91,21 @@ func PrintQuotaMsg(sa string, disablePrompt bool) {
}
}

func PrintAdminGroupPermissionMsg(sa string, disablePrompt bool) {
fmt.Println("")
fmt.Println("# Request a Super Admin to Grant 'Group Admin' role in the")
fmt.Println("# Admin Console of the Google Workspace to the Bootstrap service account:")
fmt.Printf("# %s \n", sa)
fmt.Println("")
fmt.Printf("# See: %s\n", groupAdminURL)
fmt.Println("# for additional information")
fmt.Println("")
if !disablePrompt {
PressEnter("")
fmt.Println("")
}
}

func ConfirmQuota(sa string, disablePrompt bool) {
fmt.Println("")
fmt.Println("# Proceed if you received confirmation of billing quota increase for the service account of stage 4-projects")
Expand Down
Loading

0 comments on commit 9d8f633

Please sign in to comment.