magic-pocket-cli 0.2.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.
- magic_pocket_cli-0.2.0.dist-info/METADATA +14 -0
- magic_pocket_cli-0.2.0.dist-info/RECORD +65 -0
- magic_pocket_cli-0.2.0.dist-info/WHEEL +4 -0
- magic_pocket_cli-0.2.0.dist-info/entry_points.txt +2 -0
- pocket_cli/__init__.py +0 -0
- pocket_cli/cli/__init__.py +0 -0
- pocket_cli/cli/aws_auth.py +48 -0
- pocket_cli/cli/awscontainer_cli.py +328 -0
- pocket_cli/cli/cloudfront_cli.py +116 -0
- pocket_cli/cli/cloudfront_keys_cli.py +68 -0
- pocket_cli/cli/cloudfront_waf_cli.py +68 -0
- pocket_cli/cli/deploy_cli.py +274 -0
- pocket_cli/cli/destroy_cli.py +358 -0
- pocket_cli/cli/dsql_cli.py +60 -0
- pocket_cli/cli/main_cli.py +91 -0
- pocket_cli/cli/migrate_cli.py +148 -0
- pocket_cli/cli/neon_cli.py +97 -0
- pocket_cli/cli/permissions_cli.py +46 -0
- pocket_cli/cli/rds_cli.py +63 -0
- pocket_cli/cli/runtime_config_cli.py +185 -0
- pocket_cli/cli/s3_cli.py +69 -0
- pocket_cli/cli/status_cli.py +56 -0
- pocket_cli/cli/tidb_cli.py +73 -0
- pocket_cli/cli/vpc_cli.py +92 -0
- pocket_cli/cli/waf_cli.py +182 -0
- pocket_cli/django_cli.py +412 -0
- pocket_cli/mediator.py +220 -0
- pocket_cli/resources/__init__.py +0 -0
- pocket_cli/resources/aws/__init__.py +0 -0
- pocket_cli/resources/aws/builders/__init__.py +57 -0
- pocket_cli/resources/aws/builders/codebuild.py +363 -0
- pocket_cli/resources/aws/builders/depot.py +84 -0
- pocket_cli/resources/aws/builders/docker.py +34 -0
- pocket_cli/resources/aws/builders/dockerignore.py +44 -0
- pocket_cli/resources/aws/cloudformation.py +790 -0
- pocket_cli/resources/aws/ecr.py +145 -0
- pocket_cli/resources/aws/efs.py +138 -0
- pocket_cli/resources/aws/lambdahandler.py +182 -0
- pocket_cli/resources/aws/s3_utils.py +58 -0
- pocket_cli/resources/aws/state.py +74 -0
- pocket_cli/resources/awscontainer.py +265 -0
- pocket_cli/resources/cloudfront.py +491 -0
- pocket_cli/resources/cloudfront_acm.py +55 -0
- pocket_cli/resources/cloudfront_keys.py +81 -0
- pocket_cli/resources/cloudfront_waf.py +67 -0
- pocket_cli/resources/dsql.py +142 -0
- pocket_cli/resources/neon.py +353 -0
- pocket_cli/resources/rds.py +680 -0
- pocket_cli/resources/s3.py +307 -0
- pocket_cli/resources/tidb.py +298 -0
- pocket_cli/resources/upstash.py +152 -0
- pocket_cli/resources/vpc.py +67 -0
- pocket_cli/templates/cloudformation/awscontainer.yaml +516 -0
- pocket_cli/templates/cloudformation/cf_function_api_host.js +5 -0
- pocket_cli/templates/cloudformation/cf_function_spa_auth.js +28 -0
- pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +8 -0
- pocket_cli/templates/cloudformation/cloudfront.yaml +309 -0
- pocket_cli/templates/cloudformation/cloudfront_acm.yaml +43 -0
- pocket_cli/templates/cloudformation/cloudfront_keys.yaml +32 -0
- pocket_cli/templates/cloudformation/cloudfront_waf.yaml +97 -0
- pocket_cli/templates/cloudformation/vpc.yaml +213 -0
- pocket_cli/templates/init/django-dotenv.env +3 -0
- pocket_cli/templates/init/django-settings.py +140 -0
- pocket_cli/templates/init/pocket.Dockerfile +26 -0
- pocket_cli/templates/init/pocket_simple.toml +31 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: magic-pocket-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI and deploy tools for magic-pocket.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: awscrt>=0.19.0
|
|
7
|
+
Requires-Dist: click>=8.1.7
|
|
8
|
+
Requires-Dist: deepdiff>=6.7.1
|
|
9
|
+
Requires-Dist: jinja2>=3.1.3
|
|
10
|
+
Requires-Dist: magic-pocket>=0.2.0
|
|
11
|
+
Requires-Dist: pathspec>=1.0.4
|
|
12
|
+
Requires-Dist: python-on-whales>=0.68.0
|
|
13
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
14
|
+
Requires-Dist: requests>=2.31.0
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
pocket_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pocket_cli/django_cli.py,sha256=dMy530NG_JuTh8Ij280kYntkttmiSlguxFMthcf4s6M,15630
|
|
3
|
+
pocket_cli/mediator.py,sha256=xJbVM6c8T26WQe4gihNwDngEF7VPIGRFW_l3FNu4lTw,8875
|
|
4
|
+
pocket_cli/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pocket_cli/cli/aws_auth.py,sha256=UyH3U8m6nQYggHC9RVK8zxk8YpjoAV50nGy75ZT97kk,1561
|
|
6
|
+
pocket_cli/cli/awscontainer_cli.py,sha256=XnUrLcWrNhG_wocNjJ_lAXZ2TlH5llQRZHB5KL2g9dk,12366
|
|
7
|
+
pocket_cli/cli/cloudfront_cli.py,sha256=HHhsAaxR6md6gVdKs8XCuetm-_NhMEjqaxVdsgiOCFY,3981
|
|
8
|
+
pocket_cli/cli/cloudfront_keys_cli.py,sha256=h7oBRjZBSNVmIVIlY35AMr1LDYISV914h4zHQ8mWY2Q,2228
|
|
9
|
+
pocket_cli/cli/cloudfront_waf_cli.py,sha256=uMMEFbCveS71fcTMDUljwwjdGZemQF9XhPsZ5GRKf_0,2208
|
|
10
|
+
pocket_cli/cli/deploy_cli.py,sha256=leZaj5ACmzD-kW_-HK7tiCTW_M8gA0H4BpTUw3ykPuo,11172
|
|
11
|
+
pocket_cli/cli/destroy_cli.py,sha256=QDQ-fXIBd527G5upxudTh37FDfilpv038oaeg-IUSek,13037
|
|
12
|
+
pocket_cli/cli/dsql_cli.py,sha256=-46jydJ5-VNsuz_MjUtHl4upE_OjkiGuGTFPv9eXiTs,1559
|
|
13
|
+
pocket_cli/cli/main_cli.py,sha256=R6YykChh9PjFbupHvTtHMkFb1pJOqPaVguipK_DHLPs,2209
|
|
14
|
+
pocket_cli/cli/migrate_cli.py,sha256=hoPlWmcwxjACUBE81m_aNOMJUhgKNdybTTrdgE00_ZQ,5343
|
|
15
|
+
pocket_cli/cli/neon_cli.py,sha256=sYfFFcC2z51mPmwco4ztGnVwEDpAKKeRD-IWQJ4R5Xc,2661
|
|
16
|
+
pocket_cli/cli/permissions_cli.py,sha256=DWnuOWzPoQH3n4g-RTVCjpwtAnwy25yCi6t7Uq5jgnY,1444
|
|
17
|
+
pocket_cli/cli/rds_cli.py,sha256=CKkPLlWMhSBY_JaNqrCLaCh9eDk9IYB8xtHdzerN1Pk,1800
|
|
18
|
+
pocket_cli/cli/runtime_config_cli.py,sha256=aXNOtEVK6pdSifezRik8J59tBNP4Z0Qmxxeo8j00tB0,5664
|
|
19
|
+
pocket_cli/cli/s3_cli.py,sha256=k7oXK7TxAe_h43wB39DFIEuLNI5pn7Ih58jixv3PYb0,1964
|
|
20
|
+
pocket_cli/cli/status_cli.py,sha256=aQ8RlIqD3yI2evCrv9EsUOVYySCk_10S0maEsi2_8tE,2039
|
|
21
|
+
pocket_cli/cli/tidb_cli.py,sha256=4LpBhA0R1dH0YlRC5jSmIm5ybD6hRGZieeGJ4Vi_vZ8,1985
|
|
22
|
+
pocket_cli/cli/vpc_cli.py,sha256=45EzCmOudaCq4OjtkXlU-FHzFl1Bq8i9CZ-7Tf7Svi0,2363
|
|
23
|
+
pocket_cli/cli/waf_cli.py,sha256=KrKnd9Czmo7KtI860gsJKj8D4lVnzRQjGcEwP7RhhEs,6931
|
|
24
|
+
pocket_cli/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
pocket_cli/resources/awscontainer.py,sha256=uM8G7wGazSERNGim-DY6LCVomkvvEAKEME4pyKFWwbU,9854
|
|
26
|
+
pocket_cli/resources/cloudfront.py,sha256=jnhdWlAGAYulGo8We8aScpB8fq9c3EPnHrWUyk30Qmk,19853
|
|
27
|
+
pocket_cli/resources/cloudfront_acm.py,sha256=FcilpNv3kWkXr3rasVjDWkAzdXqO19cvscZhtIOHuqc,1543
|
|
28
|
+
pocket_cli/resources/cloudfront_keys.py,sha256=2mqar5CvL7fgXNErFZy__-ZNCGVq_sPfvPINAxr0yoA,2708
|
|
29
|
+
pocket_cli/resources/cloudfront_waf.py,sha256=btGB_j5yfQyjNVxlvuHqcS29F5poN3u3Wdn-nEk0L4U,2207
|
|
30
|
+
pocket_cli/resources/dsql.py,sha256=VfmDz0Crv8QN-vulrVRhLMzdJ6I3igr88xI3cLPMZL4,4844
|
|
31
|
+
pocket_cli/resources/neon.py,sha256=gc0c0O6iYWmtQLsmc_NqHRp1kiA7BR5Sq4VSdDYpsPc,11558
|
|
32
|
+
pocket_cli/resources/rds.py,sha256=rTyLfMQVOMCmXrVse-7h6rv423jlYU7JdNnG1pKtxHE,28039
|
|
33
|
+
pocket_cli/resources/s3.py,sha256=gTisgRP-rfDgLpNq_WdUkMcOzpKv-kd1SnyPMO8BoU0,11407
|
|
34
|
+
pocket_cli/resources/tidb.py,sha256=0MuIzXtxVvDIzroOwXefrF6i6IaKitn4TYynsHB9JWw,9918
|
|
35
|
+
pocket_cli/resources/upstash.py,sha256=zQBGELyj51sJXeNc8dmg087U_b4I1kcNikmKFS4PirU,4993
|
|
36
|
+
pocket_cli/resources/vpc.py,sha256=Ordq-NzW6JtfoL5wSJx8wVfuaSIsVu5riVNOloFsvX8,1748
|
|
37
|
+
pocket_cli/resources/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
pocket_cli/resources/aws/cloudformation.py,sha256=PhStRkUUE7XTMQKrlmGHnnbtfDCK3-kdXIv65QLJDKM,27056
|
|
39
|
+
pocket_cli/resources/aws/ecr.py,sha256=rz1ew-_4INcA4_vToPDmt8062cGkOA_Kyo1WaDsopCk,4758
|
|
40
|
+
pocket_cli/resources/aws/efs.py,sha256=Efpc_gew1zRP84UNjFh6BE1p-ZO0LSgK_Rn2HnUTh4w,3959
|
|
41
|
+
pocket_cli/resources/aws/lambdahandler.py,sha256=omAnYWDxzSfuAaOzADN07EOuv_YruZmbr9cqnuOyDrA,6648
|
|
42
|
+
pocket_cli/resources/aws/s3_utils.py,sha256=V7OxvCIBPQzbGNPzjQykxv6FyXE2AgD9oTx0S1QtHxg,2239
|
|
43
|
+
pocket_cli/resources/aws/state.py,sha256=8aW74aVKw-8l_87mskFR7o2Wo_YBJRF89keSwn5EArs,2460
|
|
44
|
+
pocket_cli/resources/aws/builders/__init__.py,sha256=rXDg-eloadb9Mq5Px0YQJS74bCzmt8aQ1HeNI-vaatQ,1498
|
|
45
|
+
pocket_cli/resources/aws/builders/codebuild.py,sha256=uQPrXON3iejuiv_mer1Imge0tVcLTrWBnDCidzxrP60,12928
|
|
46
|
+
pocket_cli/resources/aws/builders/depot.py,sha256=uefBJzVsAJYNZ_tjcfaS0Hq3cbtT9cgQ-6fjSHiVLmk,2332
|
|
47
|
+
pocket_cli/resources/aws/builders/docker.py,sha256=FS8GSuBdC3WcXDGOapUqMnUK_q6Lgv0TKAz0OXL07Ug,859
|
|
48
|
+
pocket_cli/resources/aws/builders/dockerignore.py,sha256=KQCoDxO1WRNYoupAlBJkwB4L2l6DUNCsaYUpIPwwRtE,1342
|
|
49
|
+
pocket_cli/templates/cloudformation/awscontainer.yaml,sha256=D-5WYQBUm0jz4l8p3_E9HKzeNUAG2JA1f2OZrSduUp4,17755
|
|
50
|
+
pocket_cli/templates/cloudformation/cf_function_api_host.js,sha256=3gvODZucBKysLzMwIjT5dD2aomO67b5PEVqBrXVj6iI,162
|
|
51
|
+
pocket_cli/templates/cloudformation/cf_function_spa_auth.js,sha256=1lWrhMtiiSbDg0nIkv7wgga7LXCEiRsGHU-N4WnpvFo,1226
|
|
52
|
+
pocket_cli/templates/cloudformation/cf_function_spa_fallback.js,sha256=LU8qCe-xQni8xQDhQ149EG1zGDS2WR9iKRkw8rOpg-8,215
|
|
53
|
+
pocket_cli/templates/cloudformation/cloudfront.yaml,sha256=SeBGabd0Ne06c9JMiabsbq1j2Qr5bqS475rU-Wyubl4,11032
|
|
54
|
+
pocket_cli/templates/cloudformation/cloudfront_acm.yaml,sha256=vtzGST1Xups1MTHOk-NNw8qf74EhrTOIfWrIAHZUJ60,1158
|
|
55
|
+
pocket_cli/templates/cloudformation/cloudfront_keys.yaml,sha256=Cj3MDanxK-5l-0YHb4jloc5leMv7KRK4F4UK7Q5K83o,781
|
|
56
|
+
pocket_cli/templates/cloudformation/cloudfront_waf.yaml,sha256=TTWxEF5b1X8hUxpjqPUfvog08Q4-tm4y7zmZsmTVrpQ,3199
|
|
57
|
+
pocket_cli/templates/cloudformation/vpc.yaml,sha256=jV11OzAVhjOn36uO3PKWDpdEFqJD9TreCFV177lbcdY,5085
|
|
58
|
+
pocket_cli/templates/init/django-dotenv.env,sha256=sgYd_76armFBDDOVEYB4B0PZp0UR8soEf5RJ0NwVwEk,73
|
|
59
|
+
pocket_cli/templates/init/django-settings.py,sha256=6LJWDGaGhazcqsGc-XNPZ9qUAcuUciykd90slD1Y-og,3600
|
|
60
|
+
pocket_cli/templates/init/pocket.Dockerfile,sha256=d0DMct6vZIo9daxioZWT0k3CvqUhG9JlW2XqXjWNytM,688
|
|
61
|
+
pocket_cli/templates/init/pocket_simple.toml,sha256=OcyI0_w65uztA_pfYfqxzCmh1WShORpX4-6lkAwA7Lg,852
|
|
62
|
+
magic_pocket_cli-0.2.0.dist-info/METADATA,sha256=EAwxE7JcfHPmxxQwX6by7dnq5ALTftGPCHap_RlXFXE,417
|
|
63
|
+
magic_pocket_cli-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
64
|
+
magic_pocket_cli-0.2.0.dist-info/entry_points.txt,sha256=X84PBkd02_mFxiCPs97TMJCQo6SwLYjBTxs450JwPfw,56
|
|
65
|
+
magic_pocket_cli-0.2.0.dist-info/RECORD,,
|
pocket_cli/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
from botocore.exceptions import (
|
|
5
|
+
ClientError,
|
|
6
|
+
NoCredentialsError,
|
|
7
|
+
TokenRetrievalError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from pocket.utils import echo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_aws_credentials() -> None:
|
|
14
|
+
"""AWS 認証情報が有効か確認し、無効ならガイドを表示して終了する"""
|
|
15
|
+
try:
|
|
16
|
+
sts = boto3.client("sts")
|
|
17
|
+
sts.get_caller_identity()
|
|
18
|
+
except TokenRetrievalError:
|
|
19
|
+
_print_auth_guide("SSO トークンの有効期限が切れています。")
|
|
20
|
+
raise SystemExit(1) from None
|
|
21
|
+
except NoCredentialsError:
|
|
22
|
+
_print_auth_guide("AWS 認証情報が見つかりません。")
|
|
23
|
+
raise SystemExit(1) from None
|
|
24
|
+
except ClientError as e:
|
|
25
|
+
if e.response["Error"]["Code"] in (
|
|
26
|
+
"ExpiredToken",
|
|
27
|
+
"ExpiredTokenException",
|
|
28
|
+
):
|
|
29
|
+
_print_auth_guide("AWS 認証トークンの有効期限が切れています。")
|
|
30
|
+
raise SystemExit(1) from None
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _print_auth_guide(message: str) -> None:
|
|
35
|
+
echo.danger(message)
|
|
36
|
+
echo.info("")
|
|
37
|
+
echo.info("以下のいずれかで認証してください:")
|
|
38
|
+
echo.info("")
|
|
39
|
+
echo.info(" SSO の場合:")
|
|
40
|
+
echo.info(" aws sso login")
|
|
41
|
+
echo.info(" aws sso login --profile <profile-name>")
|
|
42
|
+
echo.info("")
|
|
43
|
+
echo.info(" IAM ユーザーの場合:")
|
|
44
|
+
echo.info(" aws configure")
|
|
45
|
+
echo.info("")
|
|
46
|
+
echo.info(" 環境変数の場合:")
|
|
47
|
+
echo.info(" export AWS_ACCESS_KEY_ID=...")
|
|
48
|
+
echo.info(" export AWS_SECRET_ACCESS_KEY=...")
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import webbrowser
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
import click
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
from pocket.context import Context
|
|
10
|
+
from pocket.runtime import get_secrets
|
|
11
|
+
from pocket.utils import echo
|
|
12
|
+
from pocket_cli.mediator import Mediator
|
|
13
|
+
from pocket_cli.resources.awscontainer import AwsContainer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def awscontainer():
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_awscontainer_resource(stage):
|
|
22
|
+
context = Context.from_toml(stage=stage)
|
|
23
|
+
if not context.awscontainer:
|
|
24
|
+
echo.danger("awscontainer is not configured for this stage")
|
|
25
|
+
raise Exception("awscontainer is not configured for this stage")
|
|
26
|
+
return AwsContainer(context=context.awscontainer)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@awscontainer.command()
|
|
30
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
31
|
+
def yaml(stage):
|
|
32
|
+
ac = get_awscontainer_resource(stage)
|
|
33
|
+
print(ac.stack.yaml)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@awscontainer.command()
|
|
37
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
38
|
+
def yaml_diff(stage):
|
|
39
|
+
ac = get_awscontainer_resource(stage)
|
|
40
|
+
print(ac.stack.yaml_diff.to_json(indent=2))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@awscontainer.group()
|
|
44
|
+
def secrets():
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@secrets.command("list")
|
|
49
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
50
|
+
@click.option("--show-values", is_flag=True, default=False)
|
|
51
|
+
def list_secrets(stage, show_values):
|
|
52
|
+
ac = get_awscontainer_resource(stage)
|
|
53
|
+
sc = ac.context.secrets
|
|
54
|
+
if not sc:
|
|
55
|
+
echo.warning("secrets is not configured for this stage")
|
|
56
|
+
return
|
|
57
|
+
for key, spec in sc.user.items():
|
|
58
|
+
effective_store = spec.store or sc.store
|
|
59
|
+
print("%s: %s (store=%s)" % (key, spec.name, effective_store))
|
|
60
|
+
if show_values:
|
|
61
|
+
if effective_store == "sm":
|
|
62
|
+
client = boto3.client("secretsmanager", region_name=sc.region)
|
|
63
|
+
value = client.get_secret_value(SecretId=spec.name)["SecretString"]
|
|
64
|
+
else:
|
|
65
|
+
client = boto3.client("ssm", region_name=sc.region)
|
|
66
|
+
value = client.get_parameter(Name=spec.name, WithDecryption=True)[
|
|
67
|
+
"Parameter"
|
|
68
|
+
]["Value"]
|
|
69
|
+
print(" - " + value)
|
|
70
|
+
for key, pocket_secret in sc.managed.items():
|
|
71
|
+
status = "CREATED" if key in sc.pocket_store.secrets else "NOEXIST"
|
|
72
|
+
print("%s: %s %s" % (key, pocket_secret.type, pocket_secret.options))
|
|
73
|
+
print(" - " + status)
|
|
74
|
+
if (status == "CREATED") and show_values:
|
|
75
|
+
value = sc.pocket_store.secrets[key]
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
print(" - " + value)
|
|
78
|
+
else:
|
|
79
|
+
for k, v in value.items():
|
|
80
|
+
print(f" - {k}: {v}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@secrets.command()
|
|
84
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
85
|
+
def create_pocket_managed(stage):
|
|
86
|
+
ac = get_awscontainer_resource(stage)
|
|
87
|
+
sc = ac.context.secrets
|
|
88
|
+
if not sc:
|
|
89
|
+
echo.warning("secrets is not configured for this stage")
|
|
90
|
+
return
|
|
91
|
+
mediator = Mediator(Context.from_toml(stage=stage))
|
|
92
|
+
mediator.create_pocket_managed_secrets()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _confirm_delete_pocket_managed_secrets(awscontainer: AwsContainer):
|
|
96
|
+
sc = awscontainer.context.secrets
|
|
97
|
+
if not sc:
|
|
98
|
+
echo.warning("secrets is not configured")
|
|
99
|
+
return
|
|
100
|
+
existing_secret_keys = [
|
|
101
|
+
key for key in sc.managed.keys() if key in sc.pocket_store.secrets
|
|
102
|
+
]
|
|
103
|
+
if not existing_secret_keys:
|
|
104
|
+
echo.warning("No pocket managed secets are created yet.")
|
|
105
|
+
return
|
|
106
|
+
echo.warning("You are deleting pocket managed secrets.")
|
|
107
|
+
echo.info("Deleting secrets:")
|
|
108
|
+
for key in existing_secret_keys:
|
|
109
|
+
echo.info(" - " + key)
|
|
110
|
+
echo.danger("This data cannot be restored!")
|
|
111
|
+
click.confirm("Do you realy want to delete pocket managed secrets?", abort=True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@secrets.command()
|
|
115
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
116
|
+
def delete_pocket_managed(stage):
|
|
117
|
+
ac = get_awscontainer_resource(stage)
|
|
118
|
+
_confirm_delete_pocket_managed_secrets(ac)
|
|
119
|
+
if ac.context.secrets:
|
|
120
|
+
ac.context.secrets.pocket_store.delete_secrets()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@awscontainer.command()
|
|
124
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
125
|
+
def create(stage):
|
|
126
|
+
ac = get_awscontainer_resource(stage)
|
|
127
|
+
if not ac.status == "NOEXIST":
|
|
128
|
+
echo.warning("AWS lambda container is already created.")
|
|
129
|
+
else:
|
|
130
|
+
mediator = Mediator(Context.from_toml(stage=stage))
|
|
131
|
+
ac.create(mediator)
|
|
132
|
+
echo.success("Created: lambda")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@awscontainer.command()
|
|
136
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
137
|
+
@click.option("--with-secrets", is_flag=True, default=False)
|
|
138
|
+
def destroy(stage, with_secrets):
|
|
139
|
+
ac = get_awscontainer_resource(stage)
|
|
140
|
+
if ac.stack.status == "NOEXIST":
|
|
141
|
+
echo.warning("No AWS lambda container found.")
|
|
142
|
+
else:
|
|
143
|
+
ac.stack.delete()
|
|
144
|
+
echo.success("Aws lambda container was destroyed.")
|
|
145
|
+
if ac.ecr.exists():
|
|
146
|
+
ac.ecr.delete()
|
|
147
|
+
echo.success("ECR repository was deleted.")
|
|
148
|
+
else:
|
|
149
|
+
echo.warning("No ECR repository found.")
|
|
150
|
+
if with_secrets:
|
|
151
|
+
_confirm_delete_pocket_managed_secrets(ac)
|
|
152
|
+
if ac.context.secrets:
|
|
153
|
+
ac.context.secrets.pocket_store.delete_secrets()
|
|
154
|
+
echo.success("Pocket managed secrets were deleted.")
|
|
155
|
+
else:
|
|
156
|
+
echo.warning("Pocket managed secrets still exists.")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@awscontainer.command()
|
|
160
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
161
|
+
def update(stage):
|
|
162
|
+
ac = get_awscontainer_resource(stage)
|
|
163
|
+
if ac.status == "NOEXIST":
|
|
164
|
+
echo.warning("AWS lambda has not created yet.")
|
|
165
|
+
return
|
|
166
|
+
if ac.status == "FAILED":
|
|
167
|
+
echo.danger("AWS lambda has failed. Please check console.")
|
|
168
|
+
return
|
|
169
|
+
if ac.status == "PROGRESS":
|
|
170
|
+
echo.warning("AWS lambda is updating. Please wait.")
|
|
171
|
+
return
|
|
172
|
+
mediator = Mediator(Context.from_toml(stage=stage))
|
|
173
|
+
ac.update(mediator)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@awscontainer.command()
|
|
177
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
178
|
+
def status(stage):
|
|
179
|
+
ac = get_awscontainer_resource(stage)
|
|
180
|
+
if ac.status == "COMPLETED":
|
|
181
|
+
echo.success("Container is working!!!")
|
|
182
|
+
elif ac.status == "NOEXIST":
|
|
183
|
+
echo.warning("Container has not created yet.")
|
|
184
|
+
elif ac.status == "FAILED":
|
|
185
|
+
echo.danger("Container has failed. Please check console.")
|
|
186
|
+
else:
|
|
187
|
+
echo.warning("Container stack status: %s" % ac.stack.status)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _resolve_lambda_target_handlers(
|
|
191
|
+
context: Context, handler_name: str | None
|
|
192
|
+
) -> list[str]:
|
|
193
|
+
"""reload-env / status-env の対象 handler を解決する。"""
|
|
194
|
+
assert context.awscontainer is not None
|
|
195
|
+
handlers = context.awscontainer.handlers
|
|
196
|
+
if handler_name:
|
|
197
|
+
if handler_name not in handlers:
|
|
198
|
+
raise click.ClickException(
|
|
199
|
+
"handler '%s' が見つかりません。利用可能: %s"
|
|
200
|
+
% (handler_name, ", ".join(sorted(handlers.keys())))
|
|
201
|
+
)
|
|
202
|
+
return [handler_name]
|
|
203
|
+
return list(handlers.keys())
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _function_name(context: Context, handler_key: str) -> str:
|
|
207
|
+
assert context.awscontainer is not None
|
|
208
|
+
# deploy 側 (LambdaHandlerContext.function_name = resource_prefix + key) と同じ
|
|
209
|
+
# 正準名を参照する。slug から再構成すると prefix_template / namespace
|
|
210
|
+
# (既定 `pocket`) を取りこぼすため、handler context の値をそのまま使う。
|
|
211
|
+
return context.awscontainer.handlers[handler_key].function_name
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _fetch_lambda_env(client, function_name: str) -> dict[str, str]:
|
|
215
|
+
"""Lambda の現状 Environment.Variables を取得する。"""
|
|
216
|
+
try:
|
|
217
|
+
config = client.get_function_configuration(FunctionName=function_name)
|
|
218
|
+
except ClientError as e:
|
|
219
|
+
if e.response.get("Error", {}).get("Code") == "ResourceNotFoundException":
|
|
220
|
+
raise click.ClickException(
|
|
221
|
+
"Lambda function '%s' が見つかりません。先に `pocket deploy` を"
|
|
222
|
+
"実行してください。" % function_name
|
|
223
|
+
) from e
|
|
224
|
+
raise
|
|
225
|
+
return dict(config.get("Environment", {}).get("Variables", {}))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@awscontainer.command("reload-env")
|
|
229
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
230
|
+
@click.option(
|
|
231
|
+
"--handler", default=None, help="特定 handler のみ対象 (省略時は全 handler)"
|
|
232
|
+
)
|
|
233
|
+
def reload_env(stage, handler):
|
|
234
|
+
"""SSM/Secrets Manager の最新値で Lambda env を即時更新する (CFn を介さない)。
|
|
235
|
+
|
|
236
|
+
deploy 時の CFn snapshot を base に、secrets (managed + user) の最新値を
|
|
237
|
+
boto3 で取得して上書きし、`update_function_configuration` で Lambda に反映。
|
|
238
|
+
side-channel update なので container 再生成が即座に走り、warm container
|
|
239
|
+
内の古い os.environ もリセットされる。
|
|
240
|
+
|
|
241
|
+
設計思想は `pocket waf ip` と同じ (CFn template は deploy 時 snapshot、
|
|
242
|
+
実体は CLI で直接更新、次 deploy で自己治癒)。
|
|
243
|
+
"""
|
|
244
|
+
context = Context.from_toml(stage=stage)
|
|
245
|
+
if not context.awscontainer:
|
|
246
|
+
raise click.ClickException("[awscontainer] が設定されていません")
|
|
247
|
+
|
|
248
|
+
fresh_secrets = get_secrets(stage)
|
|
249
|
+
if not fresh_secrets:
|
|
250
|
+
echo.warning("secrets が宣言されていません。何もしません。")
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
lambda_client = boto3.client("lambda", region_name=context.awscontainer.region)
|
|
254
|
+
targets = _resolve_lambda_target_handlers(context, handler)
|
|
255
|
+
|
|
256
|
+
for h_name in targets:
|
|
257
|
+
function_name = _function_name(context, h_name)
|
|
258
|
+
current = _fetch_lambda_env(lambda_client, function_name)
|
|
259
|
+
new_env = {**current, **fresh_secrets}
|
|
260
|
+
if new_env == current:
|
|
261
|
+
echo.info("[%s] 差分なし (handler 内 env は既に最新)" % h_name)
|
|
262
|
+
continue
|
|
263
|
+
changed = sorted(k for k in fresh_secrets if current.get(k) != fresh_secrets[k])
|
|
264
|
+
lambda_client.update_function_configuration(
|
|
265
|
+
FunctionName=function_name,
|
|
266
|
+
Environment={"Variables": new_env},
|
|
267
|
+
)
|
|
268
|
+
echo.success(
|
|
269
|
+
"[%s] env を更新しました (%d/%d 秘密値を反映、warm container は再生成)"
|
|
270
|
+
% (h_name, len(changed), len(fresh_secrets))
|
|
271
|
+
)
|
|
272
|
+
for k in changed:
|
|
273
|
+
echo.log(" - %s" % k)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@awscontainer.command("status-env")
|
|
277
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
278
|
+
@click.option(
|
|
279
|
+
"--handler", default=None, help="特定 handler のみ対象 (省略時は全 handler)"
|
|
280
|
+
)
|
|
281
|
+
def status_env(stage, handler):
|
|
282
|
+
"""Lambda の現在 env と SSM/SM 上の宣言値の drift を表示する。"""
|
|
283
|
+
context = Context.from_toml(stage=stage)
|
|
284
|
+
if not context.awscontainer:
|
|
285
|
+
raise click.ClickException("[awscontainer] が設定されていません")
|
|
286
|
+
|
|
287
|
+
fresh_secrets = get_secrets(stage)
|
|
288
|
+
lambda_client = boto3.client("lambda", region_name=context.awscontainer.region)
|
|
289
|
+
targets = _resolve_lambda_target_handlers(context, handler)
|
|
290
|
+
|
|
291
|
+
any_drift = False
|
|
292
|
+
for h_name in targets:
|
|
293
|
+
function_name = _function_name(context, h_name)
|
|
294
|
+
current = _fetch_lambda_env(lambda_client, function_name)
|
|
295
|
+
drift = [k for k in fresh_secrets if current.get(k) != fresh_secrets[k]]
|
|
296
|
+
echo.info(
|
|
297
|
+
"[%s] secret keys: %d declared, drift: %d"
|
|
298
|
+
% (h_name, len(fresh_secrets), len(drift))
|
|
299
|
+
)
|
|
300
|
+
for k in sorted(drift):
|
|
301
|
+
if k not in current:
|
|
302
|
+
echo.warning(" + %s (Lambda に未反映、reload-env で投入)" % k)
|
|
303
|
+
else:
|
|
304
|
+
echo.warning(" ~ %s (Lambda 値が古い、reload-env で更新)" % k)
|
|
305
|
+
if drift:
|
|
306
|
+
any_drift = True
|
|
307
|
+
if any_drift:
|
|
308
|
+
echo.warning(
|
|
309
|
+
"drift があります。`pocket resource awscontainer reload-env` で同期できます。"
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
echo.success("drift なし。Lambda env と secrets は同期されています。")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@awscontainer.command()
|
|
316
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
317
|
+
@click.option("--openpath")
|
|
318
|
+
def url(stage, openpath):
|
|
319
|
+
ac = get_awscontainer_resource(stage)
|
|
320
|
+
if ac.status == "COMPLETED":
|
|
321
|
+
if endpoint := ac.endpoints.get("wsgi"):
|
|
322
|
+
echo.success(f"wsgi url: {endpoint}")
|
|
323
|
+
if openpath:
|
|
324
|
+
webbrowser.open(endpoint + "/" + openpath)
|
|
325
|
+
else:
|
|
326
|
+
echo.warning("wsgi endpoint not found.")
|
|
327
|
+
else:
|
|
328
|
+
echo.warning("Container is not working.")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from pocket.context import Context
|
|
4
|
+
from pocket.utils import echo
|
|
5
|
+
from pocket_cli.resources.cloudfront import CloudFront
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
def cloudfront():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_cloudfront_resources(stage, name=None):
|
|
14
|
+
context = Context.from_toml(stage=stage)
|
|
15
|
+
if not context.cloudfront:
|
|
16
|
+
echo.danger("cloudfront is not configured for this stage")
|
|
17
|
+
raise Exception("cloudfront is not configured for this stage")
|
|
18
|
+
if name:
|
|
19
|
+
if name not in context.cloudfront:
|
|
20
|
+
echo.danger("cloudfront '%s' is not configured" % name)
|
|
21
|
+
raise Exception("cloudfront '%s' is not configured" % name)
|
|
22
|
+
return [CloudFront(context.cloudfront[name])]
|
|
23
|
+
return [CloudFront(cf_ctx) for cf_ctx in context.cloudfront.values()]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@cloudfront.command()
|
|
27
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
28
|
+
@click.option("--name", default=None)
|
|
29
|
+
def yaml(stage, name):
|
|
30
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
31
|
+
echo.info("[%s]" % cf.context.name)
|
|
32
|
+
print(cf.stack.yaml)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@cloudfront.command()
|
|
36
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
37
|
+
@click.option("--name", default=None)
|
|
38
|
+
def yaml_diff(stage, name):
|
|
39
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
40
|
+
echo.info("[%s]" % cf.context.name)
|
|
41
|
+
print(cf.stack.yaml_diff.to_json(indent=2))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cloudfront.command()
|
|
45
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
46
|
+
@click.option("--name", default=None)
|
|
47
|
+
def context(stage, name):
|
|
48
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
49
|
+
echo.info("[%s]" % cf.context.name)
|
|
50
|
+
print(cf.context.model_dump_json(indent=2))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@cloudfront.command()
|
|
54
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
55
|
+
@click.option("--name", default=None)
|
|
56
|
+
def create(stage, name):
|
|
57
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
58
|
+
echo.info("[%s]" % cf.context.name)
|
|
59
|
+
cf.create()
|
|
60
|
+
echo.success("cloudfront store was created")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@cloudfront.command()
|
|
64
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
65
|
+
@click.option("--name", default=None)
|
|
66
|
+
def destroy(stage, name):
|
|
67
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
68
|
+
echo.info("[%s]" % cf.context.name)
|
|
69
|
+
cf.delete()
|
|
70
|
+
echo.success("cloudfront store was deleted successfully.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@cloudfront.command()
|
|
74
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
75
|
+
@click.option("--name", default=None)
|
|
76
|
+
def update(stage, name):
|
|
77
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
78
|
+
echo.info("[%s]" % cf.context.name)
|
|
79
|
+
if cf.status == "NOEXIST":
|
|
80
|
+
echo.warning("CloudFront resource has not created yet.")
|
|
81
|
+
continue
|
|
82
|
+
if cf.status == "FAILED":
|
|
83
|
+
echo.danger(
|
|
84
|
+
"CloudFront resource creation has failed. Please check console."
|
|
85
|
+
)
|
|
86
|
+
continue
|
|
87
|
+
if cf.status == "PROGRESS":
|
|
88
|
+
echo.warning("CloudFront is updating. Please wait.")
|
|
89
|
+
continue
|
|
90
|
+
cf.update()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cloudfront.command()
|
|
94
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
95
|
+
@click.option("--name", default=None)
|
|
96
|
+
@click.option("--skip-build", is_flag=True, default=False)
|
|
97
|
+
def upload(stage, name, skip_build):
|
|
98
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
99
|
+
if not cf.context.uploadable_routes:
|
|
100
|
+
echo.info("[%s] アップロード対象のルートがありません" % cf.context.name)
|
|
101
|
+
continue
|
|
102
|
+
echo.info("[%s] アップロード開始" % cf.context.name)
|
|
103
|
+
cf.upload(skip_build=skip_build)
|
|
104
|
+
echo.success("アップロード完了")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@cloudfront.command()
|
|
108
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
109
|
+
@click.option("--name", default=None)
|
|
110
|
+
def status(stage, name):
|
|
111
|
+
for cf in get_cloudfront_resources(stage, name):
|
|
112
|
+
echo.info("[%s]" % cf.context.name)
|
|
113
|
+
if cf.status == "COMPLETED":
|
|
114
|
+
echo.success("COMPLETED")
|
|
115
|
+
else:
|
|
116
|
+
print(cf.status)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from pocket.context import Context
|
|
4
|
+
from pocket.utils import echo
|
|
5
|
+
from pocket_cli.resources.cloudfront_keys import CloudFrontKeys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
def cloudfront_keys():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_cloudfront_keys_resources(stage, name=None):
|
|
14
|
+
context = Context.from_toml(stage=stage)
|
|
15
|
+
if not context.cloudfront:
|
|
16
|
+
echo.danger("cloudfront is not configured for this stage")
|
|
17
|
+
raise Exception("cloudfront is not configured for this stage")
|
|
18
|
+
results = []
|
|
19
|
+
for cf_name, cf_ctx in context.cloudfront.items():
|
|
20
|
+
if not cf_ctx.signing_key:
|
|
21
|
+
continue
|
|
22
|
+
if name and cf_name != name:
|
|
23
|
+
continue
|
|
24
|
+
results.append(CloudFrontKeys(cf_ctx))
|
|
25
|
+
if name and not results:
|
|
26
|
+
echo.danger("cloudfront_keys '%s' is not configured" % name)
|
|
27
|
+
raise Exception("cloudfront_keys '%s' is not configured" % name)
|
|
28
|
+
return results
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@cloudfront_keys.command()
|
|
32
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
33
|
+
@click.option("--name", default=None)
|
|
34
|
+
def yaml(stage, name):
|
|
35
|
+
for cfk in get_cloudfront_keys_resources(stage, name):
|
|
36
|
+
echo.info("[%s]" % cfk.context.name)
|
|
37
|
+
print(cfk.stack.yaml)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@cloudfront_keys.command()
|
|
41
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
42
|
+
@click.option("--name", default=None)
|
|
43
|
+
def yaml_diff(stage, name):
|
|
44
|
+
for cfk in get_cloudfront_keys_resources(stage, name):
|
|
45
|
+
echo.info("[%s]" % cfk.context.name)
|
|
46
|
+
print(cfk.stack.yaml_diff.to_json(indent=2))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@cloudfront_keys.command()
|
|
50
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
51
|
+
@click.option("--name", default=None)
|
|
52
|
+
def status(stage, name):
|
|
53
|
+
for cfk in get_cloudfront_keys_resources(stage, name):
|
|
54
|
+
echo.info("[%s]" % cfk.context.name)
|
|
55
|
+
if cfk.status == "COMPLETED":
|
|
56
|
+
echo.success("COMPLETED")
|
|
57
|
+
else:
|
|
58
|
+
print(cfk.status)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@cloudfront_keys.command()
|
|
62
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
63
|
+
@click.option("--name", default=None)
|
|
64
|
+
def destroy(stage, name):
|
|
65
|
+
for cfk in get_cloudfront_keys_resources(stage, name):
|
|
66
|
+
echo.info("[%s]" % cfk.context.name)
|
|
67
|
+
cfk.delete()
|
|
68
|
+
echo.success("cloudfront_keys was deleted successfully.")
|