leverage 2.2.0rc2__tar.gz → 2.2.0rc4__tar.gz
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.
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/PKG-INFO +3 -3
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/__init__.py +1 -7
- leverage-2.2.0rc4/leverage/_backend_config.py +172 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/_internals.py +31 -4
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/_utils.py +11 -71
- leverage-2.2.0rc4/leverage/leverage.py +47 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/modules/__init__.py +2 -1
- leverage-2.2.0rc4/leverage/modules/auth.py +379 -0
- leverage-2.2.0rc4/leverage/modules/aws.py +335 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/modules/credentials.py +80 -69
- leverage-2.2.0rc4/leverage/modules/kubectl.py +168 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/modules/project.py +14 -12
- leverage-2.2.0rc4/leverage/modules/runner.py +133 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/modules/tf.py +157 -189
- leverage-2.2.0rc4/leverage/modules/tfautomv.py +38 -0
- leverage-2.2.0rc4/leverage/modules/tfrunner.py +94 -0
- leverage-2.2.0rc4/leverage/modules/utils.py +49 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/path.py +34 -69
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/pyproject.toml +4 -4
- leverage-2.2.0rc2/leverage/container.py +0 -716
- leverage-2.2.0rc2/leverage/containers/kubectl.py +0 -145
- leverage-2.2.0rc2/leverage/leverage.py +0 -68
- leverage-2.2.0rc2/leverage/modules/auth.py +0 -157
- leverage-2.2.0rc2/leverage/modules/aws.py +0 -225
- leverage-2.2.0rc2/leverage/modules/kubectl.py +0 -43
- leverage-2.2.0rc2/leverage/modules/shell.py +0 -44
- leverage-2.2.0rc2/leverage/modules/tfautomv.py +0 -39
- leverage-2.2.0rc2/leverage/modules/utils.py +0 -43
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/LICENSE.txt +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/README.md +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/_parsing.py +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/conf.py +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/logger.py +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/modules/run.py +0 -0
- {leverage-2.2.0rc2 → leverage-2.2.0rc4}/leverage/tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: leverage
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.0rc4
|
|
4
4
|
Summary: Binbash Leverage Command-Line tool.
|
|
5
5
|
Home-page: https://github.com/binbashar/leverage
|
|
6
6
|
License: MIT
|
|
@@ -21,10 +21,10 @@ Classifier: Topic :: Software Development :: Build Tools
|
|
|
21
21
|
Requires-Dist: boto3 (==1.33.2)
|
|
22
22
|
Requires-Dist: click (==8.0.1)
|
|
23
23
|
Requires-Dist: configupdater (==3.2)
|
|
24
|
-
Requires-Dist: docker (==6.1.0)
|
|
25
|
-
Requires-Dist: dockerpty (==0.4.1)
|
|
26
24
|
Requires-Dist: docutils (==0.17.1)
|
|
27
25
|
Requires-Dist: jinja2 (==3.0.1)
|
|
26
|
+
Requires-Dist: prompt-toolkit (==3.0.51)
|
|
27
|
+
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
28
28
|
Requires-Dist: python-hcl2 (==7.3.1)
|
|
29
29
|
Requires-Dist: questionary (==1.10.0)
|
|
30
30
|
Requires-Dist: requests (==2.31)
|
|
@@ -4,13 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# pylint: disable=wrong-import-position
|
|
6
6
|
|
|
7
|
-
__version__ = "2.2.
|
|
8
|
-
__toolbox_version__ = "1.3.5-0.2.0"
|
|
9
|
-
|
|
10
|
-
MINIMUM_VERSIONS = {
|
|
11
|
-
"TERRAFORM": "1.3.5",
|
|
12
|
-
"TOOLBOX": "0.2.4",
|
|
13
|
-
}
|
|
7
|
+
__version__ = "2.2.0rc4"
|
|
14
8
|
|
|
15
9
|
import sys
|
|
16
10
|
from shutil import which
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Utilities for modifying Terraform backend configuration."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Union, Optional
|
|
6
|
+
|
|
7
|
+
import hcl2
|
|
8
|
+
import lark
|
|
9
|
+
|
|
10
|
+
from leverage._utils import ExitError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_backend_key(config_file_path: Union[str, Path], key: str) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Set or update the backend key in a Terraform config.tf file.
|
|
16
|
+
|
|
17
|
+
This function modifies the Terraform backend configuration to set the S3 state key.
|
|
18
|
+
It preserves all comments, formatting, and other HCL code in the file by using
|
|
19
|
+
string manipulation to surgically modify only the key attribute.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
config_file_path: Path to the config.tf file
|
|
23
|
+
key: The backend key value to set (e.g., "apps-devstg/notifications/terraform.tfstate")
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ExitError: If the file cannot be found, cannot be parsed, or does not contain a
|
|
27
|
+
terraform block with an S3 backend
|
|
28
|
+
"""
|
|
29
|
+
config_file_path = Path(config_file_path)
|
|
30
|
+
|
|
31
|
+
# Validate file exists
|
|
32
|
+
if not config_file_path.exists():
|
|
33
|
+
raise ExitError(1, f"Config file not found: {config_file_path}")
|
|
34
|
+
|
|
35
|
+
# Read the file content
|
|
36
|
+
content = config_file_path.read_text()
|
|
37
|
+
|
|
38
|
+
# Parse the config file to validate structure
|
|
39
|
+
try:
|
|
40
|
+
config_tf = hcl2.loads(content)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise ExitError(1, f"Failed to parse config.tf: {e}")
|
|
43
|
+
|
|
44
|
+
# Validate that the file contains a terraform backend block with S3
|
|
45
|
+
if not (
|
|
46
|
+
"terraform" in config_tf
|
|
47
|
+
and config_tf["terraform"]
|
|
48
|
+
and isinstance(config_tf["terraform"], list)
|
|
49
|
+
and "backend" in config_tf["terraform"][0]
|
|
50
|
+
and config_tf["terraform"][0]["backend"]
|
|
51
|
+
and isinstance(config_tf["terraform"][0]["backend"], list)
|
|
52
|
+
and "s3" in config_tf["terraform"][0]["backend"][0]
|
|
53
|
+
):
|
|
54
|
+
raise ExitError(
|
|
55
|
+
1,
|
|
56
|
+
f"Malformed config.tf: File must contain a terraform block with an S3 backend. "
|
|
57
|
+
f"Expected structure:\n"
|
|
58
|
+
f"terraform {{\n"
|
|
59
|
+
f' backend "s3" {{\n'
|
|
60
|
+
f" # configuration\n"
|
|
61
|
+
f" }}\n"
|
|
62
|
+
f"}}",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Check if key already exists
|
|
66
|
+
backend_config = config_tf["terraform"][0]["backend"][0]["s3"]
|
|
67
|
+
key_exists = "key" in backend_config
|
|
68
|
+
|
|
69
|
+
# Modify the file content
|
|
70
|
+
modified_content = _modify_backend_key(content, key, key_exists)
|
|
71
|
+
|
|
72
|
+
# Write back to file
|
|
73
|
+
config_file_path.write_text(modified_content)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _modify_backend_key(content: str, key: str, key_exists: bool) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Modify the backend key in the HCL content.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
content: The original file content
|
|
82
|
+
key: The new key value
|
|
83
|
+
key_exists: Whether the key attribute already exists
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Modified content with the key set
|
|
87
|
+
"""
|
|
88
|
+
# Pattern to find the backend "s3" block
|
|
89
|
+
# This matches: backend "s3" { ... }
|
|
90
|
+
backend_pattern = r'(backend\s+"s3"\s*\{)'
|
|
91
|
+
|
|
92
|
+
if key_exists:
|
|
93
|
+
# Update existing key
|
|
94
|
+
# Match: key = "anything" or key = "anything" with various whitespace/quotes
|
|
95
|
+
key_pattern = r'(\s*key\s*=\s*)"[^"]*"'
|
|
96
|
+
replacement = r'\1"' + key + '"'
|
|
97
|
+
|
|
98
|
+
modified = re.sub(key_pattern, replacement, content)
|
|
99
|
+
|
|
100
|
+
return modified
|
|
101
|
+
else:
|
|
102
|
+
# Add new key attribute
|
|
103
|
+
# Find the backend "s3" block and add the key after the opening brace
|
|
104
|
+
def add_key(match):
|
|
105
|
+
# Get the matched backend opening
|
|
106
|
+
backend_opening = match.group(1)
|
|
107
|
+
|
|
108
|
+
# Find the indentation by looking at the next line
|
|
109
|
+
remaining = content[match.end() :]
|
|
110
|
+
next_line_match = re.search(r"\n(\s*)", remaining)
|
|
111
|
+
if next_line_match:
|
|
112
|
+
indent = next_line_match.group(1)
|
|
113
|
+
else:
|
|
114
|
+
indent = " " # Default to 4 spaces
|
|
115
|
+
|
|
116
|
+
# Add the key attribute with proper indentation
|
|
117
|
+
return f'{backend_opening}\n{indent}key = "{key}"'
|
|
118
|
+
|
|
119
|
+
modified = re.sub(backend_pattern, add_key, content, count=1)
|
|
120
|
+
|
|
121
|
+
return modified
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_backend_key(config_file: Union[str, Path]) -> Optional[str]:
|
|
125
|
+
"""
|
|
126
|
+
Get the current backend key from a Terraform config.tf file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
config_file: Path to the config.tf file
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The backend key if it exists, None otherwise
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> get_backend_key("/path/to/config.tf")
|
|
136
|
+
'apps-devstg/layer/terraform.tfstate'
|
|
137
|
+
"""
|
|
138
|
+
config_file = Path(config_file)
|
|
139
|
+
|
|
140
|
+
if not config_file.exists():
|
|
141
|
+
raise ExitError(1, f"Config file not found: {config_file}")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
config_content = config_file.read_text()
|
|
145
|
+
config_tf = hcl2.loads(config_content)
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
"terraform" in config_tf
|
|
149
|
+
and config_tf["terraform"]
|
|
150
|
+
and isinstance(config_tf["terraform"], list)
|
|
151
|
+
and "backend" in config_tf["terraform"][0]
|
|
152
|
+
and config_tf["terraform"][0]["backend"]
|
|
153
|
+
and isinstance(config_tf["terraform"][0]["backend"], list)
|
|
154
|
+
and "s3" in config_tf["terraform"][0]["backend"][0]
|
|
155
|
+
):
|
|
156
|
+
return config_tf["terraform"][0]["backend"][0]["s3"].get("key")
|
|
157
|
+
else:
|
|
158
|
+
raise ExitError(
|
|
159
|
+
1,
|
|
160
|
+
f"Malformed [bold]config.tf[/bold] file. Missing backend block.\n"
|
|
161
|
+
f"In some cases you may want to skip this check by using the --skip-validation flag, "
|
|
162
|
+
f"e.g. the first time you initialize a tf-backend layer.",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
except lark.exceptions.UnexpectedInput as error:
|
|
166
|
+
raise ExitError(
|
|
167
|
+
1,
|
|
168
|
+
f"Possible invalid expression in [bold]config.tf[/bold] near line {error.line}, column {error.column}\n"
|
|
169
|
+
f"{error.get_context(config_content)}",
|
|
170
|
+
)
|
|
171
|
+
except Exception:
|
|
172
|
+
raise ExitError(1, f"Malformed [bold]config.tf[/bold] file. Unable to parse.")
|
|
@@ -27,7 +27,10 @@ class State:
|
|
|
27
27
|
def __init__(self):
|
|
28
28
|
self._verbosity = None
|
|
29
29
|
self.module = Module()
|
|
30
|
-
self.
|
|
30
|
+
self.config = None
|
|
31
|
+
self.paths = None
|
|
32
|
+
self.runner = None
|
|
33
|
+
self.environment = None
|
|
31
34
|
|
|
32
35
|
@property
|
|
33
36
|
def verbosity(self):
|
|
@@ -41,13 +44,37 @@ class State:
|
|
|
41
44
|
pass_state = click.make_pass_decorator(State, ensure=True)
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
def
|
|
45
|
-
"""Decorator to pass the current
|
|
47
|
+
def pass_runner(command):
|
|
48
|
+
"""Decorator to pass the current runner (Terraform/OpenTofu runner) to the command."""
|
|
46
49
|
|
|
47
50
|
@wraps(command)
|
|
48
51
|
def new_command(*args, **kwargs):
|
|
49
52
|
ctx = click.get_current_context()
|
|
50
53
|
|
|
51
|
-
return command(ctx.obj.
|
|
54
|
+
return command(ctx.obj.runner, *args, **kwargs)
|
|
55
|
+
|
|
56
|
+
return new_command
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def pass_paths(command):
|
|
60
|
+
"""Decorator to pass the current project paths to the command."""
|
|
61
|
+
|
|
62
|
+
@wraps(command)
|
|
63
|
+
def new_command(*args, **kwargs):
|
|
64
|
+
ctx = click.get_current_context()
|
|
65
|
+
|
|
66
|
+
return command(ctx.obj.paths, *args, **kwargs)
|
|
67
|
+
|
|
68
|
+
return new_command
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def pass_environment(command):
|
|
72
|
+
"""Decorator to pass the current environment to the command."""
|
|
73
|
+
|
|
74
|
+
@wraps(command)
|
|
75
|
+
def new_command(*args, **kwargs):
|
|
76
|
+
ctx = click.get_current_context()
|
|
77
|
+
|
|
78
|
+
return command(ctx.obj.environment, *args, **kwargs)
|
|
52
79
|
|
|
53
80
|
return new_command
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from subprocess import run
|
|
7
|
-
from
|
|
6
|
+
from subprocess import PIPE, run
|
|
7
|
+
from typing import List, Optional
|
|
8
8
|
|
|
9
9
|
import hcl2
|
|
10
10
|
import lark
|
|
11
|
-
from click.exceptions import
|
|
11
|
+
from click.exceptions import ClickException
|
|
12
12
|
from configupdater import ConfigUpdater
|
|
13
|
-
from docker import DockerClient
|
|
14
|
-
from docker.models.containers import Container
|
|
15
13
|
|
|
16
14
|
from leverage import logger
|
|
17
15
|
|
|
@@ -64,56 +62,17 @@ def git(command):
|
|
|
64
62
|
run(command, stdout=PIPE, stderr=PIPE, check=True)
|
|
65
63
|
|
|
66
64
|
|
|
67
|
-
class
|
|
68
|
-
"""
|
|
69
|
-
Set a custom entrypoint on the container while entering the context.
|
|
70
|
-
Once outside, return it to its original value.
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
def __init__(self, container, entrypoint):
|
|
74
|
-
self.container = container
|
|
75
|
-
self.old_entrypoint = container.entrypoint
|
|
76
|
-
self.new_entrypoint = entrypoint
|
|
77
|
-
|
|
78
|
-
def __enter__(self):
|
|
79
|
-
self.container.entrypoint = self.new_entrypoint
|
|
80
|
-
|
|
81
|
-
def __exit__(self, *args, **kwargs):
|
|
82
|
-
self.container.entrypoint = self.old_entrypoint
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class AwsCredsEntryPoint(CustomEntryPoint):
|
|
86
|
-
"""
|
|
87
|
-
Fetching AWS credentials by setting the SSO/MFA entrypoints.
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
def __init__(self, container, override_entrypoint=None):
|
|
91
|
-
auth_method = container.auth_method()
|
|
92
|
-
|
|
93
|
-
new_entrypoint = f"{auth_method}{container.entrypoint if override_entrypoint is None else override_entrypoint}"
|
|
94
|
-
super(AwsCredsEntryPoint, self).__init__(container, entrypoint=new_entrypoint)
|
|
95
|
-
|
|
96
|
-
def __exit__(self, *args, **kwargs):
|
|
97
|
-
super(AwsCredsEntryPoint, self).__exit__(*args, **kwargs)
|
|
98
|
-
if self.container.mfa_enabled:
|
|
99
|
-
self.container.environment.update(
|
|
100
|
-
{
|
|
101
|
-
"AWS_SHARED_CREDENTIALS_FILE": self.container.environment["AWS_SHARED_CREDENTIALS_FILE"].replace(
|
|
102
|
-
".aws", "tmp"
|
|
103
|
-
),
|
|
104
|
-
"AWS_CONFIG_FILE": self.container.environment["AWS_CONFIG_FILE"].replace(".aws", "tmp"),
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class ExitError(Exit):
|
|
65
|
+
class ExitError(ClickException):
|
|
110
66
|
"""
|
|
111
67
|
Raise an Exit exception but also print an error description.
|
|
112
68
|
"""
|
|
113
69
|
|
|
114
70
|
def __init__(self, exit_code: int, error_description: str):
|
|
115
|
-
|
|
116
|
-
super(ExitError, self).__init__(
|
|
71
|
+
self.exit_code = exit_code
|
|
72
|
+
super(ExitError, self).__init__(message=error_description)
|
|
73
|
+
|
|
74
|
+
def show(self):
|
|
75
|
+
logger.error(self.message)
|
|
117
76
|
|
|
118
77
|
|
|
119
78
|
def parse_tf_file(file: Path):
|
|
@@ -134,31 +93,12 @@ def parse_tf_file(file: Path):
|
|
|
134
93
|
return parsed
|
|
135
94
|
|
|
136
95
|
|
|
137
|
-
|
|
138
|
-
"""
|
|
139
|
-
Handle the start/stop cycle of a container.
|
|
140
|
-
Useful when you need to keep your container alive to share context between multiple commands.
|
|
141
|
-
"""
|
|
142
|
-
|
|
143
|
-
def __init__(self, docker_client: DockerClient, container_data):
|
|
144
|
-
self.docker_client = docker_client
|
|
145
|
-
self.container_data = container_data
|
|
146
|
-
|
|
147
|
-
def __enter__(self) -> Container:
|
|
148
|
-
self.docker_client.api.start(self.container_data)
|
|
149
|
-
return self.docker_client.containers.get(self.container_data["Id"])
|
|
150
|
-
|
|
151
|
-
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
152
|
-
self.docker_client.api.stop(self.container_data)
|
|
153
|
-
self.docker_client.api.remove_container(self.container_data)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def key_finder(d: dict, target: str, avoid: str = None):
|
|
96
|
+
def key_finder(d: dict, target: str, avoid: Optional[str] = None) -> List[str]:
|
|
157
97
|
"""
|
|
158
98
|
Iterate over a dict of dicts and/or lists of dicts, looking for a key with value "target".
|
|
159
99
|
Collect and return all the values that matches "target" as key.
|
|
160
100
|
"""
|
|
161
|
-
values = []
|
|
101
|
+
values: List[str] = []
|
|
162
102
|
|
|
163
103
|
for key, value in d.items():
|
|
164
104
|
if isinstance(value, dict):
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Binbash Leverage Command-line tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from leverage import __version__, conf
|
|
8
|
+
from leverage._internals import pass_state
|
|
9
|
+
from leverage.path import NotARepositoryError, PathsHandler
|
|
10
|
+
from leverage.modules import aws, credentials, run, project, tofu, terraform, tfautomv, kubectl
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group(invoke_without_command=True)
|
|
14
|
+
@click.option("-v", "--verbose", is_flag=True, help="Increase output verbosity.")
|
|
15
|
+
@click.version_option(version=__version__)
|
|
16
|
+
@pass_state
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def leverage(context, state, verbose):
|
|
19
|
+
"""Leverage Reference Architecture projects command-line tool."""
|
|
20
|
+
# --verbose | -v
|
|
21
|
+
state.verbosity = verbose
|
|
22
|
+
if context.invoked_subcommand is None:
|
|
23
|
+
# leverage called with no subcommand
|
|
24
|
+
click.echo(context.get_help())
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
state.config = conf.load()
|
|
28
|
+
except NotARepositoryError:
|
|
29
|
+
return
|
|
30
|
+
state.paths = PathsHandler(state.config)
|
|
31
|
+
state.environment = {
|
|
32
|
+
"AWS_SHARED_CREDENTIALS_FILE": str(state.paths.aws_credentials_file),
|
|
33
|
+
"AWS_CONFIG_FILE": str(state.paths.aws_config_file),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Add modules to leverage
|
|
38
|
+
leverage.add_command(run)
|
|
39
|
+
leverage.add_command(project)
|
|
40
|
+
leverage.add_command(tofu)
|
|
41
|
+
leverage.add_command(tofu, name="tf")
|
|
42
|
+
leverage.add_command(terraform)
|
|
43
|
+
leverage.add_command(credentials)
|
|
44
|
+
leverage.add_command(aws)
|
|
45
|
+
leverage.add_command(tfautomv)
|
|
46
|
+
leverage.add_command(kubectl)
|
|
47
|
+
leverage.add_command(kubectl, name="kc")
|