cluster-builder 0.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of cluster-builder might be problematic. Click here for more details.

@@ -0,0 +1,10 @@
1
+ """
2
+ Cluster Builder - A tool for creating, managing, and destroying K3s clusters.
3
+ """
4
+
5
+ from cluster_builder.swarmchestrate import Swarmchestrate
6
+ from cluster_builder.utils.logging import configure_logging
7
+
8
+ configure_logging()
9
+
10
+ __all__ = ["Swarmchestrate"]
@@ -0,0 +1,355 @@
1
+ """
2
+ Swarmchestrate - Main orchestration class for K3s cluster management.
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ import shutil
8
+ from typing import Optional
9
+
10
+ from dotenv import load_dotenv
11
+
12
+ from cluster_builder.config.postgres import PostgresConfig
13
+ from cluster_builder.config.cluster import ClusterConfig
14
+ from cluster_builder.templates.manager import TemplateManager
15
+ from cluster_builder.infrastructure.executor import CommandExecutor
16
+ from cluster_builder.utils import hcl
17
+
18
+ logger = logging.getLogger("swarmchestrate")
19
+
20
+
21
+ class Swarmchestrate:
22
+ """
23
+ Main class for orchestrating K3s clusters across different cloud providers.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ template_dir: str,
29
+ output_dir: str,
30
+ variables: Optional[dict[str, any]] = None,
31
+ ):
32
+ """
33
+ Initialise the Swarmchestrate class.
34
+
35
+ Args:
36
+ template_dir: Directory containing templates
37
+ output_dir: Directory for outputting generated files
38
+ variables: Optional additional variables for deployments
39
+ """
40
+ self.template_dir = f"{template_dir}"
41
+ self.output_dir = output_dir
42
+
43
+ load_dotenv()
44
+
45
+ try:
46
+ self.pg_config = PostgresConfig.from_env()
47
+ except ValueError as e:
48
+ logger.error(f"Invalid PostgreSQL configuration: {e}")
49
+ raise
50
+
51
+ # Initialise components
52
+ self.template_manager = TemplateManager()
53
+ self.cluster_config = ClusterConfig(
54
+ self.template_manager, output_dir, self.pg_config
55
+ )
56
+
57
+ logger.info(
58
+ f"Initialised with template_dir={template_dir}, output_dir={output_dir}"
59
+ )
60
+
61
+ def get_cluster_output_dir(self, cluster_name: str) -> str:
62
+ """
63
+ Get the output directory path for a specific cluster.
64
+
65
+ Args:
66
+ cluster_name: Name of the cluster
67
+
68
+ Returns:
69
+ Path to the cluster output directory
70
+ """
71
+ return self.cluster_config.get_cluster_output_dir(cluster_name)
72
+
73
+ def generate_random_name(self) -> str:
74
+ """
75
+ Generate a readable random string using names-generator.
76
+
77
+ Returns:
78
+ A randomly generated name
79
+ """
80
+ return self.cluster_config.generate_random_name()
81
+
82
+ def _validate_node_config(self, config: dict[str, any]) -> None:
83
+ """
84
+ Validate node configuration.
85
+
86
+ Args:
87
+ config: Configuration dictionary
88
+
89
+ Raises:
90
+ ValueError: If configuration is invalid
91
+ """
92
+ # Check required fields
93
+ if "cloud" not in config:
94
+ raise ValueError("Cloud provider must be specified in configuration")
95
+
96
+ if "k3s_role" not in config:
97
+ raise ValueError("K3s role must be specified in configuration")
98
+
99
+ # Master IP validation
100
+ has_master_ip = "master_ip" in config and config["master_ip"]
101
+ role = config["k3s_role"]
102
+
103
+ # Cannot add a master node to an existing cluster
104
+ if has_master_ip and role == "master":
105
+ raise ValueError(
106
+ "Cannot add a master node to an existing cluster (master_ip specified with master role)"
107
+ )
108
+
109
+ # Worker/HA nodes require a master IP
110
+ if not has_master_ip and role in ["worker", "ha"]:
111
+ raise ValueError(f"Role '{role}' requires master_ip to be specified")
112
+
113
+ def prepare_infrastructure(
114
+ self, config: dict[str, any]
115
+ ) -> tuple[str, dict[str, any]]:
116
+ """
117
+ Prepare infrastructure configuration for deployment.
118
+
119
+ This method prepares the necessary files and configuration for deployment
120
+ but does not actually deploy the infrastructure.
121
+
122
+ Args:
123
+ config: Configuration dictionary containing cloud, k3s_role, and
124
+ optionally cluster_name and master_ip
125
+
126
+ Returns:
127
+ Tuple containing the cluster directory path and updated configuration
128
+
129
+ Raises:
130
+ ValueError: If required configuration is missing or invalid
131
+ RuntimeError: If file operations fail
132
+ """
133
+ try:
134
+ # Validate the configuration
135
+ self._validate_node_config(config)
136
+
137
+ # Prepare the configuration and files
138
+ cluster_dir, prepared_config = self.cluster_config.prepare(config)
139
+
140
+ # Create provider configuration
141
+ cloud = config["cloud"]
142
+ self.template_manager.create_provider_config(cluster_dir, cloud)
143
+ logger.info(f"Created provider configuration for {cloud}")
144
+
145
+ # Create Terraform files
146
+ main_tf_path = os.path.join(cluster_dir, "main.tf")
147
+ backend_tf_path = os.path.join(cluster_dir, "backend.tf")
148
+
149
+ # Add backend configuration
150
+ hcl.add_backend_config(
151
+ backend_tf_path,
152
+ prepared_config["pg_conn_str"],
153
+ prepared_config["cluster_name"],
154
+ )
155
+ logger.info(f"Added backend configuration to {backend_tf_path}")
156
+
157
+ # Add module block
158
+ target = prepared_config["resource_name"]
159
+ hcl.add_module_block(main_tf_path, target, prepared_config)
160
+ logger.info(f"Added module block to {main_tf_path}")
161
+
162
+ return cluster_dir, prepared_config
163
+
164
+ except Exception as e:
165
+ error_msg = f"Failed to prepare infrastructure: {e}"
166
+ logger.error(error_msg)
167
+ raise RuntimeError(error_msg)
168
+
169
+ def add_node(self, config: dict[str, any], dryrun: bool = False) -> str:
170
+ """
171
+ Add a node to an existing cluster or create a new cluster based on configuration.
172
+
173
+ If master_ip is provided, adds a node to that cluster.
174
+ If master_ip is not provided, creates a new cluster.
175
+
176
+ Args:
177
+ config: Configuration dictionary containing cloud, k3s_role, and
178
+ optionally cluster_name and master_ip
179
+ dryrun: If True, only validate the configuration without deploying
180
+
181
+ Returns:
182
+ The cluster name
183
+
184
+ Raises:
185
+ ValueError: If required configuration is missing or invalid
186
+ RuntimeError: If preparation or deployment fails
187
+ """
188
+ # Prepare the infrastructure configuration
189
+ cluster_dir, prepared_config = self.prepare_infrastructure(config)
190
+
191
+ logger.info(f"Adding node for cluster '{prepared_config['cluster_name']}'")
192
+
193
+ # Deploy the infrastructure
194
+ try:
195
+ self.deploy(cluster_dir, dryrun)
196
+ cluster_name = prepared_config["cluster_name"]
197
+ node_name = prepared_config["resource_name"]
198
+ logger.info(
199
+ f"Successfully added '{node_name}' for cluster '{cluster_name}'"
200
+ )
201
+ return cluster_name
202
+ except Exception as e:
203
+ error_msg = f"Failed to add node: {e}"
204
+ logger.error(error_msg)
205
+ raise RuntimeError(error_msg)
206
+
207
+ def remove_node(
208
+ self, cluster_name: str, resource_name: str, dryrun: bool = False
209
+ ) -> None:
210
+ """
211
+ Remove a specific node from a cluster.
212
+
213
+ This method removes a node's infrastructure component from a cluster by
214
+ removing its module block from the Terraform configuration and then
215
+ reapplying the configuration.
216
+
217
+ Args:
218
+ cluster_name: Name of the cluster containing the node
219
+ resource_name: Resource name of the node to remove
220
+ dryrun: If True, only validate the changes without applying
221
+
222
+ Raises:
223
+ RuntimeError: If node removal fails
224
+ """
225
+ logger.info(f"Removing node '{resource_name}' from cluster '{cluster_name}'...")
226
+
227
+ # Get the directory for the specified cluster
228
+ cluster_dir = self.get_cluster_output_dir(cluster_name)
229
+
230
+ if not os.path.exists(cluster_dir):
231
+ error_msg = f"Cluster directory '{cluster_dir}' not found"
232
+ logger.error(error_msg)
233
+ raise RuntimeError(error_msg)
234
+
235
+ # Path to main.tf
236
+ main_tf_path = os.path.join(cluster_dir, "main.tf")
237
+
238
+ if not os.path.exists(main_tf_path):
239
+ error_msg = f"Main Terraform file not found: {main_tf_path}"
240
+ logger.error(error_msg)
241
+ raise RuntimeError(error_msg)
242
+
243
+ try:
244
+ # Remove the module block for the specified resource
245
+ hcl.remove_module_block(main_tf_path, resource_name)
246
+ logger.info(
247
+ f"Removed module block for '{resource_name}' from {main_tf_path}"
248
+ )
249
+
250
+ self.deploy(cluster_dir, dryrun)
251
+
252
+ if not dryrun:
253
+ logger.info(
254
+ f"Successfully removed node '{resource_name}' from cluster '{cluster_name}'"
255
+ )
256
+
257
+ except Exception as e:
258
+ error_msg = f"Failed to remove node '{resource_name}' from cluster '{cluster_name}': {str(e)}"
259
+ logger.error(error_msg)
260
+ raise RuntimeError(error_msg)
261
+
262
+ def deploy(self, cluster_dir: str, dryrun: bool = False) -> None:
263
+ """
264
+ Execute OpenTofu commands to deploy the K3s component with error handling.
265
+
266
+ Args:
267
+ cluster_dir: Directory containing the Terraform files for the cluster
268
+ dryrun: If True, only run init and plan without applying
269
+
270
+ Raises:
271
+ RuntimeError: If OpenTofu commands fail
272
+ """
273
+ logger.info(f"Updating infrastructure in {cluster_dir}")
274
+
275
+ if not os.path.exists(cluster_dir):
276
+ error_msg = f"Cluster directory '{cluster_dir}' not found"
277
+ logger.error(error_msg)
278
+ raise RuntimeError(error_msg)
279
+
280
+ try:
281
+ # Initialise OpenTofu
282
+ init_command = ["tofu", "init"]
283
+ if dryrun:
284
+ logger.info("Dryrun: will init without backend and validate only")
285
+ init_command.append("-backend=false")
286
+ CommandExecutor.run_command(init_command, cluster_dir, "OpenTofu init")
287
+
288
+ # Validate the deployment
289
+ if dryrun:
290
+ CommandExecutor.run_command(
291
+ ["tofu", "validate"], cluster_dir, "OpenTofu validate"
292
+ )
293
+ logger.info("Infrastructure successfully validated")
294
+ return
295
+
296
+ # Plan the deployment
297
+ CommandExecutor.run_command(["tofu", "plan"], cluster_dir, "OpenTofu plan")
298
+
299
+ # Apply the deployment
300
+ CommandExecutor.run_command(
301
+ ["tofu", "apply", "-auto-approve"], cluster_dir, "OpenTofu apply"
302
+ )
303
+ logger.info("Infrastructure successfully updated")
304
+
305
+ except RuntimeError as e:
306
+ error_msg = f"Failed to deploy infrastructure: {str(e)}"
307
+ logger.error(error_msg)
308
+ raise RuntimeError(error_msg)
309
+
310
+ def destroy(self, cluster_name: str, dryrun: bool = False) -> None:
311
+ """
312
+ Destroy the deployed K3s cluster for the specified cluster_name using OpenTofu.
313
+
314
+ Args:
315
+ cluster_name: Name of the cluster to destroy
316
+
317
+ Raises:
318
+ RuntimeError: If destruction fails
319
+ """
320
+ logger.info(f"Destroying the K3s cluster '{cluster_name}'...")
321
+
322
+ # Get the directory for the specified cluster
323
+ cluster_dir = self.get_cluster_output_dir(cluster_name)
324
+
325
+ if not os.path.exists(cluster_dir):
326
+ error_msg = f"Cluster directory '{cluster_dir}' not found"
327
+ logger.error(error_msg)
328
+ raise RuntimeError(error_msg)
329
+
330
+ if dryrun:
331
+ logger.info("Dryrun: will only delete")
332
+ shutil.rmtree(cluster_dir, ignore_errors=True)
333
+ return
334
+
335
+ try:
336
+ # Plan destruction
337
+ CommandExecutor.run_command(
338
+ ["tofu", "plan", "-destroy"], cluster_dir, "OpenTofu plan destruction"
339
+ )
340
+
341
+ # Execute destruction
342
+ CommandExecutor.run_command(
343
+ ["tofu", "destroy", "-auto-approve"], cluster_dir, "OpenTofu destroy"
344
+ )
345
+
346
+ logger.info(f"Cluster '{cluster_name}' destroyed successfully")
347
+
348
+ # Remove the cluster directory
349
+ shutil.rmtree(cluster_dir, ignore_errors=True)
350
+ logger.info(f"Removed cluster directory: {cluster_dir}")
351
+
352
+ except RuntimeError as e:
353
+ error_msg = f"Failed to destroy cluster '{cluster_name}': {str(e)}"
354
+ logger.error(error_msg)
355
+ raise RuntimeError(error_msg)
@@ -0,0 +1,265 @@
1
+ Metadata-Version: 2.4
2
+ Name: cluster-builder
3
+ Version: 0.1.0
4
+ Summary: Swarmchestrate cluster builder
5
+ Author-email: Gunjan <G.Kotak@westminster.ac.uk>, Jay <J.Deslauriers@westminster.ac.uk>
6
+ License: Apache2
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: names_generator
10
+ Requires-Dist: python-hcl2
11
+ Requires-Dist: lark-parser
12
+ Requires-Dist: jinja2
13
+ Requires-Dist: python-dotenv
14
+ Dynamic: license-file
15
+
16
+ # Swarmchestrate - Cluster Builder
17
+
18
+ This repository contains the codebase for **[cluster-builder]**, which builds K3s clusters for Swarmchestrate using OpenTofu.
19
+
20
+ Key features:
21
+ - **Create**: Provisions infrastructure using OpenTofu and installs K3s.
22
+ - **Add**: Add worker or HA nodes to existing clusters.
23
+ - **Remove**: Selectively remove nodes from existing clusters.
24
+ - **Delete**: Destroys the provisioned infrastructure when no longer required.
25
+
26
+ ---
27
+
28
+ ## Prerequisites
29
+
30
+ Before proceeding, ensure the following prerequisites are installed:
31
+
32
+ 1. **Git**: For cloning the repository.
33
+ 2. **Python**: Version 3.9 or higher.
34
+ 3. **pip**: Python package manager.
35
+ 4. **OpenTofu**: Version 1.6 or higher for infrastructure provisioning.
36
+ 6. **Make**: To run the provided `Makefile`.
37
+ 7. **PostgreSQL**: For storing OpenTofu state.
38
+ 8. (Optional) **Docker**: To create a dev Postgres
39
+ ---
40
+
41
+ ## Getting Started
42
+
43
+ ### 1. Clone the Repository
44
+
45
+ To get started, clone this repository:
46
+
47
+ ```bash
48
+ git clone https://github.com/Swarmchestrate/cluster-builder.git
49
+ ```
50
+
51
+ ### 2. Navigate to the Project Directory
52
+
53
+ ```bash
54
+ cd cluster-builder
55
+ ```
56
+
57
+ ### 3. Install Dependencies and Tools
58
+
59
+ Run the Makefile to install all necessary dependencies, including OpenTofu:
60
+
61
+ ```bash
62
+ make install
63
+ ```
64
+
65
+ This command will:
66
+ - Install Python dependencies listed in requirements.txt.
67
+ - Download and configure OpenTofu for infrastructure management.
68
+
69
+ **Optional**
70
+
71
+ ```bash
72
+ make db
73
+ ```
74
+
75
+ This command will:
76
+ - Spin up an empty dev Postgres DB (in Docker) for storing state
77
+
78
+ ### 4. Populate .env file with access config
79
+
80
+ First, rename or copy the example file to `.env`
81
+
82
+ ```bash
83
+ cp .env_example .env
84
+ ```
85
+
86
+ Then populate postgres connection details and needed cloud credential data.
87
+
88
+ ```
89
+ ## PG Configuration
90
+ POSTGRES_USER=postgres
91
+ POSTGRES_PASSWORD=secret
92
+ POSTGRES_HOST=db.example.com
93
+ POSTGRES_DATABASE=terraform_state
94
+ POSTGRES_SSLMODE=prefer
95
+
96
+ ## AWS Auth
97
+ AWS_REGION=us-west-2
98
+ AWS_ACCESS_KEY=AKIAXXXXXXXXXXXXXXXX
99
+ AWS_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Basic Usage
105
+
106
+ ### Initialisation
107
+
108
+ ```python
109
+ from cluster_builder import Swarmchestrate
110
+
111
+ # Initialise the orchestrator
112
+ orchestrator = Swarmchestrate(
113
+ template_dir="/path/to/templates",
114
+ output_dir="/path/to/output"
115
+ )
116
+ ```
117
+
118
+ ### Creating a New Cluster
119
+
120
+ To create a new k3s cluster, use the `add_node` method with the `master` role:
121
+
122
+ ```python
123
+ # Configuration for a new cluster
124
+ config = {
125
+ "cloud": "aws",
126
+ "k3s_role": "master",
127
+ "ami": "ami-0123456789abcdef",
128
+ "instance_type": "t3.medium",
129
+ "ssh_key_name": "your-ssh-key",
130
+ "k3s_token": "your-k3s-token"
131
+ }
132
+
133
+ # Create the cluster (returns the cluster name)
134
+ cluster_name = orchestrator.add_node(config)
135
+ print(f"Created cluster: {cluster_name}")
136
+ ```
137
+
138
+ ### Adding Nodes to an Existing Cluster
139
+
140
+ To add worker or high-availability nodes to an existing cluster:
141
+
142
+ ```python
143
+ # Configuration for adding a worker node
144
+ worker_config = {
145
+ "cloud": "aws",
146
+ "k3s_role": "worker", # can be "worker" or "ha"
147
+ "master_ip": "1.2.3.4", # IP of the master node
148
+ "cluster_name": "existing-cluster-name", # specify an existing cluster
149
+ "ami": "ami-0123456789abcdef",
150
+ "instance_type": "t2.medium",
151
+ "ssh_key_name": "your-ssh-key",
152
+ "k3s_token": "k3s-cluster-token" # Token of existing cluster
153
+ }
154
+
155
+ # Add the worker node
156
+ cluster_name = orchestrator.add_node(worker_config)
157
+ print(f"Added worker node to cluster: {cluster_name}")
158
+ ```
159
+
160
+ Important requirements:
161
+ - For `k3s_role="worker"` or `k3s_role="ha"`, you must specify a `master_ip`
162
+ - For `k3s_role="master"`, you must not specify a `master_ip`
163
+
164
+ ### Removing a Specific Node
165
+
166
+ To remove a specific node from a cluster:
167
+
168
+ ```python
169
+ # Remove a node by its resource name
170
+ orchestrator.remove_node(
171
+ cluster_name="your-cluster-name",
172
+ resource_name="aws_eloquent_feynman" # The resource identifier of the node
173
+ )
174
+ ```
175
+
176
+ The `remove_node` method:
177
+ 1. Destroys the node's infrastructure resources
178
+ 2. Removes the node's configuration from the cluster
179
+
180
+ ### Destroying an Entire Cluster
181
+
182
+ To completely destroy a cluster and all its nodes:
183
+
184
+ ```python
185
+ # Destroy the entire cluster
186
+ orchestrator.destroy(
187
+ cluster_name="your-cluster-name"
188
+ )
189
+ ```
190
+
191
+ The `destroy` method:
192
+ 1. Destroys all infrastructure resources associated with the cluster
193
+ 2. Removes the cluster directory and configuration files
194
+
195
+ ## Advanced Usage
196
+
197
+ ### Dry Run Mode
198
+
199
+ All operations support a `dryrun` parameter, which validates the configuration
200
+ without making changes. A node created with dryrun should be removed with dryrun.
201
+
202
+ ```python
203
+ # Validate configuration without deploying
204
+ orchestrator.add_node(config, dryrun=True)
205
+
206
+ # Validate removal without destroying
207
+ orchestrator.remove_node(cluster_name, resource_name, dryrun=True)
208
+
209
+ # Validate destruction without destroying
210
+ orchestrator.destroy(cluster_name, dryrun=True)
211
+ ```
212
+
213
+ ### Custom Cluster Names
214
+
215
+ By default, cluster names are generated automatically. To specify a custom name:
216
+
217
+ ```python
218
+ config = {
219
+ "cloud": "aws",
220
+ "k3s_role": "master",
221
+ "cluster_name": "production-cluster",
222
+ # ... other configuration ...
223
+ }
224
+
225
+ orchestrator.add_node(config)
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Template Structure
231
+
232
+ Templates should be organised as follows:
233
+ - `templates/` - Base directory for templates
234
+ - `templates/{cloud}/` - Terraform modules for each cloud provider
235
+ - `templates/{role}_user_data.sh.tpl` - Node initialisation scripts
236
+ - `templates/{cloud}_provider.tf.j2` - Provider configuration templates
237
+
238
+ ---
239
+
240
+ ## Edge Device Requirements
241
+
242
+ To connect **edge devices** as part of your K3s cluster, ensure that the following **ports are open** on each edge device to enable communication within nodes:
243
+
244
+ ### Inbound Rules:
245
+
246
+ | Port Range| Protocol| Purpose |
247
+ |-----------|---------|-------------------------------------------------------------|
248
+ | 2379-2380 | TCP | Internal servers communication for embedded etcd |
249
+ | 6443 | TCP | K3s API server communication |
250
+ | 8472 | UDP | Flannel VXLAN (network overlay) |
251
+ | 10250 | TCP | Kubelet metrics and communication |
252
+ | 51820 | UDP | WireGuard IPv4 (for encrypted networking) |
253
+ | 51821 | UDP | WireGuard IPv6 (for encrypted networking) |
254
+ | 5001 | TCP | Embedded registry (Spegel) |
255
+ | 22 | TCP | SSH access for provisioning and management |
256
+ | 80 | TCP | HTTP communication for web access |
257
+ | 443 | TCP | HTTPS communication for secure access |
258
+ | 53 | UDP | DNS (CoreDNS) for internal service discovery |
259
+ | 5432 | TCP | PostgreSQL database access |
260
+
261
+ ### Outbound Rule:
262
+
263
+ | Port Range| Protocol | Purpose |
264
+ |-----------|----------|--------------------------------------------------------|
265
+ | all | all | Allow all outbound traffic for the system's operations |
@@ -0,0 +1,7 @@
1
+ cluster_builder/__init__.py,sha256=p2Rb2BTVm-ScqCKE38436WsItY1BjVAnvx7zwmneSLs,256
2
+ cluster_builder/swarmchestrate.py,sha256=K3zWA4Ob6XVH0GDlDQW13Q3IHd1gEjHRvBfgtBLi-Wo,12471
3
+ cluster_builder-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
+ cluster_builder-0.1.0.dist-info/METADATA,sha256=TLtMwDzzR5Rt79rie7_wkpKClfEbMzkNvljELcot-WM,7563
5
+ cluster_builder-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
6
+ cluster_builder-0.1.0.dist-info/top_level.txt,sha256=fTW8EW1mcWoeWprjwxSHRWpqfXYX8iN-ByEt8HPXIcs,16
7
+ cluster_builder-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1 @@
1
+ cluster_builder