cpflow 4.1.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +7 -7
  6. data/docs/commands.md +16 -0
  7. data/docs/terraform/details.md +415 -0
  8. data/docs/terraform/example/.controlplane/controlplane.yml +29 -0
  9. data/docs/terraform/example/.controlplane/templates/app.yml +38 -0
  10. data/docs/terraform/example/.controlplane/templates/postgres.yml +30 -0
  11. data/docs/terraform/example/.controlplane/templates/rails.yml +26 -0
  12. data/docs/terraform/overview.md +105 -0
  13. data/lib/command/base.rb +29 -5
  14. data/lib/command/base_sub_command.rb +15 -0
  15. data/lib/command/generate.rb +1 -1
  16. data/lib/command/ps.rb +1 -1
  17. data/lib/command/ps_stop.rb +2 -1
  18. data/lib/command/run.rb +1 -1
  19. data/lib/command/terraform/base.rb +35 -0
  20. data/lib/command/terraform/generate.rb +99 -0
  21. data/lib/command/terraform/import.rb +79 -0
  22. data/lib/core/controlplane.rb +3 -3
  23. data/lib/core/shell.rb +9 -4
  24. data/lib/core/terraform_config/agent.rb +31 -0
  25. data/lib/core/terraform_config/audit_context.rb +31 -0
  26. data/lib/core/terraform_config/base.rb +25 -0
  27. data/lib/core/terraform_config/dsl.rb +102 -0
  28. data/lib/core/terraform_config/generator.rb +184 -0
  29. data/lib/core/terraform_config/gvc.rb +63 -0
  30. data/lib/core/terraform_config/identity.rb +35 -0
  31. data/lib/core/terraform_config/local_variable.rb +30 -0
  32. data/lib/core/terraform_config/policy.rb +151 -0
  33. data/lib/core/terraform_config/provider.rb +22 -0
  34. data/lib/core/terraform_config/required_provider.rb +23 -0
  35. data/lib/core/terraform_config/secret.rb +138 -0
  36. data/lib/core/terraform_config/volume_set.rb +155 -0
  37. data/lib/core/terraform_config/workload/main.tf +316 -0
  38. data/lib/core/terraform_config/workload/required_providers.tf +8 -0
  39. data/lib/core/terraform_config/workload/variables.tf +263 -0
  40. data/lib/core/terraform_config/workload.rb +132 -0
  41. data/lib/cpflow/version.rb +1 -1
  42. data/lib/cpflow.rb +50 -9
  43. data/lib/generator_templates/templates/postgres.yml +1 -1
  44. data/lib/patches/array.rb +8 -0
  45. data/lib/patches/hash.rb +47 -0
  46. data/lib/patches/string.rb +34 -0
  47. data/script/update_command_docs +6 -2
  48. metadata +33 -3
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TerraformConfig
4
+ class VolumeSet < Base # rubocop:disable Metrics/ClassLength
5
+ PERFORMANCE_CLASSES = %w[general-purpose-ssd high-throughput-ssd].freeze
6
+ FILE_SYSTEM_TYPES = %w[xfs ext4].freeze
7
+ MIN_CAPACITY = 10
8
+ MIN_SCALING_FACTOR = 1.1
9
+
10
+ attr_reader :gvc, :name, :initial_capacity, :performance_class, :file_system_type,
11
+ :storage_class_suffix, :description, :tags, :snapshots, :autoscaling
12
+
13
+ def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
14
+ gvc:,
15
+ name:,
16
+ initial_capacity:,
17
+ performance_class:,
18
+ file_system_type:,
19
+ storage_class_suffix: nil,
20
+ description: nil,
21
+ tags: nil,
22
+ snapshots: nil,
23
+ autoscaling: nil
24
+ )
25
+ super()
26
+
27
+ @gvc = gvc
28
+ @name = name
29
+ @initial_capacity = initial_capacity
30
+ @performance_class = performance_class
31
+ @file_system_type = file_system_type
32
+ @storage_class_suffix = storage_class_suffix
33
+ @description = description
34
+ @tags = tags
35
+ @snapshots = snapshots
36
+ @autoscaling = autoscaling
37
+
38
+ validate_attributes!
39
+ end
40
+
41
+ def importable?
42
+ true
43
+ end
44
+
45
+ def reference
46
+ "cpln_volume_set.#{name}"
47
+ end
48
+
49
+ def to_tf
50
+ block :resource, :cpln_volume_set, name do
51
+ base_arguments_tf
52
+ snapshots_tf
53
+ autoscaling_tf
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def validate_attributes!
60
+ validate_initial_capacity!
61
+ validate_performance_class!
62
+ validate_file_system_type!
63
+ validate_autoscaling! if autoscaling
64
+ end
65
+
66
+ def validate_initial_capacity!
67
+ raise ArgumentError, "Initial capacity must be numeric" unless initial_capacity.is_a?(Numeric)
68
+ return if initial_capacity >= MIN_CAPACITY
69
+
70
+ raise ArgumentError, "Initial capacity should be >= #{MIN_CAPACITY}"
71
+ end
72
+
73
+ def validate_performance_class!
74
+ return if PERFORMANCE_CLASSES.include?(performance_class.to_s)
75
+
76
+ raise ArgumentError,
77
+ "Invalid performance class: #{performance_class}. Choose from #{PERFORMANCE_CLASSES.join(', ')}"
78
+ end
79
+
80
+ def validate_file_system_type!
81
+ return if FILE_SYSTEM_TYPES.include?(file_system_type.to_s)
82
+
83
+ raise ArgumentError, "Invalid file system type: #{file_system_type}. Choose from #{FILE_SYSTEM_TYPES.join(', ')}"
84
+ end
85
+
86
+ def validate_autoscaling!
87
+ validate_max_capacity!
88
+ validate_min_free_percentage!
89
+ validate_scaling_factor!
90
+ end
91
+
92
+ def validate_max_capacity!
93
+ max_capacity = autoscaling.fetch(:max_capacity, nil)
94
+ return if max_capacity.nil?
95
+
96
+ raise ArgumentError, "autoscaling.max_capacity must be numeric" unless max_capacity.is_a?(Numeric)
97
+ return if max_capacity >= MIN_CAPACITY
98
+
99
+ raise ArgumentError, "autoscaling.max_capacity should be >= #{MIN_CAPACITY}"
100
+ end
101
+
102
+ def validate_min_free_percentage!
103
+ min_free_percentage = autoscaling.fetch(:min_free_percentage, nil)
104
+ return if min_free_percentage.nil?
105
+
106
+ raise ArgumentError, "autoscaling.min_free_percentage must be numeric" unless min_free_percentage.is_a?(Numeric)
107
+ return if min_free_percentage.between?(1, 100)
108
+
109
+ raise ArgumentError, "autoscaling.min_free_percentage should be between 1 and 100"
110
+ end
111
+
112
+ def validate_scaling_factor!
113
+ scaling_factor = autoscaling.fetch(:scaling_factor, nil)
114
+ return if scaling_factor.nil?
115
+
116
+ raise ArgumentError, "autoscaling.scaling_factor must be numeric" unless scaling_factor.is_a?(Numeric)
117
+ return if scaling_factor >= MIN_SCALING_FACTOR
118
+
119
+ raise ArgumentError, "autoscaling.scaling_factor should be >= #{MIN_SCALING_FACTOR}"
120
+ end
121
+
122
+ def base_arguments_tf
123
+ argument :gvc, gvc
124
+
125
+ argument :name, name
126
+ argument :description, description, optional: true
127
+ argument :tags, tags, optional: true
128
+
129
+ argument :initial_capacity, initial_capacity
130
+ argument :performance_class, performance_class
131
+ argument :storage_class_suffix, storage_class_suffix, optional: true
132
+ argument :file_system_type, file_system_type
133
+ end
134
+
135
+ def snapshots_tf
136
+ return if snapshots.nil?
137
+
138
+ block :snapshots do
139
+ %i[create_final_snapshot retention_duration schedule].each do |arg_name|
140
+ argument arg_name, snapshots.fetch(arg_name, nil), optional: true
141
+ end
142
+ end
143
+ end
144
+
145
+ def autoscaling_tf
146
+ return if autoscaling.nil?
147
+
148
+ block :autoscaling do
149
+ %i[max_capacity min_free_percentage scaling_factor].each do |arg_name|
150
+ argument arg_name, autoscaling.fetch(arg_name, nil), optional: true
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,316 @@
1
+ resource "cpln_workload" "workload" {
2
+ type = var.type
3
+
4
+ gvc = var.gvc
5
+ identity_link = var.identity_link
6
+
7
+ name = var.name
8
+ description = var.description
9
+
10
+ tags = var.tags
11
+ support_dynamic_tags = var.support_dynamic_tags
12
+
13
+ dynamic "container" {
14
+ for_each = var.containers
15
+ iterator = container
16
+ content {
17
+ name = container.key
18
+
19
+ args = container.value.args
20
+ command = container.value.command
21
+ env = container.value.envs
22
+ inherit_env = container.value.inherit_env
23
+ image = container.value.image
24
+
25
+ cpu = container.value.cpu
26
+ memory = container.value.memory
27
+
28
+ dynamic "lifecycle" {
29
+ for_each = container.value.post_start_command != null || container.value.pre_stop_command != null ? [1] : []
30
+ content {
31
+ dynamic "post_start" {
32
+ for_each = container.value.post_start_command != null ? [1] : []
33
+ content {
34
+ exec {
35
+ command = [
36
+ "/bin/bash",
37
+ "-c",
38
+ "[ -f ${container.value.post_start_command} ] && ${container.value.post_start_command} || true",
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ dynamic "pre_stop" {
44
+ for_each = container.value.pre_stop_command != null ? [1] : []
45
+ content {
46
+ exec {
47
+ command = [
48
+ "/bin/bash",
49
+ "-c",
50
+ "[ -f ${container.value.pre_stop_command} ] && ${container.value.pre_stop_command} || true",
51
+ ]
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ dynamic "liveness_probe" {
59
+ for_each = container.value.liveness_probe != null ? [container.value.liveness_probe] : []
60
+ iterator = liveness
61
+ content {
62
+ dynamic "exec" {
63
+ for_each = liveness.value.exec != null ? [liveness.value.exec] : []
64
+ iterator = exec
65
+ content {
66
+ command = exec.value.command
67
+ }
68
+ }
69
+ dynamic "http_get" {
70
+ for_each = liveness.value.http_get != null ? [liveness.value.http_get] : []
71
+ iterator = http_get
72
+ content {
73
+ path = http_get.value.path
74
+ port = http_get.value.port
75
+ scheme = http_get.value.scheme
76
+ http_headers = http_get.value.http_headers
77
+ }
78
+ }
79
+ dynamic "tcp_socket" {
80
+ for_each = liveness.value.tcp_socket != null ? [liveness.value.tcp_socket] : []
81
+ iterator = tcp_socket
82
+ content {
83
+ port = tcp_socket.value.port
84
+ }
85
+ }
86
+ dynamic "grpc" {
87
+ for_each = liveness.value.grpc != null ? [liveness.value.grpc] : []
88
+ iterator = grpc
89
+ content {
90
+ port = grpc.value.port
91
+ }
92
+ }
93
+ failure_threshold = liveness.value.failure_threshold
94
+ initial_delay_seconds = liveness.value.initial_delay_seconds
95
+ period_seconds = liveness.value.period_seconds
96
+ success_threshold = liveness.value.success_threshold
97
+ timeout_seconds = liveness.value.timeout_seconds
98
+ }
99
+ }
100
+
101
+ dynamic "readiness_probe" {
102
+ for_each = container.value.readiness_probe != null ? [container.value.readiness_probe] : []
103
+ iterator = readiness
104
+ content {
105
+ dynamic "exec" {
106
+ for_each = readiness.value.exec != null ? [readiness.value.exec] : []
107
+ iterator = exec
108
+ content {
109
+ command = exec.value.command
110
+ }
111
+ }
112
+ dynamic "http_get" {
113
+ for_each = readiness.value.http_get != null ? [readiness.value.http_get] : []
114
+ iterator = http_get
115
+ content {
116
+ path = http_get.value.path
117
+ port = http_get.value.port
118
+ scheme = http_get.value.scheme
119
+ http_headers = http_get.value.http_headers
120
+ }
121
+ }
122
+ dynamic "tcp_socket" {
123
+ for_each = readiness.value.tcp_socket != null ? [readiness.value.tcp_socket] : []
124
+ iterator = tcp_socket
125
+ content {
126
+ port = tcp_socket.value.port
127
+ }
128
+ }
129
+ dynamic "grpc" {
130
+ for_each = readiness.value.grpc != null ? [readiness.value.grpc] : []
131
+ iterator = grpc
132
+ content {
133
+ port = grpc.value.port
134
+ }
135
+ }
136
+ failure_threshold = readiness.value.failure_threshold
137
+ initial_delay_seconds = readiness.value.initial_delay_seconds
138
+ period_seconds = readiness.value.period_seconds
139
+ success_threshold = readiness.value.success_threshold
140
+ timeout_seconds = readiness.value.timeout_seconds
141
+ }
142
+ }
143
+
144
+ dynamic "ports" {
145
+ for_each = container.value.ports
146
+ iterator = port
147
+ content {
148
+ number = port.value.number
149
+ protocol = port.value.protocol
150
+ }
151
+ }
152
+
153
+ dynamic "volume" {
154
+ for_each = container.value.volumes
155
+ iterator = volume
156
+ content {
157
+ uri = volume.value.uri
158
+ path = volume.value.path
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ dynamic "options" {
165
+ for_each = var.options != null ? [var.options] : []
166
+ iterator = options
167
+ content {
168
+ dynamic "autoscaling" {
169
+ for_each = options.value.autoscaling != null ? [options.value.autoscaling] : []
170
+ iterator = autoscaling
171
+ content {
172
+ metric = autoscaling.value.metric
173
+ metric_percentile = autoscaling.value.metric_percentile
174
+ max_scale = autoscaling.value.max_scale
175
+ min_scale = autoscaling.value.min_scale
176
+ target = autoscaling.value.target
177
+ scale_to_zero_delay = autoscaling.value.scale_to_zero_delay
178
+ max_concurrency = autoscaling.value.max_concurrency
179
+ }
180
+ }
181
+ capacity_ai = options.value.capacity_ai
182
+ suspend = options.value.suspend
183
+ timeout_seconds = options.value.timeout_seconds
184
+ debug = options.value.debug
185
+ }
186
+ }
187
+
188
+ dynamic "local_options" {
189
+ for_each = var.local_options != null ? [var.local_options] : []
190
+ iterator = options
191
+ content {
192
+ dynamic "autoscaling" {
193
+ for_each = options.value.autoscaling != null ? [options.value.autoscaling] : []
194
+ iterator = autoscaling
195
+ content {
196
+ metric = autoscaling.value.metric
197
+ metric_percentile = autoscaling.value.metric_percentile
198
+ max_scale = autoscaling.value.max_scale
199
+ min_scale = autoscaling.value.min_scale
200
+ target = autoscaling.value.target
201
+ scale_to_zero_delay = autoscaling.value.scale_to_zero_delay
202
+ max_concurrency = autoscaling.value.max_concurrency
203
+ }
204
+ }
205
+ location = options.value.location
206
+ capacity_ai = options.value.capacity_ai
207
+ suspend = options.value.suspend
208
+ timeout_seconds = options.value.timeout_seconds
209
+ debug = options.value.debug
210
+ }
211
+ }
212
+
213
+ dynamic "rollout_options" {
214
+ for_each = var.rollout_options != null ? [var.rollout_options] : []
215
+ iterator = rollout_options
216
+ content {
217
+ min_ready_seconds = rollout_options.value.min_ready_seconds
218
+ max_unavailable_replicas = rollout_options.value.max_unavailable_replicas
219
+ max_surge_replicas = rollout_options.value.max_surge_replicas
220
+ scaling_policy = rollout_options.value.scaling_policy
221
+ }
222
+ }
223
+
224
+ dynamic "security_options" {
225
+ for_each = var.security_options != null ? [var.security_options] : []
226
+ iterator = security_options
227
+ content {
228
+ file_system_group_id = security_options.value.file_system_group_id
229
+ }
230
+ }
231
+
232
+ dynamic "firewall_spec" {
233
+ for_each = var.firewall_spec != null ? [var.firewall_spec] : []
234
+ iterator = firewall_spec
235
+ content {
236
+ dynamic "external" {
237
+ for_each = firewall_spec.value.external != null ? [firewall_spec.value.external] : []
238
+ iterator = external
239
+ content {
240
+ inbound_allow_cidr = external.value.inbound_allow_cidr
241
+ outbound_allow_hostname = external.value.outbound_allow_hostname
242
+ outbound_allow_cidr = external.value.outbound_allow_cidr
243
+ dynamic "outbound_allow_port" {
244
+ for_each = external.value.outbound_allow_port
245
+ iterator = outbound_allow_port
246
+ content {
247
+ protocol = outbound_allow_port.value.protocol
248
+ number = outbound_allow_port.value.number
249
+ }
250
+ }
251
+ }
252
+ }
253
+ dynamic "internal" {
254
+ for_each = firewall_spec.value.internal != null ? [firewall_spec.value.internal] : []
255
+ iterator = internal
256
+ content {
257
+ inbound_allow_type = internal.value.inbound_allow_type
258
+ inbound_allow_workload = internal.value.inbound_allow_workload
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ dynamic "load_balancer" {
265
+ for_each = var.load_balancer != null ? [var.load_balancer] : []
266
+ iterator = load_balancer
267
+ content {
268
+ dynamic "direct" {
269
+ for_each = load_balancer.value.direct != null ? [load_balancer.value.direct] : []
270
+ iterator = direct
271
+ content {
272
+ enabled = direct.value.enabled
273
+ dynamic "port" {
274
+ for_each = direct.value.port
275
+ iterator = port
276
+ content {
277
+ external_port = port.value.external_port
278
+ protocol = port.value.protocol
279
+ scheme = port.value.scheme
280
+ container_port = port.value.container_port
281
+ }
282
+ }
283
+ }
284
+ }
285
+ dynamic "geo_location" {
286
+ for_each = load_balancer.value.geo_location != null ? [load_balancer.value.geo_location] : []
287
+ iterator = geo_location
288
+ content {
289
+ enabled = geo_location.value.enabled
290
+ dynamic "headers" {
291
+ for_each = geo_location.value.headers != null ? [geo_location.value.headers] : []
292
+ iterator = headers
293
+ content {
294
+ asn = headers.value.asn
295
+ city = headers.value.city
296
+ country = headers.value.country
297
+ region = headers.value.region
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ dynamic "job" {
306
+ for_each = var.job != null ? [var.job] : []
307
+ iterator = job
308
+ content {
309
+ schedule = job.value.schedule
310
+ concurrency_policy = job.value.concurrency_policy
311
+ history_limit = job.value.history_limit
312
+ restart_policy = job.value.restart_policy
313
+ active_deadline_seconds = job.value.active_deadline_seconds
314
+ }
315
+ }
316
+ }
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ cpln = {
4
+ source = "controlplane-com/cpln"
5
+ version = "~> 1.0"
6
+ }
7
+ }
8
+ }