vgs-cli 0.0.1.dev0__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.
Files changed (56) hide show
  1. vgs_cli-0.0.1.dev0.data/data/vgscli/calm.yaml +16 -0
  2. vgs_cli-0.0.1.dev0.data/data/vgscli/checkout.yaml +21 -0
  3. vgs_cli-0.0.1.dev0.data/data/vgscli/http-route-template.yaml +61 -0
  4. vgs_cli-0.0.1.dev0.data/data/vgscli/mft-route-template.yaml +10 -0
  5. vgs_cli-0.0.1.dev0.data/data/vgscli/payments-admin.yaml +25 -0
  6. vgs_cli-0.0.1.dev0.data/data/vgscli/service-account-schema.yaml +54 -0
  7. vgs_cli-0.0.1.dev0.data/data/vgscli/sub-account-checkout.yaml +23 -0
  8. vgs_cli-0.0.1.dev0.data/data/vgscli/vault-resources.yaml +710 -0
  9. vgs_cli-0.0.1.dev0.data/data/vgscli/vault-schema.yaml +36 -0
  10. vgs_cli-0.0.1.dev0.data/data/vgscli/vault-template.yaml +12 -0
  11. vgs_cli-0.0.1.dev0.data/data/vgscli/vgs-cli.yaml +17 -0
  12. vgs_cli-0.0.1.dev0.dist-info/METADATA +139 -0
  13. vgs_cli-0.0.1.dev0.dist-info/RECORD +56 -0
  14. vgs_cli-0.0.1.dev0.dist-info/WHEEL +5 -0
  15. vgs_cli-0.0.1.dev0.dist-info/entry_points.txt +2 -0
  16. vgs_cli-0.0.1.dev0.dist-info/licenses/LICENSE +22 -0
  17. vgs_cli-0.0.1.dev0.dist-info/top_level.txt +1 -0
  18. vgscli/__init__.py +0 -0
  19. vgscli/_version.py +32 -0
  20. vgscli/access_logs.py +65 -0
  21. vgscli/audits_api.py +102 -0
  22. vgscli/auth.py +68 -0
  23. vgscli/auth_server.py +131 -0
  24. vgscli/auth_utils.py +24 -0
  25. vgscli/callback_server.py +41 -0
  26. vgscli/cert_manager_api.py +34 -0
  27. vgscli/cli/__init__.py +23 -0
  28. vgscli/cli/commands/__init__.py +3 -0
  29. vgscli/cli/commands/apply.py +307 -0
  30. vgscli/cli/commands/generate.py +134 -0
  31. vgscli/cli/commands/get.py +200 -0
  32. vgscli/cli/types/__init__.py +2 -0
  33. vgscli/cli/types/resource_id.py +39 -0
  34. vgscli/cli/types/variable.py +21 -0
  35. vgscli/cli_utils.py +132 -0
  36. vgscli/click_extensions.py +88 -0
  37. vgscli/config_file.py +58 -0
  38. vgscli/errors.py +263 -0
  39. vgscli/file_token_util.py +30 -0
  40. vgscli/id_generator.py +46 -0
  41. vgscli/keyring_token_util.py +128 -0
  42. vgscli/resource-templates/http-route-template.yaml +61 -0
  43. vgscli/resource-templates/mft-route-template.yaml +10 -0
  44. vgscli/resource-templates/service-account/calm.yaml +16 -0
  45. vgscli/resource-templates/service-account/checkout.yaml +21 -0
  46. vgscli/resource-templates/service-account/payments-admin.yaml +25 -0
  47. vgscli/resource-templates/service-account/sub-account-checkout.yaml +23 -0
  48. vgscli/resource-templates/service-account/vgs-cli.yaml +17 -0
  49. vgscli/resource-templates/vault-template.yaml +12 -0
  50. vgscli/testing.py +48 -0
  51. vgscli/text.py +9 -0
  52. vgscli/token_handler.py +11 -0
  53. vgscli/validation-schemas/service-account-schema.yaml +54 -0
  54. vgscli/validation-schemas/vault-resources.yaml +710 -0
  55. vgscli/validation-schemas/vault-schema.yaml +36 -0
  56. vgscli/vgs.py +249 -0
vgscli/auth_server.py ADDED
@@ -0,0 +1,131 @@
1
+ import os
2
+ import threading
3
+ import time
4
+ import webbrowser
5
+ from urllib.parse import urlencode
6
+
7
+ import click
8
+ from vgs.sdk import auth_api
9
+ from vgs.sdk.utils import is_port_accessible
10
+
11
+ from vgscli.auth_utils import code_challenge, generate_code_verifier
12
+ from vgscli.callback_server import RequestServer
13
+ from vgscli.keyring_token_util import KeyringTokenUtil
14
+ from vgscli.token_handler import CodeHandler
15
+
16
+
17
+ class AuthServer:
18
+ env_url = {
19
+ "dev": "https://auth.verygoodsecurity.io",
20
+ "prod": "https://auth.verygoodsecurity.com",
21
+ }
22
+ token_util = KeyringTokenUtil()
23
+ token_handler = CodeHandler()
24
+
25
+ # Api
26
+ CLIENT_ID = "vgs-cli-public"
27
+ SCOPES = "idp openid"
28
+ AUTH_URL = "{base_url}/auth/realms/vgs/protocol/openid-connect/auth"
29
+ CALLBACK_PATH = "/callback"
30
+
31
+ # AuthZ
32
+ code_verifier = generate_code_verifier()
33
+ code_method = "S256"
34
+ oauth_access_token = None
35
+
36
+ # Server constants.
37
+ # Ports have been chosen based on Unassigned port list: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=111
38
+ ports = [7745, 8390, 9056]
39
+ host = os.getenv("AUTH_SERVER_BIND_IP", "127.0.0.1")
40
+ accessible_port = None
41
+ app = None
42
+
43
+ def __init__(self, environment):
44
+ self.accessible_port = next(
45
+ port for port in self.ports if is_port_accessible(self.host, port)
46
+ )
47
+ self.app = RequestServer(self.host, self.accessible_port)
48
+ self.environment = environment
49
+ self.auth_api = auth_api.create_api(environment)
50
+
51
+ def login(self, environment, **kwargs):
52
+ thread = self.ServerThread(self.app)
53
+ thread.daemon = True
54
+ thread.start()
55
+
56
+ query = {
57
+ "client_id": self.CLIENT_ID,
58
+ "code_challenge": code_challenge(self.code_verifier),
59
+ "code_challenge_method": self.code_method,
60
+ "redirect_uri": self.__get_host() + "/callback",
61
+ "response_type": "code",
62
+ "scope": self.SCOPES,
63
+ }
64
+
65
+ idp = kwargs.get("idp")
66
+ if idp:
67
+ query["kc_idp_hint"] = idp
68
+
69
+ url = (
70
+ self.AUTH_URL.format(base_url=self.env_url[environment])
71
+ + f"?{urlencode(query)}"
72
+ )
73
+
74
+ if kwargs.get("open_browser", True):
75
+ if not webbrowser.open(url, new=1, autoraise=True):
76
+ click.echo(
77
+ f"Could not open the default browser. Follow the link below to log in:\n{url}"
78
+ )
79
+ else:
80
+ click.echo(f"Follow the link below to log in:\n{url}")
81
+
82
+ while self.token_handler.get_code() is None:
83
+ time.sleep(1)
84
+ self.retrieve_access_token()
85
+
86
+ return self.token_util.get_access_token()
87
+
88
+ def logout(self):
89
+ auth_api.logout(
90
+ self.auth_api,
91
+ self.CLIENT_ID,
92
+ self.token_util.get_access_token(),
93
+ self.token_util.get_refresh_token(),
94
+ )
95
+
96
+ def refresh_authentication(self):
97
+ self.token_util.put_tokens(
98
+ auth_api.refresh_token(
99
+ self.auth_api, refresh_token=self.token_util.get_refresh_token()
100
+ ).body
101
+ )
102
+
103
+ def retrieve_access_token(self):
104
+ callback_url = self.__get_host() + self.CALLBACK_PATH
105
+ response = auth_api.get_token(
106
+ self.auth_api,
107
+ self.token_handler.get_code(),
108
+ self.code_verifier,
109
+ callback_url,
110
+ )
111
+ self.set_access_token(response.body)
112
+
113
+ def set_access_token(self, token):
114
+ self.token_util.put_tokens(token)
115
+
116
+ def __get_host(self):
117
+ return "http://" + self.host + ":" + str(self.accessible_port)
118
+
119
+ def client_credentials_login(self, client_id, secret):
120
+ response = auth_api.get_auto_token(
121
+ self.auth_api, client_id=client_id, client_secret=secret
122
+ )
123
+ self.set_access_token(response.body)
124
+
125
+ class ServerThread(threading.Thread):
126
+ def __init__(self, app):
127
+ self.app = app
128
+ threading.Thread.__init__(self)
129
+
130
+ def run(self):
131
+ self.app.run()
vgscli/auth_utils.py ADDED
@@ -0,0 +1,24 @@
1
+ import base64
2
+ import hashlib
3
+ import random
4
+ import string
5
+
6
+
7
+ def generate_code_verifier(stringLength=64):
8
+ password_characters = string.ascii_letters + string.digits
9
+ return "".join(random.choice(password_characters) for i in range(stringLength))
10
+
11
+
12
+ def code_challenge(code_verifier):
13
+ sha_signature = __sha256(code_verifier.encode())
14
+ return __base64_url_encode(sha_signature).decode("UTF-8").split("=")[0]
15
+
16
+
17
+ def __sha256(buffer):
18
+ m = hashlib.sha256()
19
+ m.update(buffer)
20
+ return m.digest()
21
+
22
+
23
+ def __base64_url_encode(random_bytes):
24
+ return base64.urlsafe_b64encode(random_bytes)
@@ -0,0 +1,41 @@
1
+ from http.server import BaseHTTPRequestHandler, HTTPServer
2
+ from urllib import parse
3
+
4
+ from vgscli.keyring_token_util import KeyringTokenUtil
5
+ from vgscli.token_handler import CodeHandler
6
+
7
+ token_util = KeyringTokenUtil()
8
+ token_handler = CodeHandler()
9
+
10
+
11
+ class RequestHandler(BaseHTTPRequestHandler):
12
+ def do_GET(self):
13
+ if self.path.startswith("/callback"):
14
+ params = parse.parse_qs(self.path.split("?")[1])
15
+ token = params["code"][0]
16
+ token_handler.put_code(token)
17
+ self.send_response(200)
18
+ self.end_headers()
19
+ self.wfile.write("Go back to terminal".encode("utf-8"))
20
+ return
21
+
22
+ def log_message(self, format, *args):
23
+ return
24
+
25
+
26
+ class RequestServer:
27
+
28
+ server = None
29
+ host = "localhost"
30
+ port = 8080
31
+
32
+ def __init__(self, host, port):
33
+ self.host = host
34
+ self.port = port
35
+
36
+ def run(self):
37
+ self.server = HTTPServer((self.host, self.port), RequestHandler)
38
+ self.server.serve_forever()
39
+
40
+ def close(self):
41
+ self.server.server_close()
@@ -0,0 +1,34 @@
1
+ from simple_rest_client.api import API
2
+ from simple_rest_client.resource import Resource
3
+
4
+ from vgscli._version import __version__
5
+
6
+ CERT_MANAGER_URLS = {
7
+ "dev": "https://cert-manager-api.verygoodsecurity.io",
8
+ "prod": "https://cert-manager.apps.verygoodvault.com",
9
+ }
10
+
11
+
12
+ class CertificatesResource(Resource):
13
+ actions = {"list": {"method": "GET", "url": "api/certificates"}}
14
+
15
+
16
+ def create_cert_manager_api(vault_id, environment, token):
17
+ env = (environment or "prod").lower()
18
+ base_url = CERT_MANAGER_URLS.get(env, CERT_MANAGER_URLS["prod"])
19
+ api = API(
20
+ api_root_url=base_url,
21
+ params={},
22
+ headers={
23
+ "VGS-Tenant": vault_id,
24
+ "Content-Type": "application/json",
25
+ "Accept": "application/json",
26
+ "User-Agent": f"VGS CLI {__version__}",
27
+ "Authorization": f"Bearer {token}",
28
+ },
29
+ timeout=30,
30
+ append_slash=False,
31
+ json_encode_body=True,
32
+ )
33
+ api.add_resource(resource_name="certificates", resource_class=CertificatesResource)
34
+ return api
vgscli/cli/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ import click
2
+ from vgs.sdk.account_mgmt import AccountMgmtAPI
3
+ from vgs.sdk.vault_mgmt import VaultMgmtAPI
4
+
5
+ from vgscli.auth import handshake, token_util
6
+
7
+
8
+ def create_account_mgmt_api(ctx: click.Context) -> AccountMgmtAPI:
9
+ environment = ctx.obj.env
10
+
11
+ handshake(ctx, environment)
12
+ access_token = token_util.get_access_token()
13
+
14
+ return AccountMgmtAPI(access_token, environment)
15
+
16
+
17
+ def create_vault_mgmt_api(ctx: click.Context, root_url: str) -> VaultMgmtAPI:
18
+ environment = ctx.obj.env
19
+
20
+ handshake(ctx, environment)
21
+ access_token = token_util.get_access_token()
22
+
23
+ return VaultMgmtAPI(access_token, root_url)
@@ -0,0 +1,3 @@
1
+ from .apply import apply
2
+ from .generate import generate
3
+ from .get import get
@@ -0,0 +1,307 @@
1
+ import logging
2
+ import time
3
+ from typing import Optional
4
+
5
+ import click
6
+ from click_plugins import with_plugins
7
+ from simple_rest_client import exceptions
8
+ from simple_rest_client.exceptions import ClientError
9
+ from vgs.sdk.errors import RouteNotValidError
10
+ from vgs.sdk.routes import dump_yaml, normalize, sync_all_routes
11
+ from vgs.sdk.vaults_api import create_api as create_vaults_api
12
+
13
+ from vgscli.auth import handshake, token_util
14
+ from vgscli.cli import create_account_mgmt_api, create_vault_mgmt_api
15
+ from vgscli.cli.types import ResourceId, ResourceIdParamType
16
+ from vgscli.cli_utils import (
17
+ dump_camelized_yaml,
18
+ iter_entry_points,
19
+ validate_multi_yaml,
20
+ validate_yaml,
21
+ )
22
+ from vgscli.errors import ServiceClientCreationError, VgsCliError, handle_errors
23
+
24
+ logger = logging.getLogger()
25
+
26
+
27
+ @with_plugins(iter_entry_points("vgs.apply.plugins"))
28
+ @click.group("apply")
29
+ def apply() -> None:
30
+ """
31
+ Create or update a VGS resource.
32
+ """
33
+ pass
34
+
35
+
36
+ @apply.command("service-account")
37
+ @click.option(
38
+ "-O",
39
+ "--organization",
40
+ "org_id",
41
+ type=ResourceIdParamType(prefix="AC"),
42
+ help="ID of the organization to associate the vault with.",
43
+ )
44
+ @click.option(
45
+ "--file",
46
+ "-f",
47
+ type=click.File(),
48
+ help="Configuration to apply.",
49
+ required=True,
50
+ )
51
+ @click.pass_context
52
+ @handle_errors()
53
+ def apply_service_account(ctx: click.Context, org_id: ResourceId, file) -> None:
54
+ """
55
+ Create a Service Account client.
56
+ """
57
+ data = validate_yaml(file, "validation-schemas/service-account-schema.yaml")["data"]
58
+
59
+ account_mgmt = create_account_mgmt_api(ctx)
60
+ try:
61
+ # noinspection PyUnresolvedReferences
62
+ response = account_mgmt.service_accounts.create(
63
+ org_id.base58,
64
+ body={
65
+ "data": {
66
+ "attributes": {
67
+ "name": data["name"],
68
+ "annotations": data.pop("annotations", {}),
69
+ "vaults": data.get("vaults", []),
70
+ "scopes": data["scopes"],
71
+ "access_token_lifespan": data.get("accessTokenLifespan", None),
72
+ }
73
+ }
74
+ },
75
+ )
76
+ except ClientError as cause:
77
+ raise ServiceClientCreationError(cause)
78
+
79
+ attributes = response.body["data"]["attributes"]
80
+
81
+ data["clientId"] = attributes["client_id"]
82
+ data["clientSecret"] = attributes["client_secret"]
83
+
84
+ # NOTE: Annotations are excluded from the output as they are undesirably camelized
85
+ # (e.g., "vgs.io/vault-id" becomes "vgs.io/vaultId")
86
+
87
+ click.echo(
88
+ dump_camelized_yaml(
89
+ {
90
+ "apiVersion": "1.0.0",
91
+ "kind": "ServiceAccount",
92
+ "data": data,
93
+ }
94
+ )
95
+ )
96
+
97
+
98
+ @apply.command("vault")
99
+ @click.option(
100
+ "-O",
101
+ "--organization",
102
+ "org_id",
103
+ type=ResourceIdParamType(prefix="AC"),
104
+ help="ID of the organization to associate the vault with.",
105
+ )
106
+ @click.option(
107
+ "--file",
108
+ "-f",
109
+ type=click.File(),
110
+ help="Configuration to apply.",
111
+ required=True,
112
+ )
113
+ @click.pass_context
114
+ @handle_errors()
115
+ def apply_vault(ctx: click.Context, org_id: Optional[ResourceId], file) -> None:
116
+ """
117
+ Create a new VGS vault.
118
+ """
119
+ data = validate_yaml(file, "validation-schemas/vault-schema.yaml")["data"]
120
+
121
+ # kubectl behavior
122
+ if "organizationId" in data:
123
+ if org_id and org_id.base58 != data["organizationId"]:
124
+ raise VgsCliError(
125
+ f"Ambiguous organization ID. "
126
+ f"Run the command with '--organization={data['organizationId']}' to resolve."
127
+ )
128
+ else:
129
+ if not org_id:
130
+ raise VgsCliError(
131
+ "Missing organization ID. Pass the '--organization' option to resolve."
132
+ )
133
+
134
+ data["organizationId"] = org_id.base58
135
+
136
+ account_mgmt = create_account_mgmt_api(ctx)
137
+
138
+ # noinspection PyUnresolvedReferences
139
+ response = account_mgmt.vaults.create_or_update(
140
+ body={
141
+ "data": {
142
+ "attributes": {
143
+ "name": data["name"],
144
+ "environment": data["environment"],
145
+ },
146
+ "type": "vaults",
147
+ "relationships": {
148
+ "organization": {
149
+ "data": {"type": "organizations", "id": data["organizationId"]}
150
+ }
151
+ },
152
+ }
153
+ }
154
+ )
155
+
156
+ attributes = response.body["data"]["attributes"]
157
+
158
+ data["id"] = attributes["identifier"]
159
+ data["credentials"] = {
160
+ "username": attributes["credentials"]["key"],
161
+ "password": attributes["credentials"]["secret"],
162
+ }
163
+
164
+ vault_mgmt = create_vault_mgmt_api(
165
+ ctx, response.body["data"]["links"]["vault_management_api"]
166
+ )
167
+
168
+ while True:
169
+ # noinspection PyUnresolvedReferences
170
+ response = vault_mgmt.vaults.retrieve(
171
+ data["id"], headers={"VGS-Tenant": data["id"]}
172
+ )
173
+ if response.body["data"]["attributes"]["state"] == "PROVISIONED":
174
+ break
175
+ time.sleep(2)
176
+
177
+ click.echo(
178
+ dump_camelized_yaml(
179
+ {
180
+ "apiVersion": "1.0.0",
181
+ "kind": "Vault",
182
+ "data": data,
183
+ }
184
+ )
185
+ )
186
+
187
+
188
+ def sync_http_route(payload, ctx, vault_id):
189
+ handshake(ctx, ctx.obj.env)
190
+
191
+ vault_management_api = create_vaults_api(
192
+ ctx, vault_id, ctx.obj.env, token_util.get_access_token()
193
+ )
194
+ route_id = payload["spec"]["id"]
195
+ try:
196
+ # api expects it to be wrapped in data attribute
197
+ response = vault_management_api.routes.update(
198
+ route_id, body={"data": payload["spec"]}
199
+ )
200
+ except exceptions.ClientError as e:
201
+ error_msg = "\n".join([error["detail"] for error in e.response.body["errors"]])
202
+ raise RouteNotValidError(error_msg)
203
+ if ctx.obj.debug:
204
+ click.echo(f"Received raw response {response}")
205
+ logger.debug(response)
206
+ # TODO: this flilth can be removed after the normalize_one function is exposed.
207
+ payload = normalize([response.body["data"]])[0]
208
+ if ctx.obj.debug:
209
+ click.echo(f"Normalized body {payload}")
210
+ payload = wrap_in_http_envelope(payload)
211
+ if ctx.obj.debug:
212
+ click.echo(f"Wrapped body {payload}")
213
+ click.echo(f"Route {route_id} processed")
214
+ return payload
215
+
216
+
217
+ def wrap_in_http_envelope(payload):
218
+ # TODO: this either needs to come from the API or we should add support for versioning in the client (yuk)
219
+ envelope = {
220
+ "apiVersion": "vault.vgs.io/v1",
221
+ "kind": "HttpRoute",
222
+ "metadata": {"name": payload["id"]},
223
+ "spec": payload,
224
+ }
225
+ return envelope
226
+
227
+
228
+ def sync_mft_route(payload, ctx, vault_id):
229
+ print("Sync MFT Route")
230
+ print(payload, ctx, vault_id)
231
+
232
+
233
+ def no_op(*_):
234
+ print("No Op")
235
+
236
+
237
+ HANDLERS = {
238
+ ("vault.vgs.io/v1", "HttpRoute"): sync_http_route,
239
+ ("mft.vgs.io/v1beta", "MftRoute"): sync_mft_route,
240
+ }
241
+
242
+
243
+ @apply.command("vault-resources")
244
+ @click.option("--vault", "-V", help="Vault ID", required=True)
245
+ @click.option(
246
+ "--file",
247
+ "-f",
248
+ type=click.File(),
249
+ help="Configuration to apply.",
250
+ required=True,
251
+ )
252
+ @click.option("--dry-run", default=False)
253
+ @click.pass_context
254
+ @handle_errors()
255
+ def all_vault_resources(
256
+ ctx: click.Context, vault: Optional[ResourceId], file, dry_run
257
+ ) -> None:
258
+ """
259
+ Apply all vault resources (routes, preferences, certificates) to a single vault.
260
+ """
261
+ parsed_resources = validate_multi_yaml(
262
+ file, "validation-schemas/vault-resources.yaml"
263
+ )
264
+ for resource in parsed_resources:
265
+ if dry_run:
266
+ print(
267
+ f"Pretending to send {resource['apiVersion']} {resource['kind']} {resource['metadata']['name']} to server"
268
+ )
269
+ else:
270
+ # for each resource find handler.
271
+ logger.info(
272
+ f"Processing {resource['apiVersion']} {resource['kind']} {resource['metadata']['name']} to server"
273
+ )
274
+ response_payload = HANDLERS.get(
275
+ (resource["apiVersion"], resource["kind"]), no_op
276
+ )(resource, ctx, vault)
277
+ if response_payload:
278
+ print(dump_yaml(response_payload))
279
+
280
+
281
+ @apply.command("routes")
282
+ @click.option("--vault", "-V", help="Vault ID", required=True)
283
+ @click.option(
284
+ "--filename",
285
+ "-f",
286
+ help="Filename for the input data",
287
+ type=click.File("r"),
288
+ required=True,
289
+ )
290
+ @click.pass_context
291
+ @handle_errors()
292
+ def apply_routes(ctx, vault, filename):
293
+ """
294
+ Create or update VGS routes.
295
+ """
296
+ handshake(ctx, ctx.obj.env)
297
+
298
+ route_data = filename.read()
299
+ vault_management_api = create_vaults_api(
300
+ ctx, vault, ctx.obj.env, token_util.get_access_token()
301
+ )
302
+ sync_all_routes(
303
+ vault_management_api,
304
+ route_data,
305
+ lambda route_id: click.echo(f"Route {route_id} processed"),
306
+ )
307
+ click.echo(f"Routes updated successfully for vault {vault}")
@@ -0,0 +1,134 @@
1
+ import click
2
+ from click_plugins import with_plugins
3
+ from jinja2 import Environment, PackageLoader, StrictUndefined, UndefinedError
4
+
5
+ from vgscli.cli import create_account_mgmt_api, create_vault_mgmt_api
6
+ from vgscli.cli.types import Variable, VariableParamType
7
+ from vgscli.cli_utils import dump_camelized_yaml, iter_entry_points, read_file
8
+ from vgscli.errors import handle_errors
9
+
10
+
11
+ @with_plugins(iter_entry_points("vgs.generate.plugins"))
12
+ @click.group("generate")
13
+ def generate() -> None:
14
+ """
15
+ Output a VGS resource template. Edited templates can be applied with a
16
+ corresponding command.
17
+ """
18
+ pass
19
+
20
+
21
+ @generate.command("vault")
22
+ @handle_errors()
23
+ def generate_vault() -> None:
24
+ """
25
+ Output a vault template.
26
+ """
27
+ click.echo(read_file("resource-templates/vault-template.yaml"), nl=False)
28
+
29
+
30
+ @generate.command("access-credentials")
31
+ @click.option("--vault", "-V", help="Vault ID", required=True)
32
+ @click.pass_context
33
+ @handle_errors()
34
+ def generate_access_credentials(ctx, vault):
35
+ """
36
+ Generate a VGS access-credential
37
+ """
38
+ account_mgmt = create_account_mgmt_api(ctx)
39
+
40
+ response = account_mgmt.vaults.get_by_id(vault)
41
+
42
+ vault_mgmt = create_vault_mgmt_api(
43
+ ctx, response.body["data"][0]["links"]["vault_management_api"]
44
+ )
45
+
46
+ response = vault_mgmt.credentials.create(headers={"VGS-Tenant": vault})
47
+
48
+ click.echo(
49
+ dump_camelized_yaml(
50
+ {
51
+ "apiVersion": "1.0.0",
52
+ "kind": "AccessCredentials",
53
+ "data": response.body["data"],
54
+ }
55
+ )
56
+ )
57
+
58
+
59
+ @generate.command("http-route")
60
+ @handle_errors()
61
+ def generate_route():
62
+ """
63
+ Generate a VGS HTTP Route
64
+ """
65
+ click.echo(read_file("resource-templates/http-route-template.yaml"), nl=False)
66
+
67
+
68
+ @generate.command("mft-route")
69
+ @handle_errors()
70
+ def generate_route():
71
+ """
72
+ Generate a VGS MFT Route
73
+ """
74
+ click.echo(read_file("resource-templates/mft-route-template.yaml"), nl=False)
75
+
76
+
77
+ @generate.command("service-account")
78
+ @click.option(
79
+ "--template",
80
+ "-t",
81
+ type=click.Choice(
82
+ ["vgs-cli", "calm", "checkout", "sub-account-checkout", "payments-admin"]
83
+ ),
84
+ help="Predefined service account template configuration",
85
+ required=True,
86
+ )
87
+ @click.option(
88
+ "--var",
89
+ "variables",
90
+ type=VariableParamType(),
91
+ multiple=True,
92
+ help="Template variables.",
93
+ )
94
+ @click.option(
95
+ "--vault",
96
+ "vaults",
97
+ type=click.STRING,
98
+ multiple=True,
99
+ help="Service Account accessible vaults",
100
+ )
101
+ @click.pass_context
102
+ @handle_errors()
103
+ def generate_service_account(ctx, template, variables, vaults):
104
+ """
105
+ Output a Service Account template.
106
+ """
107
+ environment = Environment(
108
+ loader=PackageLoader(
109
+ package_name="vgscli", package_path="resource-templates/service-account"
110
+ ),
111
+ undefined=StrictUndefined,
112
+ )
113
+
114
+ def cli_warn(msg):
115
+ click.echo(click.style("Warning!", fg="yellow") + " " + msg, err=True)
116
+
117
+ def cli_fail(msg):
118
+ click.echo(click.style("Error!", fg="red") + " " + msg, err=True)
119
+ ctx.exit(1)
120
+
121
+ environment.globals["cli_warn"] = cli_warn
122
+ environment.globals["cli_fail"] = cli_fail
123
+
124
+ variables = variables + (Variable("vaults", vaults),)
125
+ try:
126
+ template = environment.get_template(f"{template}.yaml")
127
+ click.echo(template.render(**{var.name: var.value for var in variables}))
128
+ except UndefinedError as error:
129
+ click.echo(
130
+ click.style("Error!", fg="red") + f" Could not render service "
131
+ f"account template: {error}. "
132
+ f"Please use '--var variable=value' to pass required variable."
133
+ )
134
+ ctx.exit(1)