Writing Terraform Plan Polices with KCL Programming Language
Peefy
Posted on November 2, 2023
Introduction
KCL is an open-source, constraint-based record and functional language that enhances the writing of complex configurations, including those for cloud-native scenarios. It is hosted by the Cloud Native Computing Foundation (CNCF) as a Sandbox Project.
With its advanced programming language technology and practices, KCL is dedicated to promoting better modularity, scalability, and stability for configurations. It enables simpler logic writing and offers ease of automation APIs and integration with homegrown systems.
Validation is the process of verifying that data is accurate, reliable, and meets certain requirements or constraints. This includes checking the data for completeness, consistency, accuracy, and relevance. We can use KCL and its vet tools to manually or automatically perform terraform configuration validation to ensure data consistency with KCL policy codes.
Quick Start
0. Prerequisite
- Install KCL
- Install Terraform
- Set up your AWS credentials correctly for your terminal to use.
1. Get the Example
Firstly, let's get the example.
git clone https://github.com/kcl-lang/kcl-lang.io.git/
cd ./kcl-lang.io/examples/terraform/validation
We can run the following command to show the terraform config.
cat main.tf
provider "aws" {
region = "us-west-1"
}
resource "aws_instance" "web" {
instance_type = "t2.micro"
ami = "ami-09b4b74c"
}
resource "aws_autoscaling_group" "my_asg" {
availability_zones = ["us-west-1a"]
name = "my_asg"
max_size = 5
min_size = 1
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 4
force_delete = true
launch_configuration = "my_web_config"
}
resource "aws_launch_configuration" "my_web_config" {
name = "my_web_config"
image_id = "ami-09b4b74c"
instance_type = "t2.micro"
}
Run the following command to get the terraform plan configuration.
terraform init
terraform plan --out tfplan.binary
terraform show -json tfplan.binary > tfplan.json
The output json is
tfplan.json
(click to expand large file)
{
"format_version": "0.1",
"terraform_version": "0.12.6",
"planned_values": {
"root_module": {
"resources": [
{
"address": "aws_autoscaling_group.my_asg",
"mode": "managed",
"type": "aws_autoscaling_group",
"name": "my_asg",
"provider_name": "aws",
"schema_version": 0,
"values": {
"availability_zones": [
"us-west-1a"
],
"desired_capacity": 4,
"enabled_metrics": null,
"force_delete": true,
"health_check_grace_period": 300,
"health_check_type": "ELB",
"initial_lifecycle_hook": [],
"launch_configuration": "my_web_config",
"launch_template": [],
"max_size": 5,
"metrics_granularity": "1Minute",
"min_elb_capacity": null,
"min_size": 1,
"mixed_instances_policy": [],
"name": "my_asg",
"name_prefix": null,
"placement_group": null,
"protect_from_scale_in": false,
"suspended_processes": null,
"tag": [],
"tags": null,
"termination_policies": null,
"timeouts": null,
"wait_for_capacity_timeout": "10m",
"wait_for_elb_capacity": null
}
},
{
"address": "aws_instance.web",
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider_name": "aws",
"schema_version": 1,
"values": {
"ami": "ami-09b4b74c",
"credit_specification": [],
"disable_api_termination": null,
"ebs_optimized": null,
"get_password_data": false,
"iam_instance_profile": null,
"instance_initiated_shutdown_behavior": null,
"instance_type": "t2.micro",
"monitoring": null,
"source_dest_check": true,
"tags": null,
"timeouts": null,
"user_data": null,
"user_data_base64": null
}
},
{
"address": "aws_launch_configuration.my_web_config",
"mode": "managed",
"type": "aws_launch_configuration",
"name": "my_web_config",
"provider_name": "aws",
"schema_version": 0,
"values": {
"associate_public_ip_address": false,
"enable_monitoring": true,
"ephemeral_block_device": [],
"iam_instance_profile": null,
"image_id": "ami-09b4b74c",
"instance_type": "t2.micro",
"name": "my_web_config",
"name_prefix": null,
"placement_tenancy": null,
"security_groups": null,
"spot_price": null,
"user_data": null,
"user_data_base64": null,
"vpc_classic_link_id": null,
"vpc_classic_link_security_groups": null
}
}
]
}
},
"resource_changes": [
{
"address": "aws_autoscaling_group.my_asg",
"mode": "managed",
"type": "aws_autoscaling_group",
"name": "my_asg",
"provider_name": "aws",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"availability_zones": [
"us-west-1a"
],
"desired_capacity": 4,
"enabled_metrics": null,
"force_delete": true,
"health_check_grace_period": 300,
"health_check_type": "ELB",
"initial_lifecycle_hook": [],
"launch_configuration": "my_web_config",
"launch_template": [],
"max_size": 5,
"metrics_granularity": "1Minute",
"min_elb_capacity": null,
"min_size": 1,
"mixed_instances_policy": [],
"name": "my_asg",
"name_prefix": null,
"placement_group": null,
"protect_from_scale_in": false,
"suspended_processes": null,
"tag": [],
"tags": null,
"termination_policies": null,
"timeouts": null,
"wait_for_capacity_timeout": "10m",
"wait_for_elb_capacity": null
},
"after_unknown": {
"arn": true,
"availability_zones": [
false
],
"default_cooldown": true,
"id": true,
"initial_lifecycle_hook": [],
"launch_template": [],
"load_balancers": true,
"mixed_instances_policy": [],
"service_linked_role_arn": true,
"tag": [],
"target_group_arns": true,
"vpc_zone_identifier": true
}
}
},
{
"address": "aws_instance.web",
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider_name": "aws",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"ami": "ami-09b4b74c",
"credit_specification": [],
"disable_api_termination": null,
"ebs_optimized": null,
"get_password_data": false,
"iam_instance_profile": null,
"instance_initiated_shutdown_behavior": null,
"instance_type": "t2.micro",
"monitoring": null,
"source_dest_check": true,
"tags": null,
"timeouts": null,
"user_data": null,
"user_data_base64": null
},
"after_unknown": {
"arn": true,
"associate_public_ip_address": true,
"availability_zone": true,
"cpu_core_count": true,
"cpu_threads_per_core": true,
"credit_specification": [],
"ebs_block_device": true,
"ephemeral_block_device": true,
"host_id": true,
"id": true,
"instance_state": true,
"ipv6_address_count": true,
"ipv6_addresses": true,
"key_name": true,
"network_interface": true,
"network_interface_id": true,
"password_data": true,
"placement_group": true,
"primary_network_interface_id": true,
"private_dns": true,
"private_ip": true,
"public_dns": true,
"public_ip": true,
"root_block_device": true,
"security_groups": true,
"subnet_id": true,
"tenancy": true,
"volume_tags": true,
"vpc_security_group_ids": true
}
}
},
{
"address": "aws_launch_configuration.my_web_config",
"mode": "managed",
"type": "aws_launch_configuration",
"name": "my_web_config",
"provider_name": "aws",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"associate_public_ip_address": false,
"enable_monitoring": true,
"ephemeral_block_device": [],
"iam_instance_profile": null,
"image_id": "ami-09b4b74c",
"instance_type": "t2.micro",
"name": "my_web_config",
"name_prefix": null,
"placement_tenancy": null,
"security_groups": null,
"spot_price": null,
"user_data": null,
"user_data_base64": null,
"vpc_classic_link_id": null,
"vpc_classic_link_security_groups": null
},
"after_unknown": {
"ebs_block_device": true,
"ebs_optimized": true,
"ephemeral_block_device": [],
"id": true,
"key_name": true,
"root_block_device": true
}
}
}
],
"configuration": {
"provider_config": {
"aws": {
"name": "aws",
"expressions": {
"region": {
"constant_value": "us-west-1"
}
}
}
},
"root_module": {
"resources": [
{
"address": "aws_autoscaling_group.my_asg",
"mode": "managed",
"type": "aws_autoscaling_group",
"name": "my_asg",
"provider_config_key": "aws",
"expressions": {
"availability_zones": {
"constant_value": [
"us-west-1a"
]
},
"desired_capacity": {
"constant_value": 4
},
"force_delete": {
"constant_value": true
},
"health_check_grace_period": {
"constant_value": 300
},
"health_check_type": {
"constant_value": "ELB"
},
"launch_configuration": {
"constant_value": "my_web_config"
},
"max_size": {
"constant_value": 5
},
"min_size": {
"constant_value": 1
},
"name": {
"constant_value": "my_asg"
}
},
"schema_version": 0
},
{
"address": "aws_instance.web",
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider_config_key": "aws",
"expressions": {
"ami": {
"constant_value": "ami-09b4b74c"
},
"instance_type": {
"constant_value": "t2.micro"
}
},
"schema_version": 1
},
{
"address": "aws_launch_configuration.my_web_config",
"mode": "managed",
"type": "aws_launch_configuration",
"name": "my_web_config",
"provider_config_key": "aws",
"expressions": {
"image_id": {
"constant_value": "ami-09b4b74c"
},
"instance_type": {
"constant_value": "t2.micro"
},
"name": {
"constant_value": "my_web_config"
}
},
"schema_version": 0
}
]
}
}
}
2. Create a KCL Policy File
main.k
schema TFPlan:
# Omit other attributes
[...str]: any
resource_changes?: [AcceptableChange]
schema AcceptableChange:
# Omit other attributes
[...str]: any
check:
# Reject AWS autoscaling group Resource delete action
all action in change.actions {
action not in ["delete"]
} if type == "aws_autoscaling_group", "Disable AWS autoscaling group resource delete action for the resource ${type} ${name}"
This policy file checks that no AWS Auto Scaling groups are being deleted - even if that deletion is part of a delete-and-recreate operation.
3. Evaluate the Terraform Plan File Against the KCL Policy
kcl vet tfplan.json main.k
Because the plan was acceptable to the 1 policies contained in the policy file, kcl vet
printed nothing, and its exit code was zero.
4. Mock a Policy Failure
Create a KCL file named main.policy.failure.k
schema TFPlan:
# Omit other attributes
[...str]: any
resource_changes?: [AcceptableChange]
schema AcceptableChange:
# Omit other attributes
[...str]: any
check:
# Reject AWS autoscaling group Resource delete action
all action in change.actions {
action not in ["create"]
} if type == "aws_autoscaling_group", "Disable AWS autoscaling group resource create action for the resource ${type} ${name}"
Run the command
kcl vet tfplan.json main.policy.failure.k
We can see the error message
Error: EvaluationError
--> main.policy.failure.k:13:1
|
13 | } if type == "aws_autoscaling_group", "Disable AWS autoscaling group resource create action for the resource ${type} ${name}"
| Check failed on the condition: Disable AWS autoscaling group resource create action for the resource aws_autoscaling_group my_asg
|
Summary
This document explains how to validate Terraform configuration using KCL and its vet tools. Validation is the process of verifying the accuracy, reliability, and compliance of data with certain requirements or constraints. By using KCL and vet tools, we can manually or automatically perform Terraform configuration validation to ensure data consistency with KCL policy codes.
Posted on November 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.