pangea-kubernetes 0.1.0

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/auto-bump.yml +11 -0
  3. data/.github/workflows/ci.yml +7 -0
  4. data/.github/workflows/release.yml +22 -0
  5. data/.gitignore +6 -0
  6. data/.rspec +3 -0
  7. data/AGENTS.md +3 -0
  8. data/CLAUDE.md +370 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +128 -0
  11. data/README.md +42 -0
  12. data/Rakefile +8 -0
  13. data/flake.lock +2144 -0
  14. data/flake.nix +30 -0
  15. data/gemset.nix +312 -0
  16. data/lib/pangea/kubernetes/architecture.rb +383 -0
  17. data/lib/pangea/kubernetes/backend_registry.rb +117 -0
  18. data/lib/pangea/kubernetes/backends/aws_eks.rb +203 -0
  19. data/lib/pangea/kubernetes/backends/aws_nixos.rb +1347 -0
  20. data/lib/pangea/kubernetes/backends/azure_aks.rb +145 -0
  21. data/lib/pangea/kubernetes/backends/azure_nixos.rb +275 -0
  22. data/lib/pangea/kubernetes/backends/base.rb +116 -0
  23. data/lib/pangea/kubernetes/backends/gcp_gke.rb +176 -0
  24. data/lib/pangea/kubernetes/backends/gcp_nixos.rb +240 -0
  25. data/lib/pangea/kubernetes/backends/hcloud_k3s.rb +181 -0
  26. data/lib/pangea/kubernetes/backends/nixos_base.rb +235 -0
  27. data/lib/pangea/kubernetes/bare_metal/cloud_init.rb +196 -0
  28. data/lib/pangea/kubernetes/bare_metal/cluster_reference.rb +72 -0
  29. data/lib/pangea/kubernetes/load_balancer.rb +157 -0
  30. data/lib/pangea/kubernetes/network_backend_registry.rb +54 -0
  31. data/lib/pangea/kubernetes/network_backends/base.rb +78 -0
  32. data/lib/pangea/kubernetes/network_backends/cilium.rb +105 -0
  33. data/lib/pangea/kubernetes/network_backends/vpc_cni.rb +36 -0
  34. data/lib/pangea/kubernetes/types/argocd_config.rb +55 -0
  35. data/lib/pangea/kubernetes/types/control_plane_config.rb +65 -0
  36. data/lib/pangea/kubernetes/types/etcd_config.rb +64 -0
  37. data/lib/pangea/kubernetes/types/firewall_config.rb +39 -0
  38. data/lib/pangea/kubernetes/types/k3s_config.rb +112 -0
  39. data/lib/pangea/kubernetes/types/kernel_config.rb +31 -0
  40. data/lib/pangea/kubernetes/types/kubernetes_config.rb +129 -0
  41. data/lib/pangea/kubernetes/types/persistent_state_config.rb +100 -0
  42. data/lib/pangea/kubernetes/types/pki_config.rb +48 -0
  43. data/lib/pangea/kubernetes/types/secrets_config.rb +41 -0
  44. data/lib/pangea/kubernetes/types/vpn_config.rb +188 -0
  45. data/lib/pangea/kubernetes/types/wait_for_dns_config.rb +35 -0
  46. data/lib/pangea/kubernetes/types.rb +521 -0
  47. data/lib/pangea-kubernetes/version.rb +5 -0
  48. data/lib/pangea-kubernetes.rb +43 -0
  49. data/pangea-kubernetes.gemspec +33 -0
  50. metadata +192 -0
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2025 The Pangea Authors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'pangea/kubernetes/backends/base'
18
+
19
+ module Pangea
20
+ module Kubernetes
21
+ module Backends
22
+ # Azure AKS backend — creates managed AKS clusters.
23
+ # AKS bundles default node pool with the cluster resource,
24
+ # so create_cluster handles both.
25
+ module AzureAks
26
+ include Base
27
+
28
+ class << self
29
+ def backend_name = :azure
30
+ def managed_kubernetes? = true
31
+ def required_gem = 'pangea-azure'
32
+
33
+ def load_provider!
34
+ require required_gem
35
+ rescue LoadError => e
36
+ raise LoadError,
37
+ "Backend :azure requires gem 'pangea-azure'. " \
38
+ "Add it to your Gemfile: gem 'pangea-azure'\n" \
39
+ "Original error: #{e.message}"
40
+ end
41
+
42
+ # Create Azure VNet + subnet
43
+ def create_network(ctx, name, config, tags)
44
+ network = Architecture::AzureNetworkResult.new
45
+
46
+ network.resource_group = ctx.azurerm_resource_group(
47
+ :"#{name}_rg",
48
+ name: "#{name}-rg",
49
+ location: config.region,
50
+ tags: tags
51
+ )
52
+
53
+ network.vnet = ctx.azurerm_virtual_network(
54
+ :"#{name}_vnet",
55
+ name: "#{name}-vnet",
56
+ resource_group_name: network.resource_group.ref(:name),
57
+ location: config.region,
58
+ address_space: [config.network&.vpc_cidr || '10.0.0.0/16'],
59
+ tags: tags
60
+ )
61
+ network.vpc = network.vnet
62
+
63
+ subnet = ctx.azurerm_subnet(
64
+ :"#{name}_subnet",
65
+ name: "#{name}-subnet",
66
+ resource_group_name: network.resource_group.ref(:name),
67
+ virtual_network_name: network.vnet.ref(:name),
68
+ address_prefixes: [config.network&.pod_cidr || '10.0.1.0/24']
69
+ )
70
+ network.add_subnet(:subnet, subnet)
71
+
72
+ network
73
+ end
74
+
75
+ # AKS uses managed identity — no standalone IAM resources needed
76
+ def create_iam(_ctx, _name, _config, _tags)
77
+ Architecture::IamResult.new
78
+ end
79
+
80
+ # Create AKS cluster with default node pool
81
+ def create_cluster(ctx, name, config, result, tags)
82
+ system_pool = config.system_node_pool
83
+ rg_name = config.resource_group_name || result.network&.dig(:resource_group)&.name || "#{name}-rg"
84
+ dns_prefix = config.dns_prefix || name.to_s
85
+
86
+ cluster_attrs = {
87
+ name: "#{name}-cluster",
88
+ resource_group_name: rg_name,
89
+ location: config.region,
90
+ dns_prefix: dns_prefix,
91
+ kubernetes_version: config.kubernetes_version,
92
+ default_node_pool: {
93
+ name: system_pool.name.to_s[0..11], # AKS max 12 chars
94
+ vm_size: system_pool.instance_types.first,
95
+ node_count: system_pool.effective_desired_size,
96
+ min_count: system_pool.min_size,
97
+ max_count: system_pool.max_size,
98
+ enable_auto_scaling: true,
99
+ os_disk_size_gb: system_pool.disk_size_gb
100
+ },
101
+ identity: { type: 'SystemAssigned' },
102
+ tags: tags
103
+ }
104
+
105
+ cluster_attrs[:sku_tier] = 'Standard' if tags[:Environment] == 'production'
106
+
107
+ # Network profile
108
+ if result.network&.dig(:subnet)
109
+ cluster_attrs[:default_node_pool][:vnet_subnet_id] = result.network[:subnet].id
110
+ end
111
+
112
+ ctx.azurerm_kubernetes_cluster(:"#{name}_cluster", cluster_attrs)
113
+ end
114
+
115
+ # Create additional AKS node pool
116
+ def create_node_pool(ctx, name, cluster_ref, pool_config, tags)
117
+ pool_name = :"#{name}_#{pool_config.name}"
118
+
119
+ node_pool_attrs = {
120
+ name: pool_config.name.to_s[0..11], # AKS max 12 chars
121
+ kubernetes_cluster_id: cluster_ref.id,
122
+ vm_size: pool_config.instance_types.first,
123
+ node_count: pool_config.effective_desired_size,
124
+ min_count: pool_config.min_size,
125
+ max_count: pool_config.max_size,
126
+ enable_auto_scaling: true,
127
+ os_disk_size_gb: pool_config.disk_size_gb,
128
+ tags: tags.merge(NodePool: pool_config.name.to_s)
129
+ }
130
+
131
+ node_pool_attrs[:node_labels] = pool_config.labels if pool_config.labels.any?
132
+
133
+ if pool_config.taints.any?
134
+ node_pool_attrs[:node_taints] = pool_config.taints.map do |t|
135
+ "#{t[:key]}=#{t[:value]}:#{t[:effect]}"
136
+ end
137
+ end
138
+
139
+ ctx.azurerm_kubernetes_cluster_node_pool(pool_name, node_pool_attrs)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2025 The Pangea Authors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'pangea/kubernetes/backends/base'
18
+ require 'pangea/kubernetes/backends/nixos_base'
19
+
20
+ module Pangea
21
+ module Kubernetes
22
+ module Backends
23
+ # Azure NixOS backend — Azure VMs running NixOS with k3s/k8s
24
+ # via blackmatter-kubernetes modules.
25
+ #
26
+ # Uses:
27
+ # - Azure VMs for control plane (static)
28
+ # - VM Scale Sets (VMSS) for worker node pools
29
+ # - VNet + NSG for networking
30
+ #
31
+ # No managed K8s services (AKS) — all k3s/k8s managed by NixOS.
32
+ module AzureNixos
33
+ include Base
34
+ extend NixosBase
35
+
36
+ class << self
37
+ def backend_name = :azure_nixos
38
+ def managed_kubernetes? = false
39
+ def required_gem = 'pangea-azure'
40
+
41
+ def load_provider!
42
+ require required_gem
43
+ rescue LoadError => e
44
+ raise LoadError,
45
+ "Backend :azure_nixos requires gem 'pangea-azure'. " \
46
+ "Add it to your Gemfile: gem 'pangea-azure'\n" \
47
+ "Original error: #{e.message}"
48
+ end
49
+
50
+ # Create Resource Group + VNet + Subnet + NSG
51
+ def create_network(ctx, name, config, tags)
52
+ network = Architecture::AzureNetworkResult.new
53
+
54
+ network.resource_group = ctx.azurerm_resource_group(
55
+ :"#{name}_rg",
56
+ name: "#{name}-rg",
57
+ location: config.region,
58
+ tags: tags
59
+ )
60
+
61
+ network.vnet = ctx.azurerm_virtual_network(
62
+ :"#{name}_vnet",
63
+ name: "#{name}-vnet",
64
+ resource_group_name: network.resource_group.ref(:name),
65
+ location: config.region,
66
+ address_space: [config.network&.vpc_cidr || '10.0.0.0/16'],
67
+ tags: tags
68
+ )
69
+ network.vpc = network.vnet
70
+
71
+ subnet = ctx.azurerm_subnet(
72
+ :"#{name}_subnet",
73
+ name: "#{name}-subnet",
74
+ resource_group_name: network.resource_group.ref(:name),
75
+ virtual_network_name: network.vnet.ref(:name),
76
+ address_prefixes: [config.network&.pod_cidr || '10.0.1.0/24']
77
+ )
78
+ network.add_subnet(:subnet, subnet)
79
+
80
+ # Network Security Group
81
+ network.nsg = ctx.azurerm_network_security_group(
82
+ :"#{name}_nsg",
83
+ name: "#{name}-nsg",
84
+ resource_group_name: network.resource_group.ref(:name),
85
+ location: config.region,
86
+ security_rule: azure_nsg_rules(config.distribution),
87
+ tags: tags
88
+ )
89
+ network.sg = network.nsg
90
+
91
+ # Associate NSG with subnet
92
+ ctx.azurerm_subnet_network_security_group_association(
93
+ :"#{name}_nsg_assoc",
94
+ subnet_id: subnet.id,
95
+ network_security_group_id: network.nsg.id
96
+ )
97
+
98
+ network
99
+ end
100
+
101
+ # No standalone IAM — Azure VMs use Managed Identity
102
+ def create_iam(_ctx, _name, _config, _tags)
103
+ Architecture::IamResult.new
104
+ end
105
+
106
+ # Create control plane Azure VMs (static)
107
+ def create_cluster(ctx, name, config, result, tags)
108
+ nixos_create_cluster(ctx, name, config, result, tags)
109
+ end
110
+
111
+ # Create worker node pool via VMSS (VM Scale Set)
112
+ def create_node_pool(ctx, name, cluster_ref, pool_config, tags)
113
+ nixos_create_node_pool(ctx, name, cluster_ref, pool_config, tags)
114
+ end
115
+
116
+ # --- NixosBase template hooks ---
117
+
118
+ def create_compute_instance(ctx, name, config, result, cloud_init, index, tags)
119
+ system_pool = config.system_node_pool
120
+ vm_size = system_pool.instance_types.first
121
+ image_id = config.azure_image_id || config.nixos&.image_id
122
+ rg_name = config.resource_group_name || result.network&.dig(:resource_group)&.name || "#{name}-rg"
123
+
124
+ # Network interface
125
+ nic = ctx.azurerm_network_interface(
126
+ :"#{name}_cp_#{index}_nic",
127
+ name: "#{name}-cp-#{index}-nic",
128
+ resource_group_name: rg_name,
129
+ location: config.region,
130
+ ip_configuration: {
131
+ name: 'internal',
132
+ subnet_id: result.network&.dig(:subnet)&.id,
133
+ private_ip_address_allocation: 'Dynamic',
134
+ public_ip_address_id: nil
135
+ },
136
+ tags: tags
137
+ )
138
+
139
+ ctx.azurerm_linux_virtual_machine(
140
+ :"#{name}_cp_#{index}",
141
+ name: "#{name}-cp-#{index}",
142
+ resource_group_name: rg_name,
143
+ location: config.region,
144
+ size: vm_size,
145
+ network_interface_ids: [nic.id],
146
+ admin_username: 'nixos',
147
+ admin_ssh_key: {
148
+ username: 'nixos',
149
+ public_key: '${file("~/.ssh/id_ed25519.pub")}'
150
+ },
151
+ os_disk: {
152
+ caching: 'ReadWrite',
153
+ storage_account_type: 'Premium_LRS',
154
+ disk_size_gb: system_pool.disk_size_gb
155
+ },
156
+ source_image_id: image_id,
157
+ custom_data: cloud_init,
158
+ identity: { type: 'SystemAssigned' },
159
+ tags: tags.merge(
160
+ Role: 'control-plane',
161
+ NodeIndex: index.to_s,
162
+ Distribution: config.distribution.to_s
163
+ )
164
+ )
165
+ end
166
+
167
+ def create_worker_pool(ctx, name, _cluster_ref, pool_config, cloud_init, tags)
168
+ pool_name = :"#{name}_#{pool_config.name}"
169
+ vm_size = pool_config.instance_types.first
170
+
171
+ vmss = ctx.azurerm_linux_virtual_machine_scale_set(
172
+ pool_name,
173
+ name: "#{name}-#{pool_config.name}-vmss",
174
+ resource_group_name: tags[:ResourceGroupName] || "#{name}-rg",
175
+ location: tags[:Region] || 'eastus',
176
+ sku: vm_size,
177
+ instances: pool_config.effective_desired_size,
178
+ admin_username: 'nixos',
179
+ admin_ssh_key: {
180
+ username: 'nixos',
181
+ public_key: '${file("~/.ssh/id_ed25519.pub")}'
182
+ },
183
+ os_disk: {
184
+ caching: 'ReadWrite',
185
+ storage_account_type: 'Premium_LRS',
186
+ disk_size_gb: pool_config.disk_size_gb
187
+ },
188
+ source_image_id: tags[:ImageId],
189
+ custom_data: cloud_init,
190
+ network_interface: {
191
+ name: "#{name}-#{pool_config.name}-nic",
192
+ primary: true,
193
+ ip_configuration: {
194
+ name: 'internal',
195
+ subnet_id: tags[:SubnetId],
196
+ primary: true
197
+ }
198
+ },
199
+ identity: { type: 'SystemAssigned' },
200
+ tags: tags.merge(
201
+ NodePool: pool_config.name.to_s,
202
+ Role: 'worker'
203
+ )
204
+ )
205
+
206
+ # Autoscale setting
207
+ ctx.azurerm_monitor_autoscale_setting(
208
+ :"#{pool_name}_autoscale",
209
+ name: "#{name}-#{pool_config.name}-autoscale",
210
+ resource_group_name: tags[:ResourceGroupName] || "#{name}-rg",
211
+ location: tags[:Region] || 'eastus',
212
+ target_resource_id: vmss.id,
213
+ profile: {
214
+ name: 'default',
215
+ capacity: {
216
+ default: pool_config.effective_desired_size,
217
+ minimum: pool_config.min_size,
218
+ maximum: pool_config.max_size
219
+ },
220
+ rule: [{
221
+ metric_trigger: {
222
+ metric_name: 'Percentage CPU',
223
+ metric_resource_id: vmss.id,
224
+ operator: 'GreaterThan',
225
+ threshold: 70,
226
+ time_aggregation: 'Average',
227
+ time_grain: 'PT1M',
228
+ time_window: 'PT5M',
229
+ statistic: 'Average'
230
+ },
231
+ scale_action: {
232
+ direction: 'Increase',
233
+ type: 'ChangeCount',
234
+ value: 1,
235
+ cooldown: 'PT5M'
236
+ }
237
+ }]
238
+ }
239
+ )
240
+
241
+ vmss
242
+ end
243
+
244
+ private
245
+
246
+ def azure_nsg_rules(distribution)
247
+ priority = 100
248
+ base_firewall_ports(distribution).map do |_port_name, port_def|
249
+ rule = {
250
+ name: nsg_rule_name(port_def[:description]),
251
+ priority: priority,
252
+ direction: 'Inbound',
253
+ access: 'Allow',
254
+ protocol: port_def[:protocol] == :tcp ? 'Tcp' : 'Udp',
255
+ source_port_range: '*',
256
+ destination_port_range: port_def[:port].to_s,
257
+ source_address_prefix: port_def[:public] ? '*' : '10.0.0.0/8',
258
+ destination_address_prefix: '*'
259
+ }
260
+ priority += 10
261
+ rule
262
+ end
263
+ end
264
+
265
+ # Convert description to Azure NSG rule name (strip spaces/hyphens, capitalize each word)
266
+ # "controller-manager" => "ControllerManager", "scheduler" => "Scheduler"
267
+ # Single words like "SSH", "HTTPS", "Kubelet" pass through unchanged
268
+ def nsg_rule_name(description)
269
+ description.split(/[\s-]+/).map { |w| w.sub(/\A./, &:upcase) }.join
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2025 The Pangea Authors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Pangea
18
+ module Kubernetes
19
+ module Backends
20
+ # Contract interface for Kubernetes backends. Each backend module
21
+ # must implement these class methods (via class << self):
22
+ #
23
+ # Identity methods:
24
+ # backend_name → Symbol (:aws, :gcp, :azure, :hcloud, etc.)
25
+ # managed_kubernetes? → true for EKS/GKE/AKS, false for NixOS/k3s
26
+ # required_gem → String gem name to require
27
+ # load_provider! → Require the provider gem (or raise LoadError)
28
+ #
29
+ # Infrastructure pipeline methods (all class-level):
30
+ # create_network(ctx, name, config, tags) → Pangea::Contracts::NetworkResult
31
+ # create_iam(ctx, name, config, tags) → Pangea::Contracts::IamResult
32
+ # create_cluster(ctx, name, config, result, tags) → control plane ref
33
+ # create_node_pool(ctx, name, cluster_ref, pool_config, tags) → ResourceReference
34
+ #
35
+ # Backends implement all pipeline methods in `class << self` so they
36
+ # are called as e.g. AwsNixos.create_network(ctx, ...).
37
+ module Base
38
+ def self.included(base)
39
+ base.extend(ClassMethods)
40
+ end
41
+
42
+ module ClassMethods
43
+ def backend_name
44
+ raise NotImplementedError, "#{self} must implement .backend_name"
45
+ end
46
+
47
+ def managed_kubernetes?
48
+ raise NotImplementedError, "#{self} must implement .managed_kubernetes?"
49
+ end
50
+
51
+ def required_gem
52
+ raise NotImplementedError, "#{self} must implement .required_gem"
53
+ end
54
+
55
+ def load_provider!
56
+ require required_gem
57
+ rescue LoadError => e
58
+ raise LoadError,
59
+ "Backend #{backend_name} requires gem '#{required_gem}'. " \
60
+ "Add it to your Gemfile: gem '#{required_gem}'\n" \
61
+ "Original error: #{e.message}"
62
+ end
63
+
64
+ # Create networking resources (VPC, subnets, security groups, etc.).
65
+ # Must return a Pangea::Contracts::NetworkResult (or subclass).
66
+ #
67
+ # @param ctx [Object] Synthesizer context (provides resource methods)
68
+ # @param name [Symbol] Cluster name
69
+ # @param config [Types::ClusterConfig] Cluster configuration
70
+ # @param tags [Hash] Resource tags
71
+ # @return [Pangea::Contracts::NetworkResult]
72
+ def create_network(_ctx, _name, _config, _tags)
73
+ raise NotImplementedError, "#{self} must implement .create_network"
74
+ end
75
+
76
+ # Create IAM resources (roles, policies, service accounts).
77
+ # Must return a Pangea::Contracts::IamResult (or subclass).
78
+ #
79
+ # @param ctx [Object] Synthesizer context
80
+ # @param name [Symbol] Cluster name
81
+ # @param config [Types::ClusterConfig] Cluster configuration
82
+ # @param tags [Hash] Resource tags
83
+ # @return [Pangea::Contracts::IamResult]
84
+ def create_iam(_ctx, _name, _config, _tags)
85
+ raise NotImplementedError, "#{self} must implement .create_iam"
86
+ end
87
+
88
+ # Create the cluster control plane (EKS cluster, ASG+NLB, GKE cluster, etc.).
89
+ # Return type is backend-specific (ControlPlaneRef, resource ref, etc.).
90
+ #
91
+ # @param ctx [Object] Synthesizer context
92
+ # @param name [Symbol] Cluster name
93
+ # @param config [Types::ClusterConfig] Cluster configuration
94
+ # @param result [Pangea::Contracts::ArchitectureResult] Accumulated result with network/iam
95
+ # @param tags [Hash] Resource tags
96
+ # @return [Object] Control plane reference (wrapped in ClusterResult by Architecture)
97
+ def create_cluster(_ctx, _name, _config, _result, _tags)
98
+ raise NotImplementedError, "#{self} must implement .create_cluster"
99
+ end
100
+
101
+ # Create a worker node pool for the cluster.
102
+ #
103
+ # @param ctx [Object] Synthesizer context
104
+ # @param name [Symbol] Cluster name
105
+ # @param cluster_ref [Object] Reference to the control plane
106
+ # @param pool_config [Types::NodePoolConfig] Node pool configuration
107
+ # @param tags [Hash] Resource tags
108
+ # @return [Object] Node pool resource reference
109
+ def create_node_pool(_ctx, _name, _cluster_ref, _pool_config, _tags)
110
+ raise NotImplementedError, "#{self} must implement .create_node_pool"
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end