Skip to content

Commit c76ec18

Browse files
committed
Initial
0 parents  commit c76ec18

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4091
-0
lines changed

.github/workflows/test.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: PostgreSQL service example
2+
on:
3+
push:
4+
branches:
5+
- master
6+
jobs:
7+
format:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Check out repository code
11+
uses: actions/checkout@v4
12+
13+
- name: Install terraform
14+
uses: hashicorp/setup-terraform@v3
15+
with:
16+
terraform_version: "1.9.2"
17+
18+
- name: Terraform format
19+
run: terraform fmt -check -recursive

.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Provider lock files. These cause CI errors when upgrading providers
2+
*.terraform.lock.hcl
3+
4+
# Local .terraform directories
5+
**/.terraform/*
6+
7+
# .tfstate files
8+
*.tfstate
9+
*.tfstate.*
10+
11+
# Crash log files
12+
crash.log
13+
crash.*.log
14+
15+
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
16+
# password, private keys, and other secrets. These should not be part of version
17+
# control as they are data points which are potentially sensitive and subject
18+
# to change depending on the environment.
19+
*.tfvars
20+
*.tfvars.json
21+
*.tfbackend
22+
tfplan
23+
24+
# Ignore override files as they are usually used to override resources locally and so
25+
# are not checked in
26+
override.tf
27+
override.tf.json
28+
*_override.tf
29+
*_override.tf.json
30+
31+
# Include override files you do wish to add to version control using negated pattern
32+
# !example_override.tf
33+
34+
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
35+
# example: *tfplan*
36+
37+
# Ignore CLI configuration files
38+
.terraformrc
39+
terraform.rc

.tool-versions

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
awscli 2.17.14
2+
postgres 16.3
3+
terraform 1.9.2

.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"editor.formatOnSave": true,
4+
"editor.formatOnPaste": true,
5+
"[terraform]": {
6+
"editor.defaultFormatter": "hashicorp.terraform",
7+
"editor.formatOnSave": true
8+
}
9+
}

README.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Terraform AWS Postgres Bootstrap
2+
3+
This module is designed to simplify and automate the provisioning of PostgreSQL databases on Amazon Web Services (AWS) with IaC capabilities. As is most often the case, you will have applications and systems deploying managing their own migrations. Those applications and systems however do not always address the initial configuration that needs to be done before those migrations can run, or before the software can use the database. This is where this module comes in handy.
4+
5+
Note that if you only want to bootstrap your database, and do not need an RDS instance, you can use the [postgres_init submodule](modules/postgres_init/).
6+
7+
## Key Features
8+
9+
- Automated PostgreSQL Setup: Provision a PostgreSQL instance on AWS with minimal configuration.
10+
- Credential Management: Securely manage database and user credentials without credentials being stored in the state.
11+
- Dynamic SQL generation: Dynamically generate SQL using variables and secrets
12+
- Security Configurations: Automatically set up security groups and other necessary configurations to ensure secure access to the database.
13+
- Modular Design: Utilize sub-modules for managing databases, users and custom scripts.
14+
15+
## Usage
16+
17+
### Minimal example
18+
19+
```hcl
20+
module "postgres_databases" {
21+
source = "terraform-aws-postgres-bootstrap"
22+
23+
environment = "dev"
24+
database_configs = [
25+
{
26+
rds = {
27+
identifier = "my-db"
28+
server_name = "my-database"
29+
vpc_id = "vpc-xxxxxxxx"
30+
db_subnet_ids = ["subnet-1234abcd", "subnet-5678efgh"]
31+
postgres_version = "16"
32+
instance_class = "db.t3.small"
33+
allocated_storage = 50
34+
}
35+
init = {
36+
users = [{ name = "myuser" }]
37+
databases = [{
38+
name = "mydb",
39+
owner = "myuser",
40+
schemas = ["myschema1"]
41+
extensions = [{
42+
name = "myextension",
43+
schema = "myschema1"
44+
}]
45+
}]
46+
scripts = [{
47+
id = "create_foo_config_table"
48+
script = "../path/to/script.sql",
49+
database = "mydb",
50+
variables = { "FOO" : "foo" },
51+
secrets = {
52+
"BAR" = { path = "/aws/secret/path", key = "mysecret" }
53+
}
54+
shell_script = true
55+
}]
56+
}
57+
}
58+
]
59+
}
60+
```
61+
62+
### Full example
63+
64+
```hcl
65+
module "terraform-aws-postgres-bootstrap" {
66+
source = "terraform-aws-postgres-bootstrap"
67+
68+
environment = "dev"
69+
database_configs = [
70+
{
71+
rds = {
72+
identifier = "my-db"
73+
server_name = "my-database-server"
74+
vpc_id = "vpc-xxxxxxxx"
75+
db_subnet_ids = ["subnet-1234abcd", "subnet-5678efgh"]
76+
postgres_version = "16.3"
77+
auto_minor_version_upgrade = false # false because we specify minor version
78+
instance_class = "db.t3.small"
79+
allocated_storage = 50 # Gb
80+
storage_type = "gp3"
81+
storage_encrypted = true
82+
maintenance_username = "postgres"
83+
maintenance_database = "postgres"
84+
subnet_group = null # null because of db_subnet_ids
85+
max_allocated_storage = 500 # Gb
86+
deletion_protection = true
87+
skip_final_snapshot = false
88+
existing_user_credentials = null # use when importing existing db
89+
allowed_cidrs = [
90+
{ cidr_blocks = ["10.150.0.0/24"], description = "private-subnet-1"},
91+
{ cidr_blocks = ["10.150.1.0/24"], description = "private-subnet-2"}
92+
]
93+
parameter_group = [
94+
{
95+
name = "shared_preload_libraries"
96+
value = "pg_stat_statements,pglogical,pg_cron"
97+
apply_method = "pending-reboot"
98+
}
99+
]
100+
}
101+
init = {
102+
users = [{ name = "user_1", password = "foobar" }, { name = "user_2", regenerate_password = true }]
103+
databases = [
104+
{
105+
name = "db_1",
106+
owner = "user_1",
107+
schemas = ["schema_1_db_1"]
108+
extensions = [
109+
{
110+
name = "pg_search",
111+
schema = "schema_1_db_1"
112+
}
113+
]
114+
},
115+
{
116+
name = "db_2",
117+
owner = "user_2",
118+
}
119+
]
120+
scripts = [
121+
{
122+
id = "create_foo_config_table"
123+
script = "../path/to/script.sql",
124+
database = "db_1",
125+
variables = { "FOO" : "foo" },
126+
secrets = {
127+
"BAR" = { path = "/aws/secret/path", key = "mysecret" }
128+
}
129+
shell_script = false
130+
},
131+
{
132+
id = "update_user_1_role"
133+
script = "../path/to/script.sh",
134+
database = "db_2",
135+
user = "user_2",
136+
variables = { "OTHER_USER" : "user_1" },
137+
secrets = {
138+
"OTHER_USER_PASSWORD" = {
139+
path = "/dev/database-server/my-database-server/user/user_1",
140+
key = "password"
141+
}
142+
}
143+
shell_script = true
144+
rerun_on_user_change = true
145+
rerun_on_variable_change = true
146+
}]
147+
}
148+
}
149+
]
150+
}
151+
```
152+
153+
This example creates the following:
154+
155+
1. An RDS instance with the given specifications.
156+
2. Two users: `user_1` and `user_2`.
157+
3. Two databases:
158+
- `db_1`:
159+
- Owned by `user_1`.
160+
- Contains the schema `schema_1_db_1`.
161+
- Has the extension `pg_search` in schema `schema_1_db_1`.
162+
- `db_2`:
163+
- Owned by `user_2`.
164+
4. Executes the following scripts:
165+
- `../path/to/script.sql`:
166+
- An SQL script executed on database `db_1`.
167+
- Executed by the database server maintenance user (superuser).
168+
- Receives the variable `FOO`.
169+
- Receives the variable `BAR`, extracted from AWS Secrets Manager.
170+
- `../path/to/script.sh`:
171+
- A shell script executed on database `db_2`.
172+
- Executed by user `user_2`.
173+
- Receives the environment variable `OTHER_USER`.
174+
- Receives the environment variable `OTHER_USER_PASSWORD`, extracted from AWS Secrets Manager.
175+
- Configured to rerun when the script execution user's credentials change.
176+
- Configured to rerun when the variables and secrets passed to the script change.
177+
178+
### Good to know
179+
180+
#### Using additional user credentials in scripts
181+
182+
The module creates the credentials for your database users which you can use in your `scripts` for database initialization, (f.x creating fdw's or servers). You can retrieve them from AWS secrets manager. The credentials are always created before the scripts execute. See the [user credentials module](modules/credentials/user/) and the [database credentials moduel](modules/credentials/database/) for the format of the secret name.
183+
184+
Please review the [script submodule](modules/postgres_init/modules/script) for a detailed description on how to implement the bootstrapping scripts. There are also examples in the [examples directory](examples/)
185+
186+
## Inputs
187+
188+
| Name | Description | Type | Default | Required |
189+
| ---------------- | ------------------------------------------------------------------------------- | --------------------------------------------- | ------- | :------: |
190+
| environment | A unique identifier for the environment. Used for tagging and naming resources. | `string` | n/a | yes |
191+
| database_configs | The configurations of the databases to create. | `list(object({rds = object, init = object}))` | n/a | yes |
192+
193+
## Database Configurations
194+
195+
The `database_configs` variable is a list of objects, each representing a database to be created along with bootstrapping configuration.
196+
197+
A database configuration object consists of two keys, namely `rds` and `init`.
198+
199+
- The `rds` key configures the input variables for the postgres_rds submodule which creates the desired RDS instance to specification. For details on the configuration options see the [postgres_rds submodule](modules/postgres_rds/)
200+
201+
- The `init` key configures the input variables for the postgres_init submodule, which bootstraps the RDS instance to the required specification. This includes adding users, creating databases, schemas, extensions, and executing arbitrary scripts using variables and secrets from AWS Secrets Manager. For details on the configuration options, see the [postgres_init submodule](modules/postgres_init/)
202+
203+
In the configuration, unspecified optional keys default to null. The submodules then use this null value to apply default values when the fields are specified as non-nullable.
204+
205+
## Outputs
206+
207+
| Name | Description |
208+
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
209+
| postgres_instances | Map of postgres_rds module output with each key being the identifier of the RDS instance created. |
210+
| postgres_inits | Map of postgres_init module output with each key being the identifier of the RDS instance it was executed on. |
211+
212+
## Requirements
213+
214+
| Name | Version |
215+
| --------------- | ------- |
216+
| awscli | >= 2 |
217+
| postgres-client | >= 12 |
218+
| openssl | >= 3 |
219+
| linux/mac | |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Example for database credentials submodule
2+
3+
Example for [database credentials submodule](../../modules/credentials/database/)
4+
5+
Requirements
6+
7+
- awscli
8+
- terraform
9+
- openssl
10+
- linux/mac
11+
12+
## Running test
13+
14+
Create `.tfbackend` file
15+
16+
```hcl
17+
region = "eu-west-1"
18+
bucket = "my-bucket-name"
19+
key = "terraform-aws-postgres-bootstrap/credentials-database.tfstate"
20+
21+
```
22+
23+
- log in with awscli
24+
- terraform init -backend-config=".tfbackend"
25+
- terraform plan -out=tfplan
26+
- terraform apply tfplan
27+
28+
## Cleaning up after test
29+
30+
- terraform destroy

examples/credentials-database/main.tf

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
terraform {
2+
backend "s3" {}
3+
required_providers {
4+
aws = "~> 5"
5+
}
6+
}
7+
8+
provider "aws" {
9+
default_tags {
10+
tags = {
11+
"terraform-module" = "terraform-aws-postgres-bootstrap/examples/credentials-database"
12+
"managed-by" = "terraform"
13+
}
14+
}
15+
}
16+
17+
locals {
18+
environment = "test"
19+
server_name = "credentials-database"
20+
maintenance_database = "postgres"
21+
maintenance_user = "postgres"
22+
}
23+
24+
# Create a master user's credentials - must be done before the database credentials can be created
25+
# because the database credentials retrieve the user's credentials from the secret manager
26+
module "user_credentials" {
27+
source = "../../modules/credentials/user"
28+
29+
server_name = local.server_name
30+
environment = local.environment
31+
id = "master" # must be master or null when master_user = true
32+
name = local.maintenance_user
33+
master_user = true
34+
recovery_window_in_days = 0
35+
}
36+
37+
# Create the master database credentials
38+
module "database_credentials" {
39+
source = "../../modules/credentials/database"
40+
41+
server_name = local.server_name
42+
environment = local.environment
43+
id = "master" # must be master or null when master_database = true
44+
database = local.maintenance_database
45+
host = "localhost"
46+
port = 5432
47+
engine = "postgres"
48+
user_id = "master"
49+
user_role = "owner"
50+
master_database = true
51+
}

0 commit comments

Comments
 (0)