Skip to content

Commit 9c685f5

Browse files
committed
add normalize-input-file with sqlfluff
1 parent 1aee807 commit 9c685f5

File tree

10 files changed

+85
-4
lines changed

10 files changed

+85
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ override.tf.json
2727

2828
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
2929
# example: *tfplan*
30+
.DS_Store

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 Andrew Bobulsky
3+
Copyright (c) 2022 Andrew Bobulsky
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ Documenting solutions to interesting Terraform problems.
44

55
## Requirements
66

7-
Consider installing [tfenv](https://github.com/tfutils/tfenv) if you want these code exmaples to "just work" for you.
7+
Just terraform, though consider installing [asdf-vm](https://asdf-vm.com/) if you want to be sure you're using the same version I used.
88

99
# Examples
1010

1111
* [Dynamic Templatefile Rendering](./dynamic-template-output/README.md)
12-
* Keeping `templatefile()` function calls and the files themselves DRY — avoids writing variables twice, as input and output.
12+
* Keeping `templatefile()` function calls and the files themselves DRY — avoids writing variables twice, as input and output.
13+
* [Traversing Nested Maps](traversing-nested-maps)
14+
* Illustrating how to navigate nested map structures to facilitate iterative resource creation.
15+
* [Normalizing Input Files](normalize-input-file)
16+
* Using the external data source to pass a file through a linter before consuming it to avoid unnecessary destroy/recreate cycles.`

dynamic-template-output/.terraform-version

-1
This file was deleted.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terraform 0.13.4

normalize-input-file/.tool-versions

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terraform 1.3.3

normalize-input-file/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Formatting SQL in Terraform
2+
3+
[This question](https://www.reddit.com/r/Terraform/comments/xvm6xc/terraform_pre_commit_hooks/) came up on reddit where a user wanted to use git commit hooks to format SQL code that terraform was consuming so that it would be consistent regardless of whitespace changes. My opinion was that the SQL code should be normalized by Terraform, not by git hooks, so I created this repo to illustrate the idea.
4+
5+
----
6+
7+
[unformatted.sql](unformatted.sql) shows a SQL query with terrible formatting, making it nearly unreadable.
8+
9+
```sql
10+
SELECT country.country_name_eng, SUM(CASE WHEN call.id IS NOT NULL THEN 1 ELSE 0 END) AS calls, AVG(ISNULL(DATEDIFF(SECOND, call.start_time, call.end_time),0)) AS avg_difference FROM country LEFT JOIN city ON city.country_id = country.id LEFT JOIN customer ON city.id = customer.city_id LEFT JOIN call ON call.customer_id = customer.id GROUP BY country.id, country.country_name_eng HAVING AVG(ISNULL(DATEDIFF(SECOND, call.start_time, call.end_time),0)) > (SELECT AVG(DATEDIFF(SECOND, call.start_time, call.end_time)) FROM call) ORDER BY calls DESC, country.id ASC;
11+
```
12+
13+
Using the [external_data data source](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source) in Terraform, and a [small wrapper script](sqlfluff-wrapper.sh), we pipe the data to jq -> shell -> [SQLFluff](https://github.com/sqlfluff/sqlfluff) to make it readable, as shown here:
14+
15+
```
16+
╭─ ~/Projects/personal/terraform/sqlformat main ?
17+
╰─❯ terraform apply
18+
19+
Changes to Outputs:
20+
+ formatted_sql = <<-EOT
21+
SELECT
22+
country.country_name_eng,
23+
SUM(CASE WHEN call.id IS NOT NULL THEN 1 ELSE 0 END) AS calls,
24+
AVG(
25+
ISNULL(DATEDIFF(second, call.start_time, call.end_time), 0)
26+
) AS avg_difference
27+
FROM
28+
country
29+
LEFT JOIN
30+
city ON city.country_id = country.id
31+
LEFT JOIN
32+
customer ON city.id = customer.city_id
33+
LEFT JOIN
34+
call ON call.customer_id = customer.id
35+
GROUP BY
36+
country.id, country.country_name_eng
37+
HAVING
38+
AVG(
39+
ISNULL(DATEDIFF(second, call.start_time, call.end_time), 0)
40+
) > (SELECT AVG(DATEDIFF(second, call.start_time, call.end_time)) FROM call)
41+
ORDER BY calls DESC, country.id ASC;
42+
EOT
43+
```
44+
45+
# Requires
46+
47+
* jq
48+
* sqlfluff
49+
* bash shell

normalize-input-file/main.tf

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
data "external" "format_sql" {
2+
program = ["bash", "sqlfluff-wrapper.sh"]
3+
query = {
4+
sql = file("unformatted.sql")
5+
dialect = "ansi"
6+
}
7+
}
8+
9+
output "formatted_sql" {
10+
value = data.external.format_sql.result.formatted_sql
11+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash -e
2+
3+
# Extract arguments from the input into shell variables.
4+
# jq will ensure that the values are properly quoted
5+
# and escaped for consumption by the shell.
6+
eval "$(jq -r '@sh "SQL=\(.sql) DIALECT=\(.dialect)"')"
7+
8+
# Placeholder for whatever data-fetching logic your script implements
9+
FORMATTED_SQL="$(echo $SQL | sqlfluff fix - --dialect $DIALECT)"
10+
11+
# Safely produce a JSON object containing the result value.
12+
# jq will ensure that the value is properly quoted
13+
# and escaped to produce a valid JSON string.
14+
jq -n --arg formatted_sql "$FORMATTED_SQL" '{"formatted_sql":$formatted_sql}'

normalize-input-file/unformatted.sql

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT country.country_name_eng, SUM(CASE WHEN call.id IS NOT NULL THEN 1 ELSE 0 END) AS calls, AVG(ISNULL(DATEDIFF(SECOND, call.start_time, call.end_time),0)) AS avg_difference FROM country LEFT JOIN city ON city.country_id = country.id LEFT JOIN customer ON city.id = customer.city_id LEFT JOIN call ON call.customer_id = customer.id GROUP BY country.id, country.country_name_eng HAVING AVG(ISNULL(DATEDIFF(SECOND, call.start_time, call.end_time),0)) > (SELECT AVG(DATEDIFF(SECOND, call.start_time, call.end_time)) FROM call) ORDER BY calls DESC, country.id ASC;

0 commit comments

Comments
 (0)