vgs-cli 1.35.0.dev1__py3-none-any.whl → 1.35.0.dev2__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 (32) hide show
  1. vgs_cli-1.35.0.dev2.data/data/vgscli/vgs-cli.yaml +17 -0
  2. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/METADATA +12 -4
  3. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/RECORD +32 -30
  4. vgscli/_version.py +1 -1
  5. vgscli/cli/commands/apply.py +60 -75
  6. vgscli/cli/commands/generate.py +1 -0
  7. vgscli/cli_progress.py +3 -17
  8. vgscli/click_extensions.py +1 -1
  9. vgscli/errors.py +18 -3
  10. vgscli/resource-templates/service-account/vgs-cli.yaml +17 -0
  11. vgscli/tenant_creation.py +24 -11
  12. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/collect-form-api-schema.yaml +0 -0
  13. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/collect-form-schema.yaml +0 -0
  14. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/credentials-admin.yaml +0 -0
  15. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/form-template.yaml +0 -0
  16. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/http-route-template.yaml +0 -0
  17. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/notification-integration-schema.yaml +0 -0
  18. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/notification-template.yaml +0 -0
  19. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/public-credential-collect.yaml +0 -0
  20. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/read-credentials-no-pci.yaml +0 -0
  21. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/read-credentials-with-pci.yaml +0 -0
  22. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/service-account-schema.yaml +0 -0
  23. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/sftp-route-template.yaml +0 -0
  24. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/tenant-schema.yaml +0 -0
  25. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/tenant-template.yaml +0 -0
  26. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/vault-resources.yaml +0 -0
  27. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/vault-schema.yaml +0 -0
  28. {vgs_cli-1.35.0.dev1.data → vgs_cli-1.35.0.dev2.data}/data/vgscli/vault-template.yaml +0 -0
  29. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/WHEEL +0 -0
  30. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/entry_points.txt +0 -0
  31. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/licenses/LICENSE +0 -0
  32. {vgs_cli-1.35.0.dev1.dist-info → vgs_cli-1.35.0.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,17 @@
1
+ apiVersion: 1.0.0
2
+ kind: ServiceAccount
3
+ data:
4
+ name: vgs-cli
5
+ {%- if vaults | length == 0 %}
6
+ {{- cli_warn("Service Account won't have access to any tenants inside the organization. If it needs tenant access, use --tenant <TENANT_ID>.") or "" }}
7
+ {%- else %}
8
+ vaults:
9
+ {%- for vault_id in vaults %}
10
+ - {{ vault_id }}
11
+ {%- endfor %}
12
+ {%- endif %}
13
+ scopes:
14
+ - name: access-logs:read
15
+ - name: organizations:read
16
+ - name: routes:write
17
+ - name: vaults:write
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vgs-cli
3
- Version: 1.35.0.dev1
3
+ Version: 1.35.0.dev2
4
4
  Summary: VGS Client
5
5
  Home-page: https://github.com/verygoodsecurity/vgs-cli
6
6
  Author: Very Good Security
@@ -200,9 +200,16 @@ access. The CLI masks generated webhook secrets in its output.
200
200
 
201
201
  ### Service accounts
202
202
 
203
- Generate a service-account resource for exactly one tenant. Names must contain
204
- 1–20 characters. Save the generated YAML so you can review it before applying
205
- it.
203
+ Generate a service-account resource and save the YAML so you can review it
204
+ before applying it. The general `vgs-cli` template accepts zero or more tenants;
205
+ when no tenant is supplied, the generated account has no tenant access.
206
+
207
+ ```bash
208
+ vgs generate service-account --template vgs-cli -T <TENANT_ID> > service-account.yaml
209
+ ```
210
+
211
+ Payment-credential templates require exactly one tenant and a name containing
212
+ 1–20 characters:
206
213
 
207
214
  ```bash
208
215
  vgs generate service-account --template <TEMPLATE> -T <TENANT_ID> --var name=<NAME> > service-account.yaml
@@ -210,6 +217,7 @@ vgs generate service-account --template <TEMPLATE> -T <TENANT_ID> --var name=<NA
210
217
 
211
218
  | Template | Purpose |
212
219
  | --- | --- |
220
+ | `vgs-cli` | Automate common CLI organization, tenant, route, and access-log operations. |
213
221
  | `public-credential-collect` | Collect payment credentials without read access. |
214
222
  | `read-credentials-no-pci` | Read tokenized credentials without PCI data. |
215
223
  | `read-credentials-with-pci` | Read credentials including PCI data. |
@@ -1,23 +1,24 @@
1
- vgs_cli-1.35.0.dev1.data/data/vgscli/collect-form-api-schema.yaml,sha256=THuJbLUo7LOr0c1JUo3FPJEc9Jvde59K4JjdlkVF3hw,1246
2
- vgs_cli-1.35.0.dev1.data/data/vgscli/collect-form-schema.yaml,sha256=vHAN6_Gsg14CeSL41sHEQgboYF-CHF2RaNw72aJ3W8o,1337
3
- vgs_cli-1.35.0.dev1.data/data/vgscli/credentials-admin.yaml,sha256=acyLd54Cp7l2fpugfjVfYh07-MIE0BX-JVq4lHraE1s,869
4
- vgs_cli-1.35.0.dev1.data/data/vgscli/form-template.yaml,sha256=VblOlsDLfwZP6wR6nIIX1WSrUAt4leqfMdbXr3xYvWg,934
5
- vgs_cli-1.35.0.dev1.data/data/vgscli/http-route-template.yaml,sha256=vGHvBXA_6pwFtH1vVRWKiwxb4sahzuLvpqGAlavrStc,1085
6
- vgs_cli-1.35.0.dev1.data/data/vgscli/notification-integration-schema.yaml,sha256=1qHzS5ZxlhTdrDab6-qJhszwu7cpith2HleX3uAO_4E,1837
7
- vgs_cli-1.35.0.dev1.data/data/vgscli/notification-template.yaml,sha256=hr36msbrgiTjWligLHVDHMmO49QDzjTwBSLYoBw3WQE,1288
8
- vgs_cli-1.35.0.dev1.data/data/vgscli/public-credential-collect.yaml,sha256=CiorC-6TMx7M0TI8DHmoRSfVKDpOom2wIr_ilwmHQNA,696
9
- vgs_cli-1.35.0.dev1.data/data/vgscli/read-credentials-no-pci.yaml,sha256=_kWRne_ARh8v7A_jHG08gwgPG6NqZhrDRyq6L-ULs2M,769
10
- vgs_cli-1.35.0.dev1.data/data/vgscli/read-credentials-with-pci.yaml,sha256=jWXgFdcqZYisakAt9COM6cjf2-x_Q4eidDLaUh8CZOk,796
11
- vgs_cli-1.35.0.dev1.data/data/vgscli/service-account-schema.yaml,sha256=L1pjNXgnks15N1wNgf_1GmGcOvxn0nu49v7ersUjFno,1090
12
- vgs_cli-1.35.0.dev1.data/data/vgscli/sftp-route-template.yaml,sha256=zkT6lIVDgVyzLKgZ8QnyJJooy2egZk1cQthdjan_JrM,853
13
- vgs_cli-1.35.0.dev1.data/data/vgscli/tenant-schema.yaml,sha256=gBO7xVe2qXSymG0B_45hC11X5nm_jCCAH4Yjs-sbC8g,1457
14
- vgs_cli-1.35.0.dev1.data/data/vgscli/tenant-template.yaml,sha256=F0wIBkC2dFJUNeJet-FzPX3qrLMHcGIaJvhsDzzAOCc,765
15
- vgs_cli-1.35.0.dev1.data/data/vgscli/vault-resources.yaml,sha256=95CCxQher3MBQq96bvjZviNgo1rgv_NhCtI8X-oxIpA,26335
16
- vgs_cli-1.35.0.dev1.data/data/vgscli/vault-schema.yaml,sha256=a-wW7X8RyHr-669wtOZrlxlIfeQVtx8EUfA-3vpYytk,637
17
- vgs_cli-1.35.0.dev1.data/data/vgscli/vault-template.yaml,sha256=IlDisL3H5LXUDMkhUaouKMarRdu0-pJxpTz8ij_lp_M,383
18
- vgs_cli-1.35.0.dev1.dist-info/licenses/LICENSE,sha256=NwSyzgDYdzv6O2ns2C6UODGI8LN1VnVe_FPTnWS4eXY,1082
1
+ vgs_cli-1.35.0.dev2.data/data/vgscli/collect-form-api-schema.yaml,sha256=THuJbLUo7LOr0c1JUo3FPJEc9Jvde59K4JjdlkVF3hw,1246
2
+ vgs_cli-1.35.0.dev2.data/data/vgscli/collect-form-schema.yaml,sha256=vHAN6_Gsg14CeSL41sHEQgboYF-CHF2RaNw72aJ3W8o,1337
3
+ vgs_cli-1.35.0.dev2.data/data/vgscli/credentials-admin.yaml,sha256=acyLd54Cp7l2fpugfjVfYh07-MIE0BX-JVq4lHraE1s,869
4
+ vgs_cli-1.35.0.dev2.data/data/vgscli/form-template.yaml,sha256=VblOlsDLfwZP6wR6nIIX1WSrUAt4leqfMdbXr3xYvWg,934
5
+ vgs_cli-1.35.0.dev2.data/data/vgscli/http-route-template.yaml,sha256=vGHvBXA_6pwFtH1vVRWKiwxb4sahzuLvpqGAlavrStc,1085
6
+ vgs_cli-1.35.0.dev2.data/data/vgscli/notification-integration-schema.yaml,sha256=1qHzS5ZxlhTdrDab6-qJhszwu7cpith2HleX3uAO_4E,1837
7
+ vgs_cli-1.35.0.dev2.data/data/vgscli/notification-template.yaml,sha256=hr36msbrgiTjWligLHVDHMmO49QDzjTwBSLYoBw3WQE,1288
8
+ vgs_cli-1.35.0.dev2.data/data/vgscli/public-credential-collect.yaml,sha256=CiorC-6TMx7M0TI8DHmoRSfVKDpOom2wIr_ilwmHQNA,696
9
+ vgs_cli-1.35.0.dev2.data/data/vgscli/read-credentials-no-pci.yaml,sha256=_kWRne_ARh8v7A_jHG08gwgPG6NqZhrDRyq6L-ULs2M,769
10
+ vgs_cli-1.35.0.dev2.data/data/vgscli/read-credentials-with-pci.yaml,sha256=jWXgFdcqZYisakAt9COM6cjf2-x_Q4eidDLaUh8CZOk,796
11
+ vgs_cli-1.35.0.dev2.data/data/vgscli/service-account-schema.yaml,sha256=L1pjNXgnks15N1wNgf_1GmGcOvxn0nu49v7ersUjFno,1090
12
+ vgs_cli-1.35.0.dev2.data/data/vgscli/sftp-route-template.yaml,sha256=zkT6lIVDgVyzLKgZ8QnyJJooy2egZk1cQthdjan_JrM,853
13
+ vgs_cli-1.35.0.dev2.data/data/vgscli/tenant-schema.yaml,sha256=gBO7xVe2qXSymG0B_45hC11X5nm_jCCAH4Yjs-sbC8g,1457
14
+ vgs_cli-1.35.0.dev2.data/data/vgscli/tenant-template.yaml,sha256=F0wIBkC2dFJUNeJet-FzPX3qrLMHcGIaJvhsDzzAOCc,765
15
+ vgs_cli-1.35.0.dev2.data/data/vgscli/vault-resources.yaml,sha256=95CCxQher3MBQq96bvjZviNgo1rgv_NhCtI8X-oxIpA,26335
16
+ vgs_cli-1.35.0.dev2.data/data/vgscli/vault-schema.yaml,sha256=a-wW7X8RyHr-669wtOZrlxlIfeQVtx8EUfA-3vpYytk,637
17
+ vgs_cli-1.35.0.dev2.data/data/vgscli/vault-template.yaml,sha256=IlDisL3H5LXUDMkhUaouKMarRdu0-pJxpTz8ij_lp_M,383
18
+ vgs_cli-1.35.0.dev2.data/data/vgscli/vgs-cli.yaml,sha256=5gCjjloyepaKI6zZfDLeUEWxuRExI-dGJI9RO3omP2k,484
19
+ vgs_cli-1.35.0.dev2.dist-info/licenses/LICENSE,sha256=NwSyzgDYdzv6O2ns2C6UODGI8LN1VnVe_FPTnWS4eXY,1082
19
20
  vgscli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- vgscli/_version.py,sha256=bFXK_xwM7TssYFmK1azB6ulxDG5FLm6_AgpOeKBW0WA,1681
21
+ vgscli/_version.py,sha256=0HHI66nZPGvwbIf40mXNC7yUBONlS3ZLuT9MVF9ALCM,1681
21
22
  vgscli/access_logs.py,sha256=G0GjlLjWiC-N6OzK6-RCvWddcVGQlLCLAcyO9pLWIOM,1928
22
23
  vgscli/audits_api.py,sha256=6FjpS1yTALio-DejgIS2eDw75ChjDp4GoAGXh4uZExU,3211
23
24
  vgscli/auth.py,sha256=vdUZ12nqtefIuSHNmwCFvxoqwS3m6ZP5TqLZFNwKnSU,2104
@@ -26,13 +27,13 @@ vgscli/auth_utils.py,sha256=aC1VI0dFdFeupLS9Okz4uDz-wHhhFabqiLUg2R5vO2s,600
26
27
  vgscli/callback_server.py,sha256=oFFF5AGbbwhF7Hsc5fzS7jeGFzIMbebc9Qdq8uEl1PY,1055
27
28
  vgscli/calm_api.py,sha256=5ZZhJUCuA2E5X670L-EkB_mL2kqHPrYy-ca6WtwTacg,1384
28
29
  vgscli/cert_manager_api.py,sha256=ek25BcyGeUo0pBoqineMTYMgmQaYJfimFFHMljZh5yA,1646
29
- vgscli/cli_progress.py,sha256=l_JR6pdpdk8KzZIcGudfdleZy2CVyy_HdEnsEHsKFHQ,3105
30
+ vgscli/cli_progress.py,sha256=Kpc7iZo0zO6YX3WynteBELuWSYGVvqEjKLq2E5x4lnE,2520
30
31
  vgscli/cli_utils.py,sha256=NzXK29_yIDrB-3T_XdLLB8NCWneQ-cpZFi9mazQL9tQ,3810
31
- vgscli/click_extensions.py,sha256=oyKTo8Al7j0ay0dmxyrX-ZLDzbbGNLZ41tRZaEj5Rkk,7272
32
+ vgscli/click_extensions.py,sha256=m-RpaPaH69m4caYbuiu0j2blxeyy8c8hA0Tvygw1Mvc,7271
32
33
  vgscli/collect_forms.py,sha256=HAg-tvpXxmVq5Zns4v8G0cP-tgYKuWmVFwzuNjieu-A,4619
33
34
  vgscli/collect_forms_api.py,sha256=JHnnsOUyStD7yR92LfGqn8DOpnu0l0yqTUMQnWE_NGs,1246
34
35
  vgscli/config_file.py,sha256=HB8lANU0qoO09Wnkf-QDzPh2qtrnFcqcetnCaQgtQ3U,1543
35
- vgscli/errors.py,sha256=Jh2Lmog6kjXjbwEeAshiYZzmBzbMUqlzofHglN_xM1M,11322
36
+ vgscli/errors.py,sha256=9iWS8v6Zq9A3Iqt3G1-NJQEwT4d8DI_JoU7fdiqHg00,11946
36
37
  vgscli/file_token_util.py,sha256=537b54paMGw0u5PL5VJgQqQ7awKa9lQP88n3tUc_lyw,771
37
38
  vgscli/id_generator.py,sha256=3CbvSOUSyLON76p6M8wUhVQVig8j75_rnWdpxLhBifM,1510
38
39
  vgscli/keyring_token_util.py,sha256=cnR72-0AInzujUsIwHoXwPbgprbkllgAXN-AAI3qsPM,4632
@@ -40,7 +41,7 @@ vgscli/notifications.py,sha256=6D8HFDEl3X4V0V9fYZT-9nWYe2KYVahfhRLMF0pbh4I,6252
40
41
  vgscli/notifications_api.py,sha256=1EXmRb2fOJrU1QOAZ6gmq8yMjBLhNZGSVs3kRtD2Dfw,2342
41
42
  vgscli/payment_accounts_api.py,sha256=wF6K3m7nlKtPmqO9RCpmGFSXYHWtwfhXqNfgI1IGqFI,1665
42
43
  vgscli/tenant_configuration.py,sha256=iC33f2lBHDEL4oHPvdlWp_w--u2x_nWOb86jTHumDLg,5881
43
- vgscli/tenant_creation.py,sha256=MJ5mLCirX4fcZVrCq-YwQmtk3yoxn6TxjxTGFSVWOf4,21768
44
+ vgscli/tenant_creation.py,sha256=9O1i4JsHuEIQt_QkcWYEcenLi8kGJBivcln6LkoHl7U,22230
44
45
  vgscli/tenant_provisioning_api.py,sha256=-mQcSECgWG23LKczu1B9J1d7Bb6ZA6lkpr9RegJiiLg,1203
45
46
  vgscli/testing.py,sha256=MLrsvy3mEwvjtqyuG0UjwEX-rDkbWUo52aXvu2H2sTk,1437
46
47
  vgscli/text.py,sha256=c2d1As7awq8PlEF9NiI6JrjsYYousj9YT0o1xOQ2MQM,172
@@ -48,10 +49,10 @@ vgscli/token_handler.py,sha256=MvtmlicqnGrcopNkTr8Q2RlKE7IDj_sJFxF_39Jd7W8,183
48
49
  vgscli/vgs.py,sha256=BiQuHeOuH25zbTdwNOsW_O69YuKsZFTYA2jr9JFRZng,6262
49
50
  vgscli/cli/__init__.py,sha256=112ej_l1olouWMRIM8H4-s5C4qiG8-u6RziWT102wNk,2765
50
51
  vgscli/cli/commands/__init__.py,sha256=8ptc9-Vzdx0HU3ZeMcKERYjmJyuB5IMjBSF3Igx5yQ4,206
51
- vgscli/cli/commands/apply.py,sha256=ospWMXXXca8L7RrcP1-XhIINLdGs1t_c3-yLYsUKM7A,26784
52
+ vgscli/cli/commands/apply.py,sha256=jLSllU1Hz8WX3ARL-lU8BtDv72bMzP3fXs0vGDtyoWc,26312
52
53
  vgscli/cli/commands/certificate.py,sha256=xQV-G8ZWP9uPFqrczlCpCZkHUt2aJflBP2cc5hjobIw,22930
53
54
  vgscli/cli/commands/delete.py,sha256=2nuYKhp0FhoGTcmJUECjRnlYrKLdAgIoIp-y25ViwDg,3873
54
- vgscli/cli/commands/generate.py,sha256=k6LEiBmvT_cg7SbEvwN39ehMX6I8RlP0gSKgcEL4i4k,5425
55
+ vgscli/cli/commands/generate.py,sha256=cVrU54_o3szgyi7tbMQtMAemx-37xYcFKrAcLcyMglw,5448
55
56
  vgscli/cli/commands/get.py,sha256=Xm8bN11vOjnDA3o_uuAkBXM2wtJQZDrTdQY1ykug4LE,11315
56
57
  vgscli/cli/types/__init__.py,sha256=a-P-SoGHpLzMg7CL1hhg5x_nf6sHq0qAuGm_AtIITmw,107
57
58
  vgscli/cli/types/resource_id.py,sha256=7JILR-KjoVGQDHB40tXJfTcU53iR8wM9Zr24fY5le3o,1172
@@ -66,6 +67,7 @@ vgscli/resource-templates/service-account/credentials-admin.yaml,sha256=acyLd54C
66
67
  vgscli/resource-templates/service-account/public-credential-collect.yaml,sha256=CiorC-6TMx7M0TI8DHmoRSfVKDpOom2wIr_ilwmHQNA,696
67
68
  vgscli/resource-templates/service-account/read-credentials-no-pci.yaml,sha256=_kWRne_ARh8v7A_jHG08gwgPG6NqZhrDRyq6L-ULs2M,769
68
69
  vgscli/resource-templates/service-account/read-credentials-with-pci.yaml,sha256=jWXgFdcqZYisakAt9COM6cjf2-x_Q4eidDLaUh8CZOk,796
70
+ vgscli/resource-templates/service-account/vgs-cli.yaml,sha256=5gCjjloyepaKI6zZfDLeUEWxuRExI-dGJI9RO3omP2k,484
69
71
  vgscli/validation-schemas/collect-form-api-schema.yaml,sha256=THuJbLUo7LOr0c1JUo3FPJEc9Jvde59K4JjdlkVF3hw,1246
70
72
  vgscli/validation-schemas/collect-form-schema.yaml,sha256=vHAN6_Gsg14CeSL41sHEQgboYF-CHF2RaNw72aJ3W8o,1337
71
73
  vgscli/validation-schemas/notification-integration-schema.yaml,sha256=1qHzS5ZxlhTdrDab6-qJhszwu7cpith2HleX3uAO_4E,1837
@@ -73,8 +75,8 @@ vgscli/validation-schemas/service-account-schema.yaml,sha256=L1pjNXgnks15N1wNgf_
73
75
  vgscli/validation-schemas/tenant-schema.yaml,sha256=gBO7xVe2qXSymG0B_45hC11X5nm_jCCAH4Yjs-sbC8g,1457
74
76
  vgscli/validation-schemas/vault-resources.yaml,sha256=95CCxQher3MBQq96bvjZviNgo1rgv_NhCtI8X-oxIpA,26335
75
77
  vgscli/validation-schemas/vault-schema.yaml,sha256=a-wW7X8RyHr-669wtOZrlxlIfeQVtx8EUfA-3vpYytk,637
76
- vgs_cli-1.35.0.dev1.dist-info/METADATA,sha256=VGBQ3d3gv-GXDP3G8IotQeDfo-AJh_tBzZzn-PBstYo,8630
77
- vgs_cli-1.35.0.dev1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
78
- vgs_cli-1.35.0.dev1.dist-info/entry_points.txt,sha256=wCWhRQiMX4Ja6kkqJlYr8LbuiywpmXyyRAZPsau-_RY,39
79
- vgs_cli-1.35.0.dev1.dist-info/top_level.txt,sha256=gabTxCWMkl80q6ha_m-hs_1wQMfGntZcv5DEmkl8JEw,7
80
- vgs_cli-1.35.0.dev1.dist-info/RECORD,,
78
+ vgs_cli-1.35.0.dev2.dist-info/METADATA,sha256=BgcCMIQRMIizLbofbdWzRQKrwq0V5iYsEfO_n22u24U,8984
79
+ vgs_cli-1.35.0.dev2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
80
+ vgs_cli-1.35.0.dev2.dist-info/entry_points.txt,sha256=wCWhRQiMX4Ja6kkqJlYr8LbuiywpmXyyRAZPsau-_RY,39
81
+ vgs_cli-1.35.0.dev2.dist-info/top_level.txt,sha256=gabTxCWMkl80q6ha_m-hs_1wQMfGntZcv5DEmkl8JEw,7
82
+ vgs_cli-1.35.0.dev2.dist-info/RECORD,,
vgscli/_version.py CHANGED
@@ -6,7 +6,7 @@ from packaging.version import InvalidVersion, Version
6
6
 
7
7
  from vgscli.text import bold, green
8
8
 
9
- __version__ = "1.35.0.dev1"
9
+ __version__ = "1.35.0.dev2"
10
10
 
11
11
 
12
12
  # noinspection PyBroadException
@@ -62,26 +62,12 @@ from vgscli.tenant_creation import (
62
62
  TenantCreationStageError,
63
63
  TenantCreationState,
64
64
  TenantCreator,
65
+ archive_state,
65
66
  default_state_path,
66
67
  )
67
68
 
68
69
  logger = logging.getLogger()
69
70
 
70
- TENANT_PROGRESS_LABELS = {
71
- "environment": "Resolving environment",
72
- "tenant": "Creating tenant",
73
- "provisioning": "Waiting for provisioning",
74
- "merchant": "Configuring merchant",
75
- "account": "Creating payment account",
76
- "account_configurations": "Applying account configurations",
77
- "collect_form": "Creating default Collect Form",
78
- }
79
-
80
- TENANT_OPERATION_CHOICES = {
81
- "1": "resume",
82
- "2": "cancel",
83
- }
84
-
85
71
 
86
72
  def _route_error_message(error: exceptions.ClientError) -> str:
87
73
  try:
@@ -351,37 +337,20 @@ def _resolve_tenant_organization(data, org_id: Optional[ResourceId]) -> None:
351
337
  data["organizationId"] = org_id.base58
352
338
 
353
339
 
354
- def _is_interactive_terminal() -> bool:
355
- return click.get_text_stream("stdin").isatty()
356
-
357
-
358
- def _choose_existing_tenant_operation(state: TenantCreationState) -> str:
359
- if state.status == "complete":
360
- raise click.UsageError(
361
- "A tenant was already created from this configuration. To create "
362
- "another tenant, update the file to use a unique tenant name."
340
+ def _echo_tenant_creation_state(state, state_path) -> None:
341
+ click.echo(
342
+ dump_camelized_yaml(
343
+ {
344
+ "apiVersion": "1.0.0",
345
+ "kind": "Tenant",
346
+ "data": state.output(state_path),
347
+ }
363
348
  )
349
+ )
364
350
 
365
- if not _is_interactive_terminal():
366
- raise click.UsageError(
367
- "A tenant creation already exists for this configuration. Run the "
368
- "command in an interactive terminal to resume it or cancel."
369
- )
370
351
 
371
- click.echo(
372
- f'An incomplete tenant creation exists for "{state.spec["name"]}".', err=True
373
- )
374
- click.echo("What would you like to do?", err=True)
375
- click.echo(" [1] Resume the existing creation", err=True)
376
- click.echo(" [2] Cancel", err=True)
377
- choice = click.prompt(
378
- "Choice",
379
- type=click.Choice(tuple(TENANT_OPERATION_CHOICES)),
380
- default="1",
381
- show_choices=False,
382
- err=True,
383
- )
384
- return TENANT_OPERATION_CHOICES[choice]
352
+ def _tenant_progress_message(action: str, name: str) -> str:
353
+ return f'{action} "{name}"'
385
354
 
386
355
 
387
356
  def _apply_tenant_data(
@@ -393,7 +362,7 @@ def _apply_tenant_data(
393
362
  "organizationId": data["organizationId"],
394
363
  }
395
364
  progress = (
396
- Spinner("Applying tenant", detail="Creating tenant")
365
+ Spinner(_tenant_progress_message("Creating tenant", data["name"]))
397
366
  if show_progress
398
367
  else nullcontext()
399
368
  )
@@ -429,12 +398,15 @@ def _apply_tenant_data(
429
398
  "password": attributes["credentials"]["secret"],
430
399
  }
431
400
 
401
+ if show_progress:
402
+ progress.update(
403
+ _tenant_progress_message("Configuring tenant", data["name"])
404
+ )
405
+
432
406
  vault_mgmt = create_vault_mgmt_api(
433
407
  ctx, response.body["data"]["links"]["vault_management_api"]
434
408
  )
435
409
 
436
- if show_progress:
437
- progress.update("Waiting for provisioning")
438
410
  while True:
439
411
  # noinspection PyUnresolvedReferences
440
412
  response = vault_mgmt.vaults.retrieve(
@@ -511,6 +483,7 @@ def apply_tenant(
511
483
  if resume is not None and state_file is not None:
512
484
  raise click.UsageError("--state-file cannot be used with --resume.")
513
485
 
486
+ completed_state = None
514
487
  if resume is not None:
515
488
  state = TenantCreationState.load(resume)
516
489
  resolved_state_file = resume
@@ -536,17 +509,27 @@ def apply_tenant(
536
509
  raise click.UsageError(
537
510
  "The existing tenant creation state does not match this configuration."
538
511
  )
539
- operation = _choose_existing_tenant_operation(existing_state)
540
- if operation == "cancel":
541
- raise click.Abort()
542
- if operation == "resume":
512
+ if existing_state.status == "complete":
513
+ completed_state = existing_state
514
+ else:
543
515
  state = existing_state
544
516
  is_resuming = True
545
517
 
546
518
  try:
547
- action = "Resuming tenant" if is_resuming else "Applying tenant"
548
- with Spinner(action, detail="Preparing") as progress:
549
- account_mgmt = create_account_mgmt_api(ctx)
519
+ account_mgmt = create_account_mgmt_api(ctx)
520
+ existing_tenant_name = None
521
+ configuring_message = _tenant_progress_message(
522
+ "Configuring tenant", state.spec["name"]
523
+ )
524
+ creating_message = _tenant_progress_message(
525
+ "Creating tenant", state.spec["name"]
526
+ )
527
+ initial_message = (
528
+ configuring_message
529
+ if completed_state is not None or state.tenant_resource_id
530
+ else creating_message
531
+ )
532
+ with Spinner(initial_message) as progress:
550
533
  creator = TenantCreator(
551
534
  account_mgmt=account_mgmt,
552
535
  provisioning_api=create_tenant_provisioning_api(ctx),
@@ -554,37 +537,39 @@ def apply_tenant(
554
537
  calm_api_factory=partial(create_calm_api, ctx),
555
538
  collect_forms_api_factory=partial(create_collect_forms_api, ctx),
556
539
  platform_environment=ctx.obj.env,
557
- on_stage=lambda stage: progress.update(TENANT_PROGRESS_LABELS[stage]),
540
+ on_stage=lambda stage: progress.update(
541
+ creating_message
542
+ if stage in {"environment", "tenant"}
543
+ and not state.tenant_resource_id
544
+ else configuring_message
545
+ ),
558
546
  )
559
547
 
560
- if is_resuming:
561
- progress.update("Verifying resume state")
562
- creator.verify_resume_state(state)
548
+ if completed_state is not None:
549
+ tenant_exists = creator.tenant_exists(completed_state)
550
+ if tenant_exists:
551
+ existing_tenant_name = completed_state.spec["name"]
552
+ else:
553
+ archive_state(resolved_state_file)
554
+
555
+ if existing_tenant_name is None:
556
+ if is_resuming:
557
+ creator.verify_resume_state(state)
563
558
 
564
- creator.run(state, resolved_state_file)
559
+ creator.run(state, resolved_state_file)
565
560
  except TenantCreationStageError as error:
566
- click.echo(
567
- dump_camelized_yaml(
568
- {
569
- "apiVersion": "1.0.0",
570
- "kind": "Tenant",
571
- "data": error.state.output(error.state_path),
572
- }
573
- )
574
- )
561
+ _echo_tenant_creation_state(error.state, error.state_path)
575
562
  raise TenantCreationError(
576
563
  error.stage, error.state_path, error.cause, ctx=ctx
577
564
  ) from None
578
565
 
579
- click.echo(
580
- dump_camelized_yaml(
581
- {
582
- "apiVersion": "1.0.0",
583
- "kind": "Tenant",
584
- "data": state.output(resolved_state_file),
585
- }
566
+ if existing_tenant_name is not None:
567
+ click.echo(
568
+ f'Tenant "{existing_tenant_name}" already exists. No changes were made.'
586
569
  )
587
- )
570
+ return
571
+
572
+ _echo_tenant_creation_state(state, resolved_state_file)
588
573
 
589
574
 
590
575
  @apply.command(
@@ -151,6 +151,7 @@ def generate_mft_route():
151
151
  "-t",
152
152
  type=click.Choice(
153
153
  [
154
+ "vgs-cli",
154
155
  "public-credential-collect",
155
156
  "read-credentials-no-pci",
156
157
  "read-credentials-with-pci",
vgscli/cli_progress.py CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  import itertools
4
4
  import threading
5
- import time
6
- from collections.abc import Callable
7
5
  from typing import Optional, TextIO
8
6
 
9
7
  import click
@@ -19,19 +17,14 @@ class Spinner:
19
17
  self,
20
18
  message: str,
21
19
  *,
22
- detail: Optional[str] = None,
23
20
  stream: Optional[TextIO] = None,
24
21
  interval: float = 0.1,
25
- monotonic: Callable[[], float] = time.monotonic,
26
22
  ) -> None:
27
23
  self.message = message
28
- self.detail = detail
29
24
  self.stream = stream or click.get_text_stream("stderr")
30
25
  self.interval = interval
31
- self.monotonic = monotonic
32
26
  self._stop = threading.Event()
33
27
  self._thread: Optional[threading.Thread] = None
34
- self._started_at = self.monotonic()
35
28
  self._max_width = 0
36
29
  try:
37
30
  self._enabled = self.stream.isatty()
@@ -39,7 +32,6 @@ class Spinner:
39
32
  self._enabled = False
40
33
  self._unicode = self._enabled and self._supports_unicode()
41
34
  self._frames = self.UNICODE_FRAMES if self._unicode else self.ASCII_FRAMES
42
- self._separator = " · " if self._unicode else " - "
43
35
 
44
36
  def __enter__(self):
45
37
  if self._enabled:
@@ -66,17 +58,11 @@ class Spinner:
66
58
  def _clear(self) -> None:
67
59
  self._write(f"\r{' ' * self._max_width}\r")
68
60
 
69
- def update(self, detail: str) -> None:
70
- self.detail = detail
61
+ def update(self, message: str) -> None:
62
+ self.message = message
71
63
 
72
64
  def _line(self, frame: str) -> str:
73
- parts = [self.message]
74
- if self.detail:
75
- parts.append(self.detail)
76
- elapsed = max(0, int(self.monotonic() - self._started_at))
77
- minutes, seconds = divmod(elapsed, 60)
78
- parts.append(f"{minutes:02d}:{seconds:02d}")
79
- return f"{frame} {self._separator.join(parts)}"
65
+ return f"{frame} {self.message}"
80
66
 
81
67
  def _supports_unicode(self) -> bool:
82
68
  encoding = getattr(self.stream, "encoding", None)
@@ -121,7 +121,7 @@ def tenant_selection_option(command):
121
121
  "tenants",
122
122
  type=click.STRING,
123
123
  multiple=True,
124
- help="Tenant the Service Account may access. Built-in templates require exactly one.",
124
+ help="Tenant the Service Account may access. Payment templates require exactly one.",
125
125
  )(wrapper)
126
126
  return click.option(
127
127
  "--vault",
vgscli/errors.py CHANGED
@@ -182,10 +182,25 @@ class TenantCreationError(VgsCliError):
182
182
 
183
183
  message = f"Tenant creation stopped during {stage}."
184
184
  if details:
185
- message = f"{message} {', '.join(details)}"
185
+ detail_message = ", ".join(details)
186
+ if not detail_message.endswith((".", "!", "?")):
187
+ detail_message = f"{detail_message}."
188
+ message = f"{message} {detail_message}"
189
+
190
+ if stage == "account" and any(
191
+ "account already exists" in detail.casefold() for detail in details or []
192
+ ):
193
+ self.message = (
194
+ f"{message} A payment account already uses this tenant name. "
195
+ "To create a separate tenant, change the tenant name. "
196
+ "To recover this tenant, contact VGS Support."
197
+ )
198
+ self.ctx = ctx
199
+ return
200
+
186
201
  self.message = (
187
- f"{message} Run the same `vgs apply tenant --file` command again "
188
- "to resume it."
202
+ f"{message} Run the same `vgs apply tenant --file` command again; "
203
+ "the CLI will continue automatically."
189
204
  )
190
205
  self.ctx = ctx
191
206
 
@@ -0,0 +1,17 @@
1
+ apiVersion: 1.0.0
2
+ kind: ServiceAccount
3
+ data:
4
+ name: vgs-cli
5
+ {%- if vaults | length == 0 %}
6
+ {{- cli_warn("Service Account won't have access to any tenants inside the organization. If it needs tenant access, use --tenant <TENANT_ID>.") or "" }}
7
+ {%- else %}
8
+ vaults:
9
+ {%- for vault_id in vaults %}
10
+ - {{ vault_id }}
11
+ {%- endfor %}
12
+ {%- endif %}
13
+ scopes:
14
+ - name: access-logs:read
15
+ - name: organizations:read
16
+ - name: routes:write
17
+ - name: vaults:write
vgscli/tenant_creation.py CHANGED
@@ -172,6 +172,19 @@ def default_state_path(request_digest):
172
172
  return Path(APP_DIR) / "tenant-operations" / f"{request_digest}.json"
173
173
 
174
174
 
175
+ def archive_state(path):
176
+ state_path = Path(path)
177
+ archive_directory = state_path.parent / "archive"
178
+ archive_directory.mkdir(mode=0o700, parents=True, exist_ok=True)
179
+ os.chmod(archive_directory, 0o700)
180
+ archive_path = archive_directory / (
181
+ f"{state_path.stem}-{uuid.uuid4().hex}{state_path.suffix}"
182
+ )
183
+ os.replace(state_path, archive_path)
184
+ os.chmod(archive_path, 0o600)
185
+ return archive_path
186
+
187
+
175
188
  class TenantCreator:
176
189
  def __init__(
177
190
  self,
@@ -230,20 +243,20 @@ class TenantCreator:
230
243
  state.save(state_path)
231
244
  return state
232
245
 
233
- def verify_resume_state(self, state):
246
+ def tenant_exists(self, state):
234
247
  if not state.tenant_resource_id:
235
- return
248
+ return False
236
249
  response = self.provisioning.organization_tenants(state.spec["organizationId"])
237
- tenant = next(
238
- (
239
- item
240
- for item in _resources(response)
241
- if item.get("id") == state.tenant_resource_id
242
- and item.get("attributes", {}).get("identifier") == state.tenant_id
243
- ),
244
- None,
250
+ return any(
251
+ item.get("id") == state.tenant_resource_id
252
+ and item.get("attributes", {}).get("identifier") == state.tenant_id
253
+ for item in _resources(response)
245
254
  )
246
- if tenant is None:
255
+
256
+ def verify_resume_state(self, state):
257
+ if not state.tenant_resource_id:
258
+ return
259
+ if not self.tenant_exists(state):
247
260
  raise click.UsageError(
248
261
  "The tenant in the resume state is no longer available in the organization."
249
262
  )