├── .gitignore
├── .pre-commit-config.yaml
├── AUTHORS
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── databases.tf
├── examples
├── 1-single-node
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
└── 2-multi-node
│ ├── main.tf
│ ├── network.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── main.tf
├── outputs.tf
├── pgpass.tf
├── users.tf
├── variables.tf
└── versions.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local .terraform directories
2 | **/.terraform/*
3 |
4 | # Terraform lockfile
5 | .terraform.lock.hcl
6 |
7 | # .tfstate files
8 | *.tfstate
9 | *.tfstate.*
10 |
11 | **/*.log
12 |
13 | # sh
14 | *.sh
15 | **/*.sh
16 |
17 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as
18 | # password, private keys, and other secrets. These should not be part of version
19 | # control as they are data points which are potentially sensitive and subject
20 | # to change depending on the environment.
21 | #*.tfvars
22 |
23 | # Ignore override files as they are usually used to override resources locally and so
24 | # are not checked in
25 | override.tf
26 | override.tf.json
27 | *_override.tf
28 | *_override.tf.json
29 |
30 | # Ignore CLI configuration files
31 | .terraformrc
32 | terraform.rc
33 |
34 | # Zip archive
35 | *.zip
36 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/antonbabenko/pre-commit-terraform.git
3 | rev: v1.77.1
4 | hooks:
5 | - id: terraform_fmt
6 | - id: terraform_validate
7 | - id: terraform_docs
8 | - id: terraform_tflint
9 | args:
10 | - '--args=--only=terraform_deprecated_interpolation'
11 | - '--args=--only=terraform_deprecated_index'
12 | - '--args=--only=terraform_unused_declarations'
13 | - '--args=--only=terraform_comment_syntax'
14 | - '--args=--only=terraform_documented_outputs'
15 | - '--args=--only=terraform_documented_variables'
16 | - '--args=--only=terraform_typed_variables'
17 | - '--args=--only=terraform_module_pinned_source'
18 | - '--args=--only=terraform_naming_convention'
19 | - '--args=--only=terraform_required_version'
20 | - '--args=--only=terraform_required_providers'
21 | - '--args=--only=terraform_standard_module_structure'
22 | - '--args=--only=terraform_workspace_remote'
23 | - repo: https://github.com/pre-commit/pre-commit-hooks
24 | rev: v4.4.0
25 | hooks:
26 | - id: check-merge-conflict
27 | - id: end-of-file-fixer
28 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | The following authors have created the source code of "Terraform Yandex Cloud Modules"
2 | published and distributed by YANDEX LLC as the owner:
3 | Alexander Dushein adushein@yandex-team.ru
4 | Roman Timofeev romantimofeev@yandex-team.ru
5 | Pavel Selivanov poselivanov@yandex-team.ru
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Notice to external contributors
2 |
3 |
4 | ## General info
5 |
6 | Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the **CLA**). The current version of the CLA can be found here:
7 | 1) https://yandex.ru/legal/cla/?lang=en (in English) and
8 | 2) https://yandex.ru/legal/cla/?lang=ru (in Russian).
9 |
10 | By adopting the CLA, you state the following:
11 |
12 | * You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
13 | * You have read the terms and conditions of the CLA and agree with them in full,
14 | * You are legally able to provide and license your contributions as stated,
15 | * We may use your contributions for our open source projects and for any other our project too,
16 | * We rely on your assurances concerning the rights of third parties in relation to your contributions.
17 |
18 | If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
19 |
20 | ## Provide contributions
21 |
22 | If you have already adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it:
23 |
24 | ```
25 | I hereby agree to the terms of the CLA available at: [link].
26 | ```
27 |
28 | Replace the bracketed text as follows:
29 | * [link] is the link to the current version of the CLA: https://yandex.ru/legal/cla/?lang=en (in English) or https://yandex.ru/legal/cla/?lang=ru (in Russian).
30 |
31 | It is enough to provide us such notification once.
32 |
33 | ## Pull Request Process
34 |
35 | When contributing to this repository, please first discuss the change you wish to make via issue.
36 |
37 | 1. Update the README.md with details of changes including example hcl blocks and [example files](./examples) if appropriate.
38 | 2. Run pre-commit hooks `pre-commit run -a`.
39 | 3. Once all outstanding comments and checklist items have been addressed, your contribution will be merged! Merged PRs will be included in the next release.
40 |
41 | ## Checklists for contributions
42 |
43 | - [ ] You have updated at least one of the examples/* to demonstrate and validate change(s)
44 | - [ ] You have tested and validated changes using one or more of the provided [examples/*](./examples) projects
45 | - [ ] README.md has been updated after any changes to variables and outputs.
46 | - [ ] Run pre-commit hooks `pre-commit run -a`
47 | - [ ] Done steps from [Provide contributions](#1.0) section
48 |
49 | ## Other questions
50 |
51 | If you have any questions, please mail us at opensource@yandex-team.ru.
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 YANDEX LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yandex Cloud Managed PostgreSQL Cluster
2 |
3 | ## Features
4 |
5 | - Create a managed PostgreSQL cluster with a predefined number of DB hosts
6 | - Create a list of users and databases with permissions
7 | - Easy to use in other resources via outputs
8 |
9 | ## PostgreSQL cluster definition
10 |
11 | First, you need to create a VPC network with three subnets.
12 |
13 | PostgreSQL module requires the following input variables:
14 | - VPC network ID.
15 | - VPC network subnet IDs.
16 | - PostgreSQL host definitions: List of maps with DB host name, zone name, and subnet ID.
17 | - Databases: List of databases with database names, owners, and other parameters.
18 | - Owners: List of database owners.
19 | - Users: All other users with a list of permissions to databases.
20 |
21 | Notes:
22 | 1. The `owners` variable defines a list of databases owners. It does not support the `permissions` list because these users will be linked with `databases` via the `owner` parameter. This means each database must have an owner.
23 | 2. The `users` variable defines a list of separate DB users with the `permissions` list, which points to a list of databases. This means each user will have access to each database from the `permissions` list.
24 | 3. The `settings_options` parameter may be null, in which case the default values will be used.
25 |
26 | ### Example
27 |
28 | See [examples section](./examples/)
29 |
30 | ### How to configure Terraform for Yandex Cloud
31 |
32 | - Install [YC CLI](https://cloud.yandex.com/docs/cli/quickstart)
33 | - Add environment variables for terraform auth in Yandex.Cloud
34 |
35 | ```
36 | export YC_TOKEN=$(yc iam create-token)
37 | export YC_CLOUD_ID=$(yc config get cloud-id)
38 | export YC_FOLDER_ID=$(yc config get folder-id)
39 | export TF_VAR_network_id=_vpc id here_
40 | ```
41 |
42 |
43 | ## Requirements
44 |
45 | | Name | Version |
46 | |------|---------|
47 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
48 | | [random](#requirement\_random) | > 3.3 |
49 | | [yandex](#requirement\_yandex) | >= 0.89.0 |
50 |
51 | ## Providers
52 |
53 | | Name | Version |
54 | |------|---------|
55 | | [local](#provider\_local) | 2.4.0 |
56 | | [random](#provider\_random) | 3.5.1 |
57 | | [yandex](#provider\_yandex) | 0.98.0 |
58 |
59 | ## Modules
60 |
61 | No modules.
62 |
63 | ## Resources
64 |
65 | | Name | Type |
66 | |------|------|
67 | | [local_file.pgpass_file](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
68 | | [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
69 | | [yandex_dns_recordset.rw](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/dns_recordset) | resource |
70 | | [yandex_mdb_postgresql_cluster.this](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/mdb_postgresql_cluster) | resource |
71 | | [yandex_mdb_postgresql_database.database](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/mdb_postgresql_database) | resource |
72 | | [yandex_mdb_postgresql_user.owner](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/mdb_postgresql_user) | resource |
73 | | [yandex_mdb_postgresql_user.user](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/mdb_postgresql_user) | resource |
74 | | [yandex_client_config.client](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/data-sources/client_config) | data source |
75 |
76 | ## Inputs
77 |
78 | | Name | Description | Type | Default | Required |
79 | |------|-------------|------|---------|:--------:|
80 | | [access\_policy](#input\_access\_policy) | Access policy from other services to the PostgreSQL cluster. |
object({
data_lens = optional(bool, null)
web_sql = optional(bool, null)
serverless = optional(bool, null)
data_transfer = optional(bool, null)
})
| `{}` | no |
81 | | [autofailover](#input\_autofailover) | (Optional) Configuration setting which enables and disables auto failover in the cluster. | `bool` | `true` | no |
82 | | [backup\_retain\_period\_days](#input\_backup\_retain\_period\_days) | (Optional) The period in days during which backups are stored. | `number` | `null` | no |
83 | | [backup\_window\_start](#input\_backup\_window\_start) | (Optional) Time to start the daily backup, in the UTC timezone. | object({
hours = string
minutes = optional(string, "00")
})
| `null` | no |
84 | | [databases](#input\_databases) | List of PostgreSQL databases.
Required values:
- name - (Required) The name of the database.
- owner - (Required) Name of the user assigned as the owner of the database. Forbidden to change in an existing database.
- extension - (Optional) Set of database extensions.
- lc\_collate - (Optional) POSIX locale for string sorting order. Forbidden to change in an existing database.
- lc\_type - (Optional) POSIX locale for character classification. Forbidden to change in an existing database.
- template\_db - (Optional) Name of the template database.
- deletion\_protection - (Optional) A deletion protection. | list(object({
name = string
owner = string
lc_collate = optional(string, null)
lc_type = optional(string, null)
template_db = optional(string, null)
deletion_protection = optional(bool, null)
extensions = optional(list(string), [])
}))
| n/a | yes |
85 | | [default\_user\_settings](#input\_default\_user\_settings) | The default user settings. These settings are overridden by the user's settings.
Full description https://cloud.yandex.com/en-ru/docs/managed-postgresql/api-ref/grpc/user_service#UserSettings1 | `map(any)` | `{}` | no |
86 | | [deletion\_protection](#input\_deletion\_protection) | Protects the cluster from deletion | `bool` | `false` | no |
87 | | [description](#input\_description) | PostgreSQL cluster description | `string` | `"Managed PostgreSQL cluster"` | no |
88 | | [disk\_size](#input\_disk\_size) | Disk size for every cluster host | `number` | `20` | no |
89 | | [disk\_type](#input\_disk\_type) | Disk type for all cluster hosts | `string` | `"network-ssd"` | no |
90 | | [dns\_cname\_config](#input\_dns\_cname\_config) | Creates a CNAME record of connection host in the specified DNS zone.
Object values:
- name - (Required) The DNS name this record set will apply to.
- zone\_id - (Required) The id of the zone in which this record set will reside
- ttl - (Optional) The time-to-live of this record set (seconds). | object({
name = string
zone_id = string
ttl = optional(number, 360)
})
| `null` | no |
91 | | [environment](#input\_environment) | Environment type: PRODUCTION or PRESTABLE | `string` | `"PRODUCTION"` | no |
92 | | [folder\_id](#input\_folder\_id) | Yandex Cloud Folder ID where the cluster resides | `string` | `null` | no |
93 | | [host\_master\_name](#input\_host\_master\_name) | Name of the master host. | `string` | `null` | no |
94 | | [hosts\_definition](#input\_hosts\_definition) | A list of PostgreSQL hosts. | list(object({
name = optional(string, null)
zone = string
subnet_id = string
assign_public_ip = optional(bool, false)
replication_source_name = optional(string, null)
priority = optional(number, null)
}))
| n/a | yes |
95 | | [labels](#input\_labels) | A set of label pairs to assing to the PostgreSQL cluster. | `map(any)` | `{}` | no |
96 | | [maintenance\_window](#input\_maintenance\_window) | (Optional) Maintenance policy of the PostgreSQL cluster.
- type - (Required) Type of maintenance window. Can be either ANYTIME or WEEKLY. A day and hour of window need to be specified with weekly window.
- day - (Optional) Day of the week (in DDD format). Allowed values: "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"
- hour - (Optional) Hour of the day in UTC (in HH format). Allowed value is between 0 and 23. | object({
type = string
day = optional(string, null)
hour = optional(string, null)
})
| {
"type": "ANYTIME"
}
| no |
97 | | [name](#input\_name) | PostgreSQL cluster name | `string` | `"pgsql-cluster"` | no |
98 | | [network\_id](#input\_network\_id) | Network id of the PostgreSQL cluster | `string` | n/a | yes |
99 | | [owners](#input\_owners) | List of special PostgreSQL DB users - database owners. These users are created first and assigned to database as owner.
There is also an aditional list for other users with own permissions.
Required values:
- name - (Required) The name of the user.
- password - (Optional) The user's password. If it's omitted a random password will be generated.
- grants - (Optional) List of the user's grants.
- login - (Optional) The user's ability to login.
- conn\_limit - (Optional) The maximum number of connections per user.
- settings - (Optional) A user setting options.
- deletion\_protection - (Optional) A deletion protection. | list(object({
name = string
password = optional(string, null)
grants = optional(list(string), [])
login = optional(bool, null)
conn_limit = optional(number, null)
settings = optional(map(any), {})
deletion_protection = optional(bool, null)
}))
| n/a | yes |
100 | | [performance\_diagnostics](#input\_performance\_diagnostics) | (Optional) PostgreSQL cluster performance diagnostics settings. | object({
enabled = optional(bool, null)
sessions_sampling_interval = optional(number, 60)
statements_sampling_interval = optional(number, 600)
})
| `{}` | no |
101 | | [pg\_version](#input\_pg\_version) | PostgreSQL version | `string` | `"15"` | no |
102 | | [pgpass\_path](#input\_pgpass\_path) | Location of the .pgpass file. If it's omitted the file will not be created | `string` | `null` | no |
103 | | [pooler\_config](#input\_pooler\_config) | Configuration of the connection pooler.
- pool\_discard - Setting pool\_discard parameter in Odyssey. Values: yes \| no
- pooling\_mode - Mode that the connection pooler is working in. Values: `POOLING_MODE_UNSPECIFIED`, `SESSION`, `TRANSACTION`, `STATEMENT` | object({
pool_discard = optional(bool, null)
pooling_mode = optional(string, null)
})
| `null` | no |
104 | | [postgresql\_config](#input\_postgresql\_config) | Map of PostgreSQL cluster configuration.
Details info in a 'PostgreSQL cluster settings' of official documentation.
Link: https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/mdb_postgresql_cluster#postgresql-cluster-settings | `map(any)` | `null` | no |
105 | | [resource\_preset\_id](#input\_resource\_preset\_id) | Preset for hosts | `string` | `"s2.micro"` | no |
106 | | [restore\_parameters](#input\_restore\_parameters) | The cluster will be created from the specified backup.
NOTES:
- backup\_id must be specified to create a new PostgreSQL cluster from a backup.
- time format is 'yyy-mm-ddThh:mi:ss', where T is a delimeter, e.g. "2023-04-05T11:22:33".
- time\_inclusive indicates recovery to nearest recovery point just before (false) or right after (true) the time. | object({
backup_id = string
time = optional(string, null)
time_inclusive = optional(bool, null)
})
| `null` | no |
107 | | [security\_groups\_ids\_list](#input\_security\_groups\_ids\_list) | List of security group IDs to which the PostgreSQL cluster belongs | `list(string)` | `[]` | no |
108 | | [users](#input\_users) | List of additional PostgreSQL users with own permissions. They are created at the end.
Required values:
- name - (Required) The name of the user.
- password - (Optional) The user's password. If it's omitted a random password will be generated.
- grants - (Optional) List of the user's grants.
- login - (Optional) The user's ability to login.
- conn\_limit - (Optional) The maximum number of connections per user.
- permissions - (Optional) List of databases names for an access
- settings - (Optional) A user setting options.
- deletion\_protection - (Optional) A deletion protection. | list(object({
name = string
password = optional(string, null)
grants = optional(list(string), [])
login = optional(bool, null)
conn_limit = optional(number, null)
permissions = optional(list(string), [])
settings = optional(map(any), {})
deletion_protection = optional(bool, null)
}))
| `[]` | no |
109 |
110 | ## Outputs
111 |
112 | | Name | Description |
113 | |------|-------------|
114 | | [cluster\_fqdns\_list](#output\_cluster\_fqdns\_list) | PostgreSQL cluster nodes FQDN list |
115 | | [cluster\_host\_names\_list](#output\_cluster\_host\_names\_list) | PostgreSQL cluster host name |
116 | | [cluster\_id](#output\_cluster\_id) | PostgreSQL cluster ID |
117 | | [cluster\_name](#output\_cluster\_name) | PostgreSQL cluster name |
118 | | [connection\_host\_cname](#output\_connection\_host\_cname) | PostgreSQL cluster connection host CNAME. |
119 | | [connection\_step\_1](#output\_connection\_step\_1) | 1 step - Install certificate |
120 | | [connection\_step\_2](#output\_connection\_step\_2) | How connect to PostgreSQL cluster?
1. Install certificate
mkdir --parents \~/.postgresql && \\
curl -sfL "https://storage.yandexcloud.net/cloud-certs/CA.pem" -o \~/.postgresql/root.crt && \\
chmod 0600 \~/.postgresql/root.crt
2. Run connection string from the output value, for example
psql "host=rc1a-g2em5m3zc9dxxasn.mdb.yandexcloud.net \\
port=6432 \\
sslmode=verify-full \\
dbname=db-b \\
user=owner-b \\
target\_session\_attrs=read-write" |
121 | | [databases](#output\_databases) | List of databases names. |
122 | | [owners\_data](#output\_owners\_data) | List of owners with passwords. |
123 | | [users\_data](#output\_users\_data) | List of users with passwords. |
124 |
125 |
--------------------------------------------------------------------------------
/databases.tf:
--------------------------------------------------------------------------------
1 | # PostgreSQL databases
2 | resource "yandex_mdb_postgresql_database" "database" {
3 | for_each = length(var.databases) > 0 ? { for db in var.databases : db.name => db } : {}
4 |
5 | cluster_id = yandex_mdb_postgresql_cluster.this.id
6 | name = each.value.name
7 | owner = yandex_mdb_postgresql_user.owner[each.value.owner].name
8 | lc_collate = each.value.lc_collate
9 | lc_type = each.value.lc_type
10 | deletion_protection = each.value.deletion_protection
11 | template_db = each.value.template_db
12 |
13 | dynamic "extension" {
14 | for_each = each.value.extensions
15 | content {
16 | name = extension.value
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/1-single-node/main.tf:
--------------------------------------------------------------------------------
1 | # main.tf
2 |
3 | module "db" {
4 | source = "../../"
5 |
6 | network_id = var.network_id
7 | name = "alone-in-the-dark"
8 | description = "Single-node PostgreSQL cluster for test purposes"
9 |
10 | maintenance_window = {
11 | type = "WEEKLY"
12 | day = "SUN"
13 | hour = "02"
14 | }
15 |
16 | access_policy = {
17 | web_sql = true
18 | }
19 |
20 | performance_diagnostics = {
21 | enabled = true
22 | }
23 |
24 | hosts_definition = [
25 | {
26 | zone = "ru-central1-a"
27 | assign_public_ip = true
28 | subnet_id = var.subnet_id
29 | }
30 | ]
31 |
32 | postgresql_config = {
33 | max_connections = 395
34 | enable_parallel_hash = true
35 | autovacuum_vacuum_scale_factor = 0.34
36 | default_transaction_isolation = "TRANSACTION_ISOLATION_READ_COMMITTED"
37 | shared_preload_libraries = "SHARED_PRELOAD_LIBRARIES_AUTO_EXPLAIN,SHARED_PRELOAD_LIBRARIES_PG_HINT_PLAN"
38 | }
39 |
40 | default_user_settings = {
41 | default_transaction_isolation = "read committed"
42 | log_min_duration_statement = 5000
43 | }
44 |
45 | databases = [
46 | {
47 | name = "test1"
48 | owner = "test1"
49 | lc_collate = "ru_RU.UTF-8"
50 | lc_type = "ru_RU.UTF-8"
51 | extensions = ["uuid-ossp", "xml2"]
52 | }
53 | ]
54 |
55 | owners = [
56 | {
57 | name = "test1"
58 | conn_limit = 15
59 | }
60 | ]
61 |
62 | users = [
63 | {
64 | name = "test1-guest"
65 | conn_limit = 30
66 | permissions = ["test1"]
67 | settings = {
68 | pool_mode = "transaction"
69 | prepared_statements_pooling = true
70 | }
71 | }
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/examples/1-single-node/outputs.tf:
--------------------------------------------------------------------------------
1 | output "postgresql_cluster_id" {
2 | description = "PostgreSQL cluster ID"
3 | value = try(module.db.cluster_id, null)
4 | }
5 |
6 | output "postgresql_cluster_name" {
7 | description = "PostgreSQL cluster name"
8 | value = try(module.db.cluster_name, null)
9 | }
10 |
11 | output "postgresql_cluster_host_names_list" {
12 | description = "PostgreSQL cluster host name list"
13 | value = try(module.db.cluster_host_names_list, null)
14 | }
15 |
16 | output "postgresql_cluster_fqdns_list" {
17 | description = "PostgreSQL cluster FQDNs list"
18 | value = try(module.db.cluster_fqdns_list, null)
19 | }
20 |
21 | output "db_owners" {
22 | description = "A list of DB owners users with password."
23 | sensitive = true
24 | value = try(module.db.owners_data, null)
25 | }
26 |
27 | output "db_users" {
28 | description = "A list of separate DB users with passwords."
29 | sensitive = true
30 | value = try(module.db.users_data, null)
31 | }
32 |
33 | output "postgresql_databases" {
34 | description = "A list of database names."
35 | value = try(module.db.databases, null)
36 | }
37 |
38 | output "postgresql_connection_step_1" {
39 | description = "1 step - Install certificate"
40 | value = try(module.db.connection_step_1, null)
41 | }
42 | output "postgresql_connection_step_2" {
43 | description = "2 step - Execute psql command for a connection to the cluster"
44 | value = try(module.db.connection_step_2, null)
45 | }
46 |
--------------------------------------------------------------------------------
/examples/1-single-node/variables.tf:
--------------------------------------------------------------------------------
1 | variable "network_id" {
2 | description = "PostgreSQL cluster network id"
3 | type = string
4 | }
5 |
6 | variable "subnet_id" {
7 | description = "PostgreSQL cluster subnet id"
8 | type = string
9 | # Isn't necessarily to set the subnet_id if there's the only subnet in the availability zone
10 | default = null
11 | }
12 |
--------------------------------------------------------------------------------
/examples/1-single-node/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.0.0"
3 |
4 | required_providers {
5 | yandex = {
6 | source = "yandex-cloud/yandex"
7 | version = "> 0.8"
8 | }
9 | local = {
10 | source = "hashicorp/local"
11 | version = "2.2.3"
12 | }
13 | random = {
14 | source = "hashicorp/random"
15 | version = "> 3.3"
16 | }
17 | }
18 | }
19 |
20 | provider "yandex" {}
21 |
22 | provider "local" {}
23 |
24 | provider "random" {}
25 |
--------------------------------------------------------------------------------
/examples/2-multi-node/main.tf:
--------------------------------------------------------------------------------
1 | # main.tf
2 |
3 | module "db" {
4 | source = "../../"
5 |
6 | network_id = var.network_id
7 | name = "one-two-tree"
8 | description = "Multi-node PostgreSQL cluster for test purposes"
9 |
10 | maintenance_window = {
11 | type = "WEEKLY"
12 | day = "SUN"
13 | hour = "02"
14 | }
15 |
16 | access_policy = {
17 | web_sql = true
18 | }
19 |
20 | performance_diagnostics = {
21 | enabled = true
22 | }
23 |
24 | hosts_definition = [
25 | {
26 | name = "one"
27 | priority = 0
28 | zone = "ru-central1-a"
29 | assign_public_ip = true
30 | subnet_id = yandex_vpc_subnet.foo.id
31 | },
32 | {
33 | name = "two"
34 | priority = 10
35 | zone = "ru-central1-b"
36 | assign_public_ip = true
37 | subnet_id = yandex_vpc_subnet.bar.id
38 | },
39 | {
40 | name = "suntree"
41 | zone = "ru-central1-b"
42 | assign_public_ip = true
43 | subnet_id = yandex_vpc_subnet.bar.id
44 | replication_source_name = "two"
45 | }
46 | ]
47 |
48 | postgresql_config = {
49 | max_connections = 395
50 | enable_parallel_hash = true
51 | autovacuum_vacuum_scale_factor = 0.34
52 | default_transaction_isolation = "TRANSACTION_ISOLATION_READ_COMMITTED"
53 | shared_preload_libraries = "SHARED_PRELOAD_LIBRARIES_AUTO_EXPLAIN,SHARED_PRELOAD_LIBRARIES_PG_HINT_PLAN"
54 | }
55 |
56 | default_user_settings = {
57 | default_transaction_isolation = "read committed"
58 | log_min_duration_statement = 5000
59 | }
60 |
61 | databases = [
62 | {
63 | name = "test1"
64 | owner = "test1"
65 | lc_collate = "ru_RU.UTF-8"
66 | lc_type = "ru_RU.UTF-8"
67 | extensions = ["uuid-ossp", "xml2"]
68 | }
69 | ]
70 |
71 | owners = [
72 | {
73 | name = "test1"
74 | conn_limit = 15
75 | }
76 | ]
77 |
78 | users = [
79 | {
80 | name = "test1-guest"
81 | conn_limit = 30
82 | permissions = ["test1"]
83 | settings = {
84 | pool_mode = "transaction"
85 | prepared_statements_pooling = true
86 | }
87 | }
88 | ]
89 | }
90 |
--------------------------------------------------------------------------------
/examples/2-multi-node/network.tf:
--------------------------------------------------------------------------------
1 | resource "yandex_vpc_subnet" "foo" {
2 | zone = "ru-central1-a"
3 | network_id = var.network_id
4 | v4_cidr_blocks = ["10.1.0.0/24"]
5 | }
6 |
7 | resource "yandex_vpc_subnet" "bar" {
8 | zone = "ru-central1-b"
9 | network_id = var.network_id
10 | v4_cidr_blocks = ["10.2.0.0/24"]
11 | }
12 |
--------------------------------------------------------------------------------
/examples/2-multi-node/outputs.tf:
--------------------------------------------------------------------------------
1 | output "postgresql_cluster_id" {
2 | description = "PostgreSQL cluster ID"
3 | value = try(module.db.cluster_id, null)
4 | }
5 |
6 | output "postgresql_cluster_name" {
7 | description = "PostgreSQL cluster name"
8 | value = try(module.db.cluster_name, null)
9 | }
10 |
11 | output "postgresql_cluster_host_names_list" {
12 | description = "PostgreSQL cluster host name list"
13 | value = try(module.db.cluster_host_names_list, null)
14 | }
15 |
16 | output "postgresql_cluster_fqdns_list" {
17 | description = "PostgreSQL cluster FQDNs list"
18 | value = try(module.db.cluster_fqdns_list, null)
19 | }
20 |
21 | output "db_owners" {
22 | description = "A list of DB owners users with password."
23 | sensitive = true
24 | value = try(module.db.owners_data, null)
25 | }
26 |
27 | output "db_users" {
28 | description = "A list of separate DB users with passwords."
29 | sensitive = true
30 | value = try(module.db.users_data, null)
31 | }
32 |
33 | output "postgresql_databases" {
34 | description = "A list of database names."
35 | value = try(module.db.databases, null)
36 | }
37 |
38 | output "postgresql_connection_step_1" {
39 | description = "1 step - Install certificate"
40 | value = try(module.db.connection_step_1, null)
41 | }
42 |
43 | output "postgresql_connection_step_2" {
44 | description = "2 step - Execute psql command for a connection to the cluster"
45 | value = try(module.db.connection_step_2, null)
46 | }
47 |
--------------------------------------------------------------------------------
/examples/2-multi-node/variables.tf:
--------------------------------------------------------------------------------
1 | variable "network_id" {
2 | description = "PostgreSQL cluster network id"
3 | type = string
4 | }
5 |
--------------------------------------------------------------------------------
/examples/2-multi-node/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.0.0"
3 |
4 | required_providers {
5 | yandex = {
6 | source = "yandex-cloud/yandex"
7 | version = "> 0.8"
8 | }
9 | local = {
10 | source = "hashicorp/local"
11 | version = "2.2.3"
12 | }
13 | random = {
14 | source = "hashicorp/random"
15 | version = "> 3.3"
16 | }
17 | }
18 | }
19 |
20 | provider "yandex" {}
21 |
22 | provider "local" {}
23 |
24 | provider "random" {}
25 |
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 | data "yandex_client_config" "client" {}
2 |
3 | locals {
4 | folder_id = var.folder_id == null ? data.yandex_client_config.client.folder_id : var.folder_id
5 | }
6 |
7 | # PostgreSQL cluster
8 | resource "yandex_mdb_postgresql_cluster" "this" {
9 | name = var.name
10 | description = var.description
11 | environment = var.environment
12 | network_id = var.network_id
13 | folder_id = local.folder_id
14 | labels = var.labels
15 | host_master_name = var.host_master_name
16 | deletion_protection = var.deletion_protection
17 | security_group_ids = var.security_groups_ids_list
18 |
19 | config {
20 | version = var.pg_version
21 | postgresql_config = var.postgresql_config
22 | autofailover = var.autofailover
23 | backup_retain_period_days = var.backup_retain_period_days
24 |
25 | resources {
26 | disk_size = var.disk_size
27 | disk_type_id = var.disk_type
28 | resource_preset_id = var.resource_preset_id
29 | }
30 |
31 | dynamic "access" {
32 | for_each = range(var.access_policy == null ? 0 : 1)
33 | content {
34 | data_lens = var.access_policy.data_lens
35 | web_sql = var.access_policy.web_sql
36 | serverless = var.access_policy.serverless
37 | data_transfer = var.access_policy.data_transfer
38 | }
39 | }
40 |
41 | dynamic "performance_diagnostics" {
42 | for_each = range(var.performance_diagnostics == null ? 0 : 1)
43 | content {
44 | enabled = var.performance_diagnostics.enabled
45 | sessions_sampling_interval = var.performance_diagnostics.sessions_sampling_interval
46 | statements_sampling_interval = var.performance_diagnostics.statements_sampling_interval
47 | }
48 | }
49 |
50 | dynamic "backup_window_start" {
51 | for_each = range(var.backup_window_start == null ? 0 : 1)
52 | content {
53 | hours = var.backup_window_start.hours
54 | minutes = var.backup_window_start.minutes
55 | }
56 | }
57 |
58 | dynamic "pooler_config" {
59 | for_each = range(var.pooler_config == null ? 0 : 1)
60 | content {
61 | pool_discard = var.pooler_config.pool_discard
62 | pooling_mode = var.pooler_config.pooling_mode
63 | }
64 | }
65 | }
66 |
67 | dynamic "host" {
68 | for_each = var.hosts_definition
69 | content {
70 | name = host.value.name
71 | zone = host.value.zone
72 | subnet_id = host.value.subnet_id
73 | assign_public_ip = host.value.assign_public_ip
74 | priority = host.value.priority
75 | replication_source_name = host.value.replication_source_name
76 | }
77 | }
78 |
79 | dynamic "restore" {
80 | for_each = range(var.restore_parameters == null ? 0 : 1)
81 | content {
82 | backup_id = var.restore_parameters.backup_id
83 | time = var.restore_parameters.time
84 | time_inclusive = var.restore_parameters.time_inclusive
85 | }
86 | }
87 |
88 | dynamic "maintenance_window" {
89 | for_each = range(var.maintenance_window == null ? 0 : 1)
90 | content {
91 | type = var.maintenance_window.type
92 | day = var.maintenance_window.day
93 | hour = var.maintenance_window.hour
94 | }
95 | }
96 |
97 | }
98 |
99 | resource "yandex_dns_recordset" "rw" {
100 | count = var.dns_cname_config != null ? 1 : 0
101 |
102 | zone_id = var.dns_cname_config["zone_id"]
103 | name = "${var.dns_cname_config["name"]}."
104 | type = "CNAME"
105 | ttl = var.dns_cname_config["ttl"]
106 | data = ["c-${yandex_mdb_postgresql_cluster.this.id}.rw.mdb.yandexcloud.net"]
107 | }
108 |
--------------------------------------------------------------------------------
/outputs.tf:
--------------------------------------------------------------------------------
1 | output "cluster_id" {
2 | description = "PostgreSQL cluster ID"
3 | value = yandex_mdb_postgresql_cluster.this.id
4 | }
5 |
6 | output "cluster_name" {
7 | description = "PostgreSQL cluster name"
8 | value = yandex_mdb_postgresql_cluster.this.name
9 | }
10 |
11 | output "cluster_host_names_list" {
12 | description = "PostgreSQL cluster host name"
13 | value = [yandex_mdb_postgresql_cluster.this.host[*].name]
14 | }
15 |
16 | output "cluster_fqdns_list" {
17 | description = "PostgreSQL cluster nodes FQDN list"
18 | value = [yandex_mdb_postgresql_cluster.this.host[*].fqdn]
19 | }
20 |
21 | output "owners_data" {
22 | description = "List of owners with passwords."
23 | sensitive = true
24 | value = [
25 | for u in yandex_mdb_postgresql_user.owner : {
26 | user = u.name
27 | password = u.password
28 | }
29 | ]
30 | }
31 |
32 | output "users_data" {
33 | description = "List of users with passwords."
34 | sensitive = true
35 | value = [
36 | for u in yandex_mdb_postgresql_user.user : {
37 | user = u.name
38 | password = u.password
39 | }
40 | ]
41 | }
42 |
43 | output "databases" {
44 | description = "List of databases names."
45 | value = [for db in var.databases : db.name]
46 | }
47 |
48 | output "connection_step_1" {
49 | description = "1 step - Install certificate"
50 | value = "mkdir --parents ~/.postgresql && curl -sfL 'https://storage.yandexcloud.net/cloud-certs/CA.pem' -o ~/.postgresql/root.crt && chmod 0600 ~/.postgresql/root.crt"
51 | }
52 |
53 | output "connection_step_2" {
54 | description = < v if v.password == null }
3 | length = 16
4 | special = true
5 | min_lower = 1
6 | min_numeric = 1
7 | min_special = 1
8 | min_upper = 1
9 | override_special = "-_()[]{}!%^"
10 | }
11 |
12 | # PostgreSQL databases owners
13 | resource "yandex_mdb_postgresql_user" "owner" {
14 | for_each = length(var.owners) > 0 ? { for owner in var.owners : owner.name => owner } : {}
15 |
16 | cluster_id = yandex_mdb_postgresql_cluster.this.id
17 | name = each.value.name
18 | password = each.value.password == null ? random_password.password[each.value.name].result : each.value.password
19 | grants = each.value.grants
20 | login = each.value.login
21 | conn_limit = each.value.conn_limit
22 | deletion_protection = each.value.deletion_protection
23 | settings = merge(var.default_user_settings, each.value.settings)
24 | }
25 |
26 | # PostgreSQL users with own permissions
27 | resource "yandex_mdb_postgresql_user" "user" {
28 | for_each = length(var.users) > 0 ? { for user in var.users : user.name => user } : {}
29 |
30 | cluster_id = yandex_mdb_postgresql_cluster.this.id
31 | name = each.value.name
32 | password = each.value.password == null ? random_password.password[each.value.name].result : each.value.password
33 | grants = each.value.grants
34 | login = each.value.login
35 | conn_limit = each.value.conn_limit
36 | deletion_protection = each.value.deletion_protection
37 | settings = merge(var.default_user_settings, each.value.settings)
38 |
39 | dynamic "permission" {
40 | for_each = each.value.permissions
41 | content {
42 | database_name = yandex_mdb_postgresql_database.database[permission.value].name
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/variables.tf:
--------------------------------------------------------------------------------
1 | # Variables
2 | variable "name" {
3 | description = "PostgreSQL cluster name"
4 | type = string
5 | default = "pgsql-cluster"
6 | }
7 |
8 | variable "environment" {
9 | description = "Environment type: PRODUCTION or PRESTABLE"
10 | type = string
11 | default = "PRODUCTION"
12 | validation {
13 | condition = contains(["PRODUCTION", "PRESTABLE"], var.environment)
14 | error_message = "Release channel should be PRODUCTION (stable feature set) or PRESTABLE (early bird feature access)."
15 | }
16 | }
17 |
18 | variable "network_id" {
19 | description = "Network id of the PostgreSQL cluster"
20 | type = string
21 | }
22 |
23 | variable "description" {
24 | description = "PostgreSQL cluster description"
25 | type = string
26 | default = "Managed PostgreSQL cluster"
27 | }
28 |
29 | variable "folder_id" {
30 | description = "Yandex Cloud Folder ID where the cluster resides"
31 | type = string
32 | default = null
33 | }
34 |
35 | variable "labels" {
36 | description = "Set of label pairs to assing to the PostgreSQL cluster"
37 | type = map(any)
38 | default = {}
39 | }
40 |
41 | variable "host_master_name" {
42 | description = "Name of the master host."
43 | type = string
44 | default = null
45 | }
46 |
47 | variable "security_groups_ids_list" {
48 | description = "List of security group IDs to which the PostgreSQL cluster belongs"
49 | type = list(string)
50 | default = []
51 | nullable = true
52 | }
53 |
54 | variable "deletion_protection" {
55 | description = "Protects the cluster from deletion"
56 | type = bool
57 | default = false
58 | }
59 |
60 | variable "pg_version" {
61 | description = "PostgreSQL version"
62 | type = string
63 | default = "15"
64 | validation {
65 | condition = contains(["13", "13-1c", "14", "14-1c", "15", "15-1c", "16", "16-1c", "17"], var.pg_version)
66 | error_message = "Allowed PostgreSQL versions are 13, 13-1c, 14, 14-1c, 15, 15-1c, 16, 16-1c, 17."
67 | }
68 | }
69 |
70 | variable "disk_size" {
71 | description = "Disk size for every cluster host"
72 | type = number
73 | default = 20
74 | }
75 |
76 | variable "disk_type" {
77 | description = "Disk type for all cluster hosts"
78 | type = string
79 | default = "network-ssd"
80 | }
81 |
82 | variable "resource_preset_id" {
83 | description = "Preset for hosts"
84 | type = string
85 | default = "s2.micro"
86 | }
87 |
88 | variable "access_policy" {
89 | description = "Access policy from other services to the PostgreSQL cluster."
90 | type = object({
91 | data_lens = optional(bool, null)
92 | web_sql = optional(bool, null)
93 | serverless = optional(bool, null)
94 | data_transfer = optional(bool, null)
95 | })
96 | default = {}
97 | }
98 |
99 | variable "restore_parameters" {
100 | description = <