Skip to content

Commit

Permalink
Add CV user guide page (#8135) (#14886)
Browse files Browse the repository at this point in the history
* Add CV guide page

* Fix h2 headers in markdown

* Fix HCL formatting

* Fix name of `plantimestamp` function

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Jun 12, 2023
1 parent d3e2427 commit 2069bb2
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/8135.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:none

```
223 changes: 223 additions & 0 deletions website/docs/guides/google-continuous-validation.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
page_title: "Using Terraform Cloud's Continuous Validation feature with the Google Provider"
description: |-
Continuous validation helps identify issues immediately and continuously instead of waiting until customers encounter problems. This guide shows how continuous validation can be used with the Google provider.
---

# Using Terraform Cloud's Continuous Validation feature with the Google Provider

The Continuous Validation feature in Terraform Cloud (TFC) allows users to make assertions about their infrastructure between applied runs. This helps users to identify issues at the time they first appear and avoid situations where a change is only identified during a future terraform plan/apply or once it causes a user-facing problem.

Users can add checks to their Terraform configuration using an HCL language feature called [check{} blocks](https://developer.hashicorp.com/terraform/language/checks). Check blocks contain assertions that are defined with a custom condition expression and an error message. When the condition expression evaluates to true the check passes, but when the expression evaluates to false Terraform will show a warning message that includes the user-defined error message.

Custom conditions can be created using data from Terraform providers’ resources and data sources. Data can also be combined from multiple sources; for example, you can use checks to monitor expirable resources by comparing a resource’s expiration date attribute to the current time returned by Terraform’s built-in time functions. These include the [plantimestamp function](https://developer.hashicorp.com/terraform/language/functions/plantimestamp), which was added in Terraform 1.5.

For more information about continuous validation visit the [Workspace Health page in the Terraform Cloud documentation](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/health#continuous-validation).

Below, this guide shows examples of how data returned by the Google provider can be used to define checks in your Terraform configuration. In each example it is assumed that the Google provider is configured with a default project, region, and zone.

~> Check blocks and the plantime function are available in Terraform 1.5 and later

## Example - Assert a VM is in a running state (`google_compute_instance`)

VM instances provisioned using Compute Engine can pass through several states as part of the [VM instance lifecycle](https://cloud.google.com/compute/docs/instances/instance-life-cycle). Once a VM is provisioned it could experience an error, or a user could suspend or stop that VM in the Google Cloud console, without that change being detected until the next Terraform plan is generated. Continuous validation can be used to assert the state of a VM and detect if there are any unexpected status changes that occur out-of-band.

The example below shows how a check block can be used to assert that a VM is in the running state.

You can force the check to fail in this example by provisioning the VM, manually stopping it in the Google Cloud console, and then triggering a health check in TFC. The check will fail and report that the VM is not running.

```hcl
data "google_compute_network" "default" {
name = "default"
}
resource "google_compute_instance" "vm_instance" {
name = "my-instance"
machine_type = "f1-micro"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
}
}
network_interface {
network = data.google_compute_network.default.name
access_config {
}
}
}
check "check_vm_status" {
data "google_compute_instance" "vm_instance" {
name = google_compute_instance.vm_instance.name
}
assert {
condition = data.google_compute_instance.vm_instance.current_status == "RUNNING"
error_message = format("Provisioned VMs should be in a RUNNING status, instead the VM `%s` has status: %s",
data.google_compute_instance.vm_instance.name,
data.google_compute_instance.vm_instance.current_status
)
}
}
```

## Example - Check if a certificate will expire within a certain timeframe (`google_privateca_certificate`)

Certificates can be provisioned using either the Certificate Manager, Certificate Authority Service (‘Private CA’), and Compute Engine APIs. In this example we provision a certificate via the Certificate Authority Service that has a user-supplied [lifetime argument](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/privateca_certificate#lifetime). After the lifetime duration passes the certificate is automatically deleted in GCP. By creating a check that asserts the certificate’s expiration date is more than 30 days away we can be notified by TFC health checks when the certificate is approaching expiration and needs manual intervention.

In the example below we provision a certificate with a lifetime of 30 days and 2 minutes (see `local.month_and_2min_in_second_duration`) and create a check that asserts certificates should be valid for the next month (see `local.month_in_hour_duration`).

We can see the check begin to fail by waiting 2 minutes after the certificate is provisioned and then triggering a health check in TFC. The check will fail and report that the certificate is due to expire in less than a month.

```hcl
locals {
month_in_hour_duration = "${24 * 30}h"
month_and_2min_in_second_duration = "${(60 * 60 * 24 * 30) + (60 * 2)}s"
}
resource "tls_private_key" "example" {
algorithm = "RSA"
}
resource "tls_cert_request" "example" {
private_key_pem = tls_private_key.example.private_key_pem
subject {
common_name = "example.com"
organization = "ACME Examples, Inc"
}
}
resource "google_privateca_ca_pool" "default" {
name = "my-ca-pool"
location = "us-central1"
tier = "ENTERPRISE"
publishing_options {
publish_ca_cert = true
publish_crl = true
}
labels = {
terraform = true
}
issuance_policy {
baseline_values {
ca_options {
is_ca = false
}
key_usage {
base_key_usage {
digital_signature = true
key_encipherment = true
}
extended_key_usage {
server_auth = true
}
}
}
}
}
resource "google_privateca_certificate_authority" "test-ca" {
Deletion_protection = false
certificate_authority_id = "my-authority"
location = google_privateca_ca_pool.default.location
pool = google_privateca_ca_pool.default.name
config {
subject_config {
subject {
country_code = "us"
organization = "google"
organizational_unit = "enterprise"
locality = "mountain view"
province = "california"
street_address = "1600 amphitheatre parkway"
postal_code = "94109"
common_name = "my-certificate-authority"
}
}
x509_config {
ca_options {
is_ca = true
}
key_usage {
base_key_usage {
cert_sign = true
crl_sign = true
}
extended_key_usage {
server_auth = true
}
}
}
}
type = "SELF_SIGNED"
key_spec {
algorithm = "RSA_PKCS1_4096_SHA256"
}
}
resource "google_privateca_certificate" "default" {
name = "my-certificate"
pool = google_privateca_ca_pool.default.name
certificate_authority = google_privateca_certificate_authority.test-ca.certificate_authority_id
location = google_privateca_ca_pool.default.location
lifetime = local.month_and_2min_in_second_duration # lifetime is 2mins over the threshold in the check block below
pem_csr = tls_cert_request.example.cert_request_pem
}
check "check_certificate_state" {
assert {
condition = timecmp(plantimestamp(), timeadd(
google_privateca_certificate.default.certificate_description[0].subject_description[0].not_after_time,
"-${local.month_in_hour_duration}")) < 0
error_message = format("Provisioned certificates should be valid for at least 30 days, but `%s`is due to expire on `%s`.",
google_privateca_certificate.default.name,
google_privateca_certificate.default.certificate_description[0].subject_description[0].not_after_time
)
}
}
```

## Example - Validate the status of a Cloud Function (`google_cloudfunctions2_function`)

Cloud Functions can have multiple statuses depending on issues that occur during deployment or triggering the function. These are: ACTIVE, FAILED, DEPLOYING, DELETING

In the example below we create a 2nd generation cloud function that uses source code stored as a .zip file in a GCS bucket. A .zip file containing the files needed by the function is uploaded by Terraform from the local machine. In the check we use the `google_cloudfunctions2_function` data source’s [state attribute](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudfunctions2_function#state) to access the function’s state and assert that the function is active.

```hcl
resource "google_storage_bucket" "bucket" {
name = "my-bucket"
location = "US"
uniform_bucket_level_access = true
}
resource "google_storage_bucket_object" "object" {
name = "function-source.zip"
bucket = google_storage_bucket.bucket.name
source = "./function-source.zip"
}
resource "google_cloudfunctions2_function" "my-function" {
name = "my-function"
location = "us-central1"
description = "a new function"
build_config {
runtime = "nodejs12"
entry_point = "helloHttp"
source {
storage_source {
bucket = google_storage_bucket.bucket.name
object = google_storage_bucket_object.object.name
}
}
}
service_config {
max_instance_count = 1
available_memory = "1536Mi"
timeout_seconds = 30
}
}
check "check_cf_state" {
data "google_cloudfunctions2_function" "my-function" {
name = google_cloudfunctions2_function.my-function.name
location = google_cloudfunctions2_function.my-function.location
}
assert {
condition = data.google_cloudfunctions2_function.my-function.state == "ACTIVE"
error_message = format("Provisioned Cloud Functions should be in an ACTIVE state, instead the function `%s` has state: %s",
data.google_cloudfunctions2_function.my-function.name,
data.google_cloudfunctions2_function.my-function.state
)
}
}
```

0 comments on commit 2069bb2

Please sign in to comment.