diff --git a/gcp/.header.md b/gcp/.header.md new file mode 100644 index 0000000..861be1d --- /dev/null +++ b/gcp/.header.md @@ -0,0 +1,223 @@ +# Terraform Google Cloud FleetDM Deployment + +This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: + +* A new Google Cloud Project (via `terraform-google-modules/project-factory`) +* VPC Network and Subnets +* Cloud NAT for outbound internet access +* Cloud SQL for MySQL (database for Fleet) +* Memorystore for Redis (caching for Fleet) +* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) +* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) +* Cloud Run v2 Service (for the main Fleet application) +* Cloud Run v2 Job (for database migrations) +* External HTTP(S) Load Balancer with managed SSL certificate +* Cloud DNS for managing the Fleet endpoint +* Appropriate IAM service accounts and permissions + +## Prerequisites + +1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). +2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). +3. **GCP Account & Permissions:** + * A Google Cloud Organization ID. + * A Billing Account ID. + * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: + * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). + * `Billing Account User` role on the Billing Account. + * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. + * Alternatively, use a Service Account with appropriate permissions. +4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). +5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. + +## Configuration + +1. **Clone the repository (if applicable) or ensure you have all the files.** +2. **Create a `terraform.tfvars` file:** + Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. + + **Example `terraform.tfvars.example`:** + ```hcl + # Required: GCP Organization and Billing + org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" + billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" + + # Required: DNS Configuration + # The public DNS zone name (e.g., "example.com." - note the trailing dot) + # This project will create/manage this zone in Cloud DNS. + dns_zone_name = "your-domain.com." + # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") + dns_record_name = "fleet.your-domain.com." + + # Optional: Project Configuration + project_name = "my-fleet-project" # Name for the new GCP project + random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) + + # Optional: Labels + labels = { + application = "fleetdm" + environment = "production" + owner = "devops-team" + } + + # --- byo-project module specific variables (passed through from root) --- + # You can override defaults from byo-project/variables.tf here if needed, + # or set values for variables that don't have defaults. + + # Example: FleetDM Configuration including migration execution + fleet_config = { + # image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version + # license_key = "YOUR_FLEET_LICENSE_KEY" + # installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique + # fleet_cpu = "1000m" + # fleet_memory = "4096Mi" + # debug_logging = false + # min_instance_count = 1 + # max_instance_count = 3 + # exec_migration = true # Set to true to run migrations on `terraform apply` + # (e.g., when changing image_tag) + } + + ``` + + +**Key Variables to Set:** + +* `org_id`: Your GCP Organization ID. +* `billing_account_id`: Your GCP Billing Account ID. +* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** +* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** +* `project_name`: A descriptive name for the project to be created. +* `fleet_config.image_tag`: The Docker image tag for the FleetDM version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`). +* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade. +* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. + +Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. + + +## Deployment Steps + +1. **Authenticate with GCP:** + Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. + ```bash + gcloud auth application-default login + ``` + +2. **Initialize Terraform:** + Navigate to the root directory of the project (where `main.tf` is located) and run: + ```bash + terraform init + ``` + +3. **Plan the Deployment:** + Review the changes Terraform will make: + ```bash + terraform plan -out=tfplan + ``` + +4. **Apply the Configuration:** + Provision the resources: + ```bash + terraform apply tfplan + ``` + This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. + +5. **Update DNS (if not using Cloud DNS for the parent zone):** + If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. + * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): + ```bash + # First, get the project ID and zone name if you don't know them + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) + DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable + + gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ + --project=${PROJECT_ID} \ + --format="value(nameServers)" + ``` + * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. + + If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. + +## Accessing FleetDM + +Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): + +* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). +* You should be greeted by the FleetDM setup page or login screen. + +## Key Resources Created (within the `byo-project` module) + +* **VPC Network (`fleet-network`):** Custom network for Fleet resources. +* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. +* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. +* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. +* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. +* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. +* **Secret Manager Secrets:** + * `fleet-db-password-xxxx`: Stores the generated MySQL user password. + * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. +* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. +* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. +* **Cloud Run Job (`fleet-migration-job`):** For database migrations. +* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. +* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. +* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. +* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. + +(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) + +## Cleaning Up + +To destroy all resources created by this Terraform project: +1. Navigate to the root directory of the project. +2. Run the destroy command: + ```bash + terraform destroy + ``` +3. Confirm by typing `yes` when prompted. + +## Important Considerations + +* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. +* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. +* **Security:** + * Review IAM permissions granted. + * The Load Balancer is configured for HTTPS and redirects HTTP. + * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. +* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. + + +### Networking Diagram +```mermaid +graph TD + subgraph External + Internet[(Internet)] + Users[Web Console / fleetd agent] + GitHub[(GitHub - Vulnerability Resources)] + end + + subgraph "Google Cloud Platform (GCP)" + subgraph VPC [VPC] + direction LR + subgraph PublicFacing [Public Zone] + LB[Global Load Balancer] + end + subgraph PrivateZone [Private Zone] + CloudRun[Cloud Run: Fleet App] + CloudSQL[Cloud SQL for MySQL] + Redis[Memorystore for Redis] + NAT[Cloud NAT] + end + + CloudRun --> Redis + CloudRun --> CloudSQL + CloudRun --> NAT + end + end + + Users -- "fleet.yourdomain.com" --> Internet + Internet -- "fleet.yourdomain.com" --> LB + LB --> CloudRun + NAT --> GitHub + +``` \ No newline at end of file diff --git a/gcp/.terraform-docs.yml b/gcp/.terraform-docs.yml new file mode 100644 index 0000000..1d139dd --- /dev/null +++ b/gcp/.terraform-docs.yml @@ -0,0 +1 @@ +header-from: .header.md diff --git a/gcp/README.md b/gcp/README.md new file mode 100644 index 0000000..42f4694 --- /dev/null +++ b/gcp/README.md @@ -0,0 +1,284 @@ +# Terraform Google Cloud FleetDM Deployment + +This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: + +* A new Google Cloud Project (via `terraform-google-modules/project-factory`) +* VPC Network and Subnets +* Cloud NAT for outbound internet access +* Cloud SQL for MySQL (database for Fleet) +* Memorystore for Redis (caching for Fleet) +* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) +* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) +* Cloud Run v2 Service (for the main Fleet application) +* Cloud Run v2 Job (for database migrations) +* External HTTP(S) Load Balancer with managed SSL certificate +* Cloud DNS for managing the Fleet endpoint +* Appropriate IAM service accounts and permissions + +## Prerequisites + +1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). +2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). +3. **GCP Account & Permissions:** + * A Google Cloud Organization ID. + * A Billing Account ID. + * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: + * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). + * `Billing Account User` role on the Billing Account. + * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. + * Alternatively, use a Service Account with appropriate permissions. +4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). +5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. + +## Configuration + +1. **Clone the repository (if applicable) or ensure you have all the files.** +2. **Create a `terraform.tfvars` file:** + Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. + + **Example `terraform.tfvars.example`:** + ```hcl + # Required: GCP Organization and Billing + org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" + billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" + + # Required: DNS Configuration + # The public DNS zone name (e.g., "example.com." - note the trailing dot) + # This project will create/manage this zone in Cloud DNS. + dns_zone_name = "your-domain.com." + # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") + dns_record_name = "fleet.your-domain.com." + + # Optional: Project Configuration + project_name = "my-fleet-project" # Name for the new GCP project + random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) + + # Optional: Labels + labels = { + application = "fleetdm" + environment = "production" + owner = "devops-team" + } + + # --- byo-project module specific variables (passed through from root) --- + # You can override defaults from byo-project/variables.tf here if needed, + # or set values for variables that don't have defaults. + + # Example: FleetDM Configuration including migration execution + fleet_config = { + # image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version + # license_key = "YOUR_FLEET_LICENSE_KEY" + # installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique + # fleet_cpu = "1000m" + # fleet_memory = "4096Mi" + # debug_logging = false + # min_instance_count = 1 + # max_instance_count = 3 + # exec_migration = true # Set to true to run migrations on `terraform apply` + # (e.g., when changing image_tag) + } + + ``` + +**Key Variables to Set:** +* `org_id`: Your GCP Organization ID. +* `billing_account_id`: Your GCP Billing Account ID. +* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** +* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** +* `project_name`: A descriptive name for the project to be created. +* `fleet_config.image_tag`: The Docker image tag for the FleetDM version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`). +* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade. +* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. + +Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. + +## Deployment Steps + +1. **Authenticate with GCP:** + Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. + ```bash + gcloud auth application-default login + ``` + +2. **Initialize Terraform:** + Navigate to the root directory of the project (where `main.tf` is located) and run: + ```bash + terraform init + ``` + +3. **Plan the Deployment:** + Review the changes Terraform will make: + ```bash + terraform plan -out=tfplan + ``` + +4. **Apply the Configuration:** + Provision the resources: + ```bash + terraform apply tfplan + ``` + This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. + +5. **Update DNS (if not using Cloud DNS for the parent zone):** + If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. + * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): + ```bash + # First, get the project ID and zone name if you don't know them + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) + DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable + + gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ + --project=${PROJECT_ID} \ + --format="value(nameServers)" + ``` + * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. + + If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. + +## Accessing FleetDM + +Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): + +* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). +* You should be greeted by the FleetDM setup page or login screen. + +## Key Resources Created (within the `byo-project` module) + +* **VPC Network (`fleet-network`):** Custom network for Fleet resources. +* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. +* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. +* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. +* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. +* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. +* **Secret Manager Secrets:** + * `fleet-db-password-xxxx`: Stores the generated MySQL user password. + * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. +* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. +* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. +* **Cloud Run Job (`fleet-migration-job`):** For database migrations. +* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. +* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. +* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. +* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. + +(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) + +## Cleaning Up + +To destroy all resources created by this Terraform project: +1. Navigate to the root directory of the project. +2. Run the destroy command: + ```bash + terraform destroy + ``` +3. Confirm by typing `yes` when prompted. + +## Important Considerations + +* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. +* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. +* **Security:** + * Review IAM permissions granted. + * The Load Balancer is configured for HTTPS and redirects HTTP. + * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. +* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. + +### Networking Diagram +```mermaid +graph TD + subgraph External + Internet[(Internet)] + Users[Web Console / fleetd agent] + GitHub[(GitHub - Vulnerability Resources)] + end + + subgraph "Google Cloud Platform (GCP)" + subgraph VPC [VPC] + direction LR + subgraph PublicFacing [Public Zone] + LB[Global Load Balancer] + end + subgraph PrivateZone [Private Zone] + CloudRun[Cloud Run: Fleet App] + CloudSQL[Cloud SQL for MySQL] + Redis[Memorystore for Redis] + NAT[Cloud NAT] + end + + CloudRun --> Redis + CloudRun --> CloudSQL + CloudRun --> NAT + end + end + + Users -- "fleet.yourdomain.com" --> Internet + Internet -- "fleet.yourdomain.com" --> LB + LB --> CloudRun + NAT --> GitHub + +``` + +## Requirements + +| Name | Version | +|---------------------------------------------------------------------------|---------| +| [terraform](#requirement\_terraform) | ~> 1.11 | +| [google](#requirement\_google) | 6.35.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|-------------------------------------------------------------------------------------|-------------------------------------------------|-----------| +| [fleet](#module\_fleet) | ./byo-project | n/a | +| [project\_factory](#module\_project\_factory) | terraform-google-modules/project-factory/google | ~> 18.0.0 | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:| +| [billing\_account\_id](#input\_billing\_account\_id) | billing account id | `any` | n/a | yes | +| [cache\_config](#input\_cache\_config) | Configuration for the Memorystore (Redis) instance. |
object({
name = string
tier = string
engine_version = string
connect_mode = string
memory_size = number
})
|
{
"connect_mode": "PRIVATE_SERVICE_ACCESS",
"engine_version": null,
"memory_size": 1,
"name": "fleet-cache",
"tier": "STANDARD_HA"
}
| no | +| [database\_config](#input\_database\_config) | Configuration for the Cloud SQL (MySQL) instance. |
object({
name = string
database_name = string
database_user = string
collation = string
charset = string
deletion_protection = bool
database_version = string
tier = string
})
|
{
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci",
"database_name": "fleet",
"database_user": "fleet",
"database_version": "MYSQL_8_0",
"deletion_protection": false,
"name": "fleet-mysql",
"tier": "db-n1-standard-1"
}
| no | +| [dns\_record\_name](#input\_dns\_record\_name) | The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.') | `string` | n/a | yes | +| [dns\_zone\_name](#input\_dns\_zone\_name) | The DNS name of the managed zone (e.g., 'my-fleet-infra.com.') | `string` | n/a | yes | +| [fleet\_config](#input\_fleet\_config) | Configuration for the Fleet application deployment. |
object({
installers_bucket_name = string
image_tag = string
fleet_cpu = string
fleet_memory = string
debug_logging = bool
license_key = optional(string)
min_instance_count = number
max_instance_count = number
exec_migration = bool
extra_env_vars = optional(map(string))
extra_secret_env_vars = optional(map(object({
secret = string
version = string
})))
})
|
{
"debug_logging": false,
"exec_migration": true,
"extra_env_vars": {},
"extra_secret_env_vars": {},
"fleet_cpu": "1000m",
"fleet_memory": "4096Mi",
"image_tag": "fleetdm/fleet:v4.67.3",
"installers_bucket_name": "fleet-installers-gcp-test-1",
"max_instance_count": 5,
"min_instance_count": 1
}
| no | +| [fleet\_image](#input\_fleet\_image) | n/a | `string` | `"v4.67.3"` | no | +| [labels](#input\_labels) | resource labels | `map(string)` |
{
"application": "fleet"
}
| no | +| [location](#input\_location) | The general location for resources, e.g., 'us' for GCS buckets. | `string` | `"us"` | no | +| [org\_id](#input\_org\_id) | organization id | `any` | n/a | yes | +| [prefix](#input\_prefix) | A prefix used for naming resources within the byo-project module. | `string` | `"fleet"` | no | +| [project\_name](#input\_project\_name) | n/a | `string` | `"fleet"` | no | +| [random\_project\_id](#input\_random\_project\_id) | n/a | `bool` | `true` | no | +| [region](#input\_region) | The GCP region for regional resources. | `string` | `"us-central1"` | no | +| [vpc\_config](#input\_vpc\_config) | Configuration for the VPC network and subnets. |
object({
network_name = string
subnets = list(object({
subnet_name = string
subnet_ip = string
subnet_region = string
subnet_private_access = bool
}))
})
|
{
"network_name": "fleet-network",
"subnets": [
{
"subnet_ip": "10.10.10.0/24",
"subnet_name": "fleet-subnet",
"subnet_private_access": true,
"subnet_region": "us-central1"
}
]
}
| no | + +## Outputs + +| Name | Description | +|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| +| [cloud\_router\_name](#output\_cloud\_router\_name) | The name of the Cloud Router created for NAT. | +| [cloud\_run\_service\_location](#output\_cloud\_run\_service\_location) | The location of the deployed Fleet Cloud Run service. | +| [cloud\_run\_service\_name](#output\_cloud\_run\_service\_name) | The name of the deployed Fleet Cloud Run service. | +| [dns\_managed\_zone\_name](#output\_dns\_managed\_zone\_name) | The name of the Cloud DNS managed zone created for Fleet. | +| [dns\_managed\_zone\_name\_servers](#output\_dns\_managed\_zone\_name\_servers) | The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these. | +| [fleet\_application\_url](#output\_fleet\_application\_url) | The primary URL to access the Fleet application (via the Load Balancer). | +| [fleet\_service\_account\_email](#output\_fleet\_service\_account\_email) | The email address of the service account used by the Fleet Cloud Run service. | +| [load\_balancer\_ip\_address](#output\_load\_balancer\_ip\_address) | The external IP address of the HTTP(S) Load Balancer. | +| [mysql\_instance\_connection\_name](#output\_mysql\_instance\_connection\_name) | The connection name for the Cloud SQL instance (used by Cloud SQL Proxy). | +| [mysql\_instance\_name](#output\_mysql\_instance\_name) | The name of the Cloud SQL for MySQL instance. | +| [redis\_host](#output\_redis\_host) | The host IP address of the Memorystore for Redis instance. | +| [redis\_instance\_name](#output\_redis\_instance\_name) | The name of the Memorystore for Redis instance. | +| [redis\_port](#output\_redis\_port) | The port number of the Memorystore for Redis instance. | +| [software\_installers\_bucket\_name](#output\_software\_installers\_bucket\_name) | The name of the GCS bucket for Fleet software installers. | +| [software\_installers\_bucket\_url](#output\_software\_installers\_bucket\_url) | The gsutil URL of the GCS bucket for Fleet software installers. | +| [vpc\_network\_name](#output\_vpc\_network\_name) | The name of the VPC network created. | +| [vpc\_network\_self\_link](#output\_vpc\_network\_self\_link) | The self-link of the VPC network created. | +| [vpc\_subnets\_names](#output\_vpc\_subnets\_names) | List of subnet names created in the VPC. | diff --git a/gcp/byo-project/cache.tf b/gcp/byo-project/cache.tf new file mode 100644 index 0000000..e2248bc --- /dev/null +++ b/gcp/byo-project/cache.tf @@ -0,0 +1,18 @@ +module "memstore" { + source = "terraform-google-modules/memorystore/google" + version = "~> 14.0" + + name = var.cache_config.name + redis_version = var.cache_config.engine_version + tier = var.cache_config.tier + memory_size_gb = var.cache_config.memory_size + + project_id = var.project_id + region = var.region + enable_apis = true + transit_encryption_mode = "DISABLED" + authorized_network = module.vpc.network_id + connect_mode = var.cache_config.connect_mode + + depends_on = [module.private-service-access.peering_completed] +} \ No newline at end of file diff --git a/gcp/byo-project/cloud_run.tf b/gcp/byo-project/cloud_run.tf new file mode 100644 index 0000000..cf115ab --- /dev/null +++ b/gcp/byo-project/cloud_run.tf @@ -0,0 +1,225 @@ + +locals { + # --- Shared Container Configuration --- + fleet_image_tag = var.fleet_config.image_tag + fleet_resources_limits = { + cpu = var.fleet_config.fleet_cpu + memory = var.fleet_config.fleet_memory + } + fleet_secrets_env_vars = merge(var.fleet_config.extra_secret_env_vars, { + FLEET_MYSQL_PASSWORD = { + secret = google_secret_manager_secret.database_password.secret_id + version = "latest" + }, + FLEET_SERVER_PRIVATE_KEY = { + secret = google_secret_manager_secret.private_key.secret_id + version = "latest" + } + }) + fleet_env_vars = merge(var.fleet_config.extra_env_vars, { + FLEET_LICENSE_KEY = var.fleet_config.license_key + FLEET_SERVER_FORCE_H2C = "true" + FLEET_MYSQL_PROTOCOL = "tcp" + FLEET_MYSQL_ADDRESS = "${module.mysql.private_ip_address}:3306" + FLEET_MYSQL_USERNAME = var.database_config.database_user + FLEET_MYSQL_DATABASE = var.database_config.database_name + FLEET_REDIS_ADDRESS = "${module.memstore.host}:${module.memstore.port}" + FLEET_REDIS_USE_TLS = "false" + #FLEET_UPGRADES_ALLOW_MISSING_MIGRATIONS = "1" + FLEET_LOGGING_JSON = "true" + FLEET_LOGGING_DEBUG = var.fleet_config.debug_logging + FLEET_SERVER_TLS = "false" + FLEET_S3_SOFTWARE_INSTALLERS_BUCKET = google_storage_bucket.software_installers.id + FLEET_S3_SOFTWARE_INSTALLERS_ACCESS_KEY_ID = google_storage_hmac_key.key.access_id + FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY = google_storage_hmac_key.key.secret + FLEET_S3_SOFTWARE_INSTALLERS_ENDPOINT_URL = "https://storage.googleapis.com" + FLEET_S3_SOFTWARE_INSTALLERS_FORCE_S3_PATH_STYLE = "true" + FLEET_S3_SOFTWARE_INSTALLERS_REGION = var.region + }) + + fleet_vpc_network_id = module.vpc.network_id + # Use the direct construction for the subnet ID key as discussed + fleet_vpc_subnet_id = "fleet-subnet" +} + +module "fleet-service" { + source = "GoogleCloudPlatform/cloud-run/google//modules/v2" + version = "~> 0.17" + + service_name = "fleet-api" + project_id = var.project_id + location = var.region + create_service_account = false + service_account = google_service_account.fleet_run_sa.email + enable_prometheus_sidecar = false + cloud_run_deletion_protection = false + + vpc_access = { + network_interfaces = { + network = local.fleet_vpc_network_id + subnetwork = local.fleet_vpc_subnet_id + } + egress = "ALL_TRAFFIC" + } + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + timeout = "300s" + service_scaling = { + min_instance_count = 0 + } + template_scaling = { + min_instance_count = 0 + max_instance_count = 0 + } + + containers = [ + { + container_image = local.fleet_image_tag + ports = { + name = "h2c" + container_port = 8080 + } + # container_command = ["/bin/sh"] + # container_args = [ + # "-c", + # "fleet prepare --no-prompt=true db; exec fleet serve" + # ] + + startup_probe = { + initial_delay_seconds = 30 + timeout_seconds = 2 + period_seconds = 60 + failure_threshold = 3 + + tcp_socket = { + port = 8080 + } + } + + liveness_probe = { + initial_delay_seconds = 30 + timeout_seconds = 2 + failure_threshold = 3 + period_seconds = 60 + http_get = { + path = "/healthz" + http_headers = [] + } + } + + resources = { + limits = local.fleet_resources_limits + } + + env_vars = local.fleet_env_vars + env_secret_vars = local.fleet_secrets_env_vars + } + ] +} + +# --- Cloud Run Job (Migrations) --- +resource "google_cloud_run_v2_job" "fleet_migration_job" { + + name = "fleet-migration" + location = var.region + project = var.project_id + + template { + template { # Double template for jobs + service_account = google_service_account.fleet_run_sa.email # Defined in iam.tf + + # Define vpc_access block directly + vpc_access { + network_interfaces { + network = local.fleet_vpc_network_id + subnetwork = local.fleet_vpc_subnet_id + } + egress = "ALL_TRAFFIC" + } + + timeout = "3600s" + + containers { + image = local.fleet_image_tag + # Define resources block directly + resources { + limits = local.fleet_resources_limits + } + + dynamic "env" { + for_each = local.fleet_env_vars + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = local.fleet_secrets_env_vars + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + + command = ["fleet"] + args = ["prepare", "db", "--no-prompt=true"] + } + } + } + + depends_on = [ + google_service_account.fleet_run_sa, + google_secret_manager_secret_version.database_password, + ] +} + +data "google_client_config" "default" {} + +resource "terracurl_request" "exec" { + count = var.fleet_config.exec_migration ? 1 : 0 + name = "exec-job" + url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}:run" + method = "POST" + headers = { + Authorization = "Bearer ${data.google_client_config.default.access_token}" + Content-Type = "application/json", + } + response_codes = [200] + // no-op destroy + // we don't use terracurl_request data source as that will result in + // repeated job runs on every refresh + destroy_url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}" + destroy_method = "GET" + destroy_response_codes = [200] + destroy_headers = { + Authorization = "Bearer ${data.google_client_config.default.access_token}" + Content-Type = "application/json", + } +} + +resource "google_compute_region_network_endpoint_group" "neg" { + name = "${var.prefix}-neg" + region = var.region + project = var.project_id + network_endpoint_type = "SERVERLESS" # This type works for Cloud Run v2 services + cloud_run { + service = module.fleet-service.service_name + } + depends_on = [module.fleet-service] +} + +data "google_project" "project" { + project_id = var.project_id +} + +resource "google_cloud_run_v2_service_iam_member" "allow_lb_invoker" { + project = var.project_id + location = module.fleet-service.location + name = module.fleet-service.service_name + role = "roles/run.invoker" + member = "allUsers" +} \ No newline at end of file diff --git a/gcp/byo-project/database.tf b/gcp/byo-project/database.tf new file mode 100644 index 0000000..dfa3815 --- /dev/null +++ b/gcp/byo-project/database.tf @@ -0,0 +1,43 @@ +resource "random_id" "suffix" { + byte_length = 5 +} + + +module "private-service-access" { + source = "terraform-google-modules/sql-db/google//modules/private_service_access" + version = "~> 25.0" + + project_id = var.project_id + vpc_network = module.vpc.network_name + deletion_policy = "ABANDON" +} + +module "mysql" { + source = "terraform-google-modules/sql-db/google//modules/mysql" + version = "~> 25.0" + + name = var.database_config.name + project_id = var.project_id + deletion_protection = var.database_config.deletion_protection + database_version = var.database_config.database_version + tier = var.database_config.tier + region = var.region + random_instance_name = true + enable_default_user = true + enable_default_db = true + user_name = var.database_config.database_user + db_name = var.database_config.database_name + db_collation = var.database_config.collation + db_charset = var.database_config.charset + + ip_configuration = { + ipv4_enabled = false + # We never set authorized networks, we need all connections via the + # public IP to be mediated by Cloud SQL. + authorized_networks = [] + require_ssl = false + private_network = module.vpc.network_self_link + } + + module_depends_on = [module.private-service-access.peering_completed] +} \ No newline at end of file diff --git a/gcp/byo-project/iam.tf b/gcp/byo-project/iam.tf new file mode 100644 index 0000000..4a58178 --- /dev/null +++ b/gcp/byo-project/iam.tf @@ -0,0 +1,50 @@ + +# ------------------------------------- +# Service Account for Cloud Run Service & Job +# ------------------------------------- + +resource "google_service_account" "fleet_run_sa" { + project = var.project_id + account_id = "${var.prefix}-run-sa" + display_name = "Service Account for Fleet Cloud Run Service and Jobs" +} + +resource "google_project_iam_member" "fleet_run_sa_sql_instance_user" { + project = var.project_id + role = "roles/cloudsql.instanceUser" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + +# Recommended for Cloud Run standard logging +resource "google_project_iam_member" "fleet_run_sa_log_writer" { + project = var.project_id + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + +# Recommended for Cloud Run standard metrics +resource "google_project_iam_member" "fleet_run_sa_monitoring_writer" { + project = var.project_id + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + + +resource "google_secret_manager_secret_iam_member" "fleet_run_sa_db_secret_access" { + project = var.project_id + secret_id = google_secret_manager_secret.database_password.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + + depends_on = [google_secret_manager_secret.database_password] +} + +resource "google_secret_manager_secret_iam_member" "fleet_run_sa_private_key_secret_access" { + project = var.project_id + secret_id = google_secret_manager_secret.private_key.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + + depends_on = [google_secret_manager_secret.private_key] +} + diff --git a/gcp/byo-project/loadbalancer.tf b/gcp/byo-project/loadbalancer.tf new file mode 100644 index 0000000..8b25541 --- /dev/null +++ b/gcp/byo-project/loadbalancer.tf @@ -0,0 +1,65 @@ +locals { + # Clean the DNS record name for use in managed SSL cert domains (remove trailing dot) + managed_ssl_domain = trim(var.dns_record_name, ".") +} + +# Create/Manage the DNS Zone in Cloud DNS +resource "google_dns_managed_zone" "fleet_dns_zone" { + project = var.project_id + name = "${var.prefix}-zone" + dns_name = var.dns_zone_name +} + +# Configure the External HTTP(S) Load Balancer +module "fleet_lb" { + source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs" + version = "~> 12.0" + + project = var.project_id + name = "${var.prefix}-lb" # e.g., fleet-lb + + # SSL Configuration + ssl = true + https_redirect = true # Enforce HTTPS + managed_ssl_certificate_domains = [local.managed_ssl_domain] + + # Backend Configuration + backends = { + default = { + description = "Backend for Fleet Cloud Run service" + enable_cdn = false # Set to true if you want Cloud CDN + protocol = "HTTP" + groups = [ + { + group = google_compute_region_network_endpoint_group.neg.id + } + ] + + log_config = { + enable = true + sample_rate = 1.0 # Log all requests + } + + # IAP (Identity-Aware Proxy) - disabled by default + iap_config = { + enable = false + } + } + } + + depends_on = [google_compute_region_network_endpoint_group.neg] +} + +# Create the DNS A Record for the Load Balancer +resource "google_dns_record_set" "fleet_dns_record" { + project = var.project_id + managed_zone = google_dns_managed_zone.fleet_dns_zone.name + name = var.dns_record_name + type = "A" + ttl = 300 # Time-to-live in seconds + + # Point to the external IP address of the created load balancer + rrdatas = [module.fleet_lb.external_ip] + + depends_on = [module.fleet_lb] +} \ No newline at end of file diff --git a/gcp/byo-project/main.tf b/gcp/byo-project/main.tf new file mode 100644 index 0000000..552122c --- /dev/null +++ b/gcp/byo-project/main.tf @@ -0,0 +1,16 @@ +terraform { + required_version = "~> 1.11" + required_providers { + google = { + source = "hashicorp/google" + version = "6.35.0" + } + + terracurl = { + source = "devops-rob/terracurl" + version = "~> 1.0" + } + } +} + +data "google_client_config" "current" {} diff --git a/gcp/byo-project/outputs.tf b/gcp/byo-project/outputs.tf new file mode 100644 index 0000000..7b67b39 --- /dev/null +++ b/gcp/byo-project/outputs.tf @@ -0,0 +1,89 @@ +output "fleet_application_url" { + description = "The primary URL to access the Fleet application (via the Load Balancer)." + value = "https://${google_dns_record_set.fleet_dns_record.name}" +} + +output "load_balancer_ip_address" { + description = "The external IP address of the HTTP(S) Load Balancer." + value = module.fleet_lb.external_ip +} + +output "cloud_run_service_name" { + description = "The name of the deployed Fleet Cloud Run service." + value = module.fleet-service.service_name +} + +output "cloud_run_service_location" { + description = "The location of the deployed Fleet Cloud Run service." + value = module.fleet-service.location // Check the actual output name +} + +output "mysql_instance_name" { + description = "The name of the Cloud SQL for MySQL instance." + value = module.mysql.instance_name +} + +output "mysql_instance_connection_name" { + description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)." + value = module.mysql.instance_connection_name +} + +output "redis_instance_name" { + description = "The name of the Memorystore for Redis instance." + value = module.memstore.id +} + +output "redis_host" { + description = "The host IP address of the Memorystore for Redis instance." + value = module.memstore.host +} + +output "redis_port" { + description = "The port number of the Memorystore for Redis instance." + value = module.memstore.port +} + +output "software_installers_bucket_name" { + description = "The name of the GCS bucket for Fleet software installers." + value = google_storage_bucket.software_installers.name +} + +output "software_installers_bucket_url" { + description = "The gsutil URL of the GCS bucket for Fleet software installers." + value = google_storage_bucket.software_installers.url +} + +output "fleet_service_account_email" { + description = "The email address of the service account used by the Fleet Cloud Run service." + value = google_service_account.fleet_run_sa.email +} + +output "dns_managed_zone_name" { + description = "The name of the Cloud DNS managed zone created for Fleet." + value = google_dns_managed_zone.fleet_dns_zone.name +} + +output "dns_managed_zone_name_servers" { + description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these." + value = google_dns_managed_zone.fleet_dns_zone.name_servers +} + +output "vpc_network_name" { + description = "The name of the VPC network created." + value = module.vpc.network_name +} + +output "vpc_network_self_link" { + description = "The self-link of the VPC network created." + value = module.vpc.network_self_link +} + +output "vpc_subnets_names" { + description = "List of subnet names created in the VPC." + value = module.vpc.subnets_names +} + +output "cloud_router_name" { + description = "The name of the Cloud Router created for NAT." + value = module.cloud_router.router.name +} diff --git a/gcp/byo-project/secrets.tf b/gcp/byo-project/secrets.tf new file mode 100644 index 0000000..c465e61 --- /dev/null +++ b/gcp/byo-project/secrets.tf @@ -0,0 +1,33 @@ +resource "random_pet" "suffix" { + length = 1 +} + +resource "random_password" "private_key" { + length = 32 +} + +resource "google_secret_manager_secret" "database_password" { + project = var.project_id + secret_id = "fleet-db-password-${random_pet.suffix.id}" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "database_password" { + secret = google_secret_manager_secret.database_password.name + secret_data = module.mysql.generated_user_password +} + +resource "google_secret_manager_secret" "private_key" { + project = var.project_id + secret_id = "fleet-private-key-${random_pet.suffix.id}" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "private_key" { + secret = google_secret_manager_secret.private_key.name + secret_data = random_password.private_key.result +} \ No newline at end of file diff --git a/gcp/byo-project/storage.tf b/gcp/byo-project/storage.tf new file mode 100644 index 0000000..37ab6b2 --- /dev/null +++ b/gcp/byo-project/storage.tf @@ -0,0 +1,19 @@ +resource "google_storage_hmac_key" "key" { + project = var.project_id + service_account_email = google_service_account.fleet_run_sa.email +} + +resource "google_storage_bucket" "software_installers" { + project = var.project_id + name = var.fleet_config.installers_bucket_name + location = var.location + force_destroy = true + + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "hmac_sa_storage_admin" { + bucket = google_storage_bucket.software_installers.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} \ No newline at end of file diff --git a/gcp/byo-project/variables.tf b/gcp/byo-project/variables.tf new file mode 100644 index 0000000..532a2e0 --- /dev/null +++ b/gcp/byo-project/variables.tf @@ -0,0 +1,120 @@ +variable "project_id" { + description = "GCP project ID" +} + +variable "location" { + default = "us" +} + +variable "region" { + default = "us-central1" +} + +variable "prefix" { + default = "fleet" +} + +variable "dns_zone_name" { + description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')" + type = string +} + +variable "dns_record_name" { + description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')" + type = string +} + +variable "cache_config" { + type = object({ + name = string + tier = string + engine_version = string + connect_mode = string + memory_size = number + }) + default = { + name = "fleet-cache" + tier = "STANDARD_HA" + engine_version = null // defaults to version 7 + connect_mode = "PRIVATE_SERVICE_ACCESS" + memory_size = 1 + } +} + +variable "database_config" { + type = object({ + name = string + database_name = string + database_user = string + collation = string + charset = string + deletion_protection = bool + database_version = string + tier = string + }) + default = { + name = "fleet-mysql" + database_name = "fleet" + database_user = "fleet" + collation = "utf8mb4_unicode_ci" + charset = "utf8mb4" + deletion_protection = false + database_version = "MYSQL_8_0" + tier = "db-n1-standard-1" + } +} + +variable "vpc_config" { + type = object({ + network_name = string + subnets = list(object({ + subnet_name = string + subnet_ip = string + subnet_region = string + subnet_private_access = bool + })) + }) + + default = { + network_name = "fleet-network" + subnets = [ + { + subnet_name = "fleet-subnet" + subnet_ip = "10.10.10.0/24" + subnet_region = "us-central1" + subnet_private_access = true + } + ] + } + +} +variable "fleet_config" { + type = object({ + installers_bucket_name = string + image_tag = string + fleet_cpu = string + fleet_memory = string + debug_logging = bool + license_key = optional(string) + min_instance_count = number + max_instance_count = number + exec_migration = bool + extra_env_vars = optional(map(string)) + extra_secret_env_vars = optional(map(object({ + secret = string + version = string + }))) + }) + default = { + image_tag = "fleetdm/fleet:v4.67.3" + installers_bucket_name = "fleet-installers-gcp-test-1" // todo fix + fleet_cpu = "1000m" + fleet_memory = "4096Mi" + debug_logging = false + min_instance_count = 1 + max_instance_count = 5 + exec_migration = true + extra_env_vars = {} + extra_secret_env_vars = {} + } +} diff --git a/gcp/byo-project/vpc.tf b/gcp/byo-project/vpc.tf new file mode 100644 index 0000000..5e7a5e4 --- /dev/null +++ b/gcp/byo-project/vpc.tf @@ -0,0 +1,30 @@ +# Example from your vpc.tf (ensure this part is correct) + + +locals { + network_name = "${var.prefix}-network" + subnet_name = "${var.prefix}-subnet" +} + + +module "vpc" { + source = "terraform-google-modules/network/google" + version = "11.0.0" + + project_id = var.project_id + network_name = var.vpc_config.network_name + subnets = var.vpc_config.subnets +} + +module "cloud_router" { + source = "terraform-google-modules/cloud-router/google" + version = "7.0" + name = "${var.prefix}-cloud-router" + project = var.project_id + network = module.vpc.network_name + region = var.region + + nats = [{ + name = "${var.prefix}-vpc-nat" + }] +} diff --git a/gcp/main.tf b/gcp/main.tf new file mode 100644 index 0000000..338a4b1 --- /dev/null +++ b/gcp/main.tf @@ -0,0 +1,63 @@ +terraform { + required_version = "~> 1.11" + required_providers { + google = { + source = "hashicorp/google" + version = "6.35.0" + } + } +} + +provider "google" { + # Credentials used here need Org/Folder level permissions + default_labels = var.labels +} + + +module "project_factory" { + source = "terraform-google-modules/project-factory/google" + version = "~> 18.0.0" + + name = var.project_name + random_project_id = var.random_project_id + org_id = var.org_id + billing_account = var.billing_account_id + + default_service_account = "delete" + + # Enable baseline APIs needed by most projects + your app stack + activate_apis = [ + "compute.googleapis.com", + "sqladmin.googleapis.com", + "redis.googleapis.com", + "run.googleapis.com", + "vpcaccess.googleapis.com", + "secretmanager.googleapis.com", + "storage.googleapis.com", + "dns.googleapis.com", + "iam.googleapis.com", + "cloudresourcemanager.googleapis.com", + "serviceusage.googleapis.com", + "servicenetworking.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "memorystore.googleapis.com", + "serviceconsumermanagement.googleapis.com", + "networkconnectivity.googleapis.com" + ] + + labels = var.labels +} + +module "fleet" { + source = "./byo-project" + project_id = module.project_factory.project_id + dns_record_name = var.dns_record_name + dns_zone_name = var.dns_zone_name + vpc_config = var.vpc_config + fleet_config = var.fleet_config + cache_config = var.cache_config + database_config = var.database_config + region = var.region + location = var.location +} \ No newline at end of file diff --git a/gcp/outputs.tf b/gcp/outputs.tf new file mode 100644 index 0000000..2fdc2e0 --- /dev/null +++ b/gcp/outputs.tf @@ -0,0 +1,89 @@ +output "fleet_application_url" { + description = "The primary URL to access the Fleet application (via the Load Balancer)." + value = module.fleet.fleet_application_url +} + +output "load_balancer_ip_address" { + description = "The external IP address of the HTTP(S) Load Balancer." + value = module.fleet.load_balancer_ip_address +} + +output "cloud_run_service_name" { + description = "The name of the deployed Fleet Cloud Run service." + value = module.fleet.cloud_run_service_name +} + +output "cloud_run_service_location" { + description = "The location of the deployed Fleet Cloud Run service." + value = module.fleet.cloud_run_service_location +} + +output "mysql_instance_name" { + description = "The name of the Cloud SQL for MySQL instance." + value = module.fleet.mysql_instance_name +} + +output "mysql_instance_connection_name" { + description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)." + value = module.fleet.mysql_instance_connection_name +} + +output "redis_instance_name" { + description = "The name of the Memorystore for Redis instance." + value = module.fleet.redis_instance_name +} + +output "redis_host" { + description = "The host IP address of the Memorystore for Redis instance." + value = module.fleet.redis_host +} + +output "redis_port" { + description = "The port number of the Memorystore for Redis instance." + value = module.fleet.redis_port +} + +output "software_installers_bucket_name" { + description = "The name of the GCS bucket for Fleet software installers." + value = module.fleet.software_installers_bucket_name +} + +output "software_installers_bucket_url" { + description = "The gsutil URL of the GCS bucket for Fleet software installers." + value = module.fleet.software_installers_bucket_url +} + +output "fleet_service_account_email" { + description = "The email address of the service account used by the Fleet Cloud Run service." + value = module.fleet.fleet_service_account_email +} + +output "dns_managed_zone_name" { + description = "The name of the Cloud DNS managed zone created for Fleet." + value = module.fleet.dns_managed_zone_name +} + +output "dns_managed_zone_name_servers" { + description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these." + value = module.fleet.dns_managed_zone_name_servers +} + +output "vpc_network_name" { + description = "The name of the VPC network created." + value = module.fleet.vpc_network_name +} + +output "vpc_network_self_link" { + description = "The self-link of the VPC network created." + value = module.fleet.vpc_network_self_link +} + +output "vpc_subnets_names" { + description = "List of subnet names created in the VPC." + value = module.fleet.vpc_subnets_names +} + +output "cloud_router_name" { + description = "The name of the Cloud Router created for NAT." + value = module.fleet.cloud_router_name +} diff --git a/gcp/variables.tf b/gcp/variables.tf new file mode 100644 index 0000000..be4b9e0 --- /dev/null +++ b/gcp/variables.tf @@ -0,0 +1,150 @@ +variable "project_name" { + default = "fleet" +} + +variable "org_id" { + description = "organization id" +} + +variable "billing_account_id" { + description = "billing account id" +} + +variable "labels" { + description = "resource labels" + default = { application = "fleet" } + type = map(string) +} + +variable "fleet_image" { + default = "v4.67.3" +} + +variable "dns_zone_name" { + description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')" + type = string +} + +variable "dns_record_name" { + description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')" + type = string +} + +variable "random_project_id" { + default = true +} +variable "location" { + description = "The general location for resources, e.g., 'us' for GCS buckets." + type = string + default = "us" +} + +variable "region" { + description = "The GCP region for regional resources." + type = string + default = "us-central1" +} + +variable "prefix" { + description = "A prefix used for naming resources within the byo-project module." + type = string + default = "fleet" +} + +variable "cache_config" { + description = "Configuration for the Memorystore (Redis) instance." + type = object({ + name = string + tier = string + engine_version = string + connect_mode = string + memory_size = number + }) + default = { + name = "fleet-cache" + tier = "STANDARD_HA" + engine_version = null // defaults to version 7 + connect_mode = "PRIVATE_SERVICE_ACCESS" + memory_size = 1 + } +} + +variable "database_config" { + description = "Configuration for the Cloud SQL (MySQL) instance." + type = object({ + name = string + database_name = string + database_user = string + collation = string + charset = string + deletion_protection = bool + database_version = string + tier = string + }) + default = { + name = "fleet-mysql" + database_name = "fleet" + database_user = "fleet" + collation = "utf8mb4_unicode_ci" + charset = "utf8mb4" + deletion_protection = false + database_version = "MYSQL_8_0" + tier = "db-n1-standard-1" + } +} + +variable "vpc_config" { + description = "Configuration for the VPC network and subnets." + type = object({ + network_name = string + subnets = list(object({ + subnet_name = string + subnet_ip = string + subnet_region = string + subnet_private_access = bool + })) + }) + default = { + network_name = "fleet-network" + subnets = [ + { + subnet_name = "fleet-subnet" + subnet_ip = "10.10.10.0/24" + subnet_region = "us-central1" + subnet_private_access = true + } + ] + } +} + +variable "fleet_config" { + description = "Configuration for the Fleet application deployment." + type = object({ + installers_bucket_name = string + image_tag = string + fleet_cpu = string + fleet_memory = string + debug_logging = bool + license_key = optional(string) + min_instance_count = number + max_instance_count = number + exec_migration = bool + extra_env_vars = optional(map(string)) + extra_secret_env_vars = optional(map(object({ + secret = string + version = string + }))) + }) + default = { + image_tag = "fleetdm/fleet:v4.67.3" + installers_bucket_name = "fleet-installers-gcp-test-1" # Bucket names must be globally unique + fleet_cpu = "1000m" + fleet_memory = "4096Mi" + debug_logging = false + min_instance_count = 1 + max_instance_count = 5 + exec_migration = true + extra_env_vars = {} + extra_secret_env_vars = {} + } +}