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.
Files changed (65) hide show
  1. magic_pocket_cli-0.2.0.dist-info/METADATA +14 -0
  2. magic_pocket_cli-0.2.0.dist-info/RECORD +65 -0
  3. magic_pocket_cli-0.2.0.dist-info/WHEEL +4 -0
  4. magic_pocket_cli-0.2.0.dist-info/entry_points.txt +2 -0
  5. pocket_cli/__init__.py +0 -0
  6. pocket_cli/cli/__init__.py +0 -0
  7. pocket_cli/cli/aws_auth.py +48 -0
  8. pocket_cli/cli/awscontainer_cli.py +328 -0
  9. pocket_cli/cli/cloudfront_cli.py +116 -0
  10. pocket_cli/cli/cloudfront_keys_cli.py +68 -0
  11. pocket_cli/cli/cloudfront_waf_cli.py +68 -0
  12. pocket_cli/cli/deploy_cli.py +274 -0
  13. pocket_cli/cli/destroy_cli.py +358 -0
  14. pocket_cli/cli/dsql_cli.py +60 -0
  15. pocket_cli/cli/main_cli.py +91 -0
  16. pocket_cli/cli/migrate_cli.py +148 -0
  17. pocket_cli/cli/neon_cli.py +97 -0
  18. pocket_cli/cli/permissions_cli.py +46 -0
  19. pocket_cli/cli/rds_cli.py +63 -0
  20. pocket_cli/cli/runtime_config_cli.py +185 -0
  21. pocket_cli/cli/s3_cli.py +69 -0
  22. pocket_cli/cli/status_cli.py +56 -0
  23. pocket_cli/cli/tidb_cli.py +73 -0
  24. pocket_cli/cli/vpc_cli.py +92 -0
  25. pocket_cli/cli/waf_cli.py +182 -0
  26. pocket_cli/django_cli.py +412 -0
  27. pocket_cli/mediator.py +220 -0
  28. pocket_cli/resources/__init__.py +0 -0
  29. pocket_cli/resources/aws/__init__.py +0 -0
  30. pocket_cli/resources/aws/builders/__init__.py +57 -0
  31. pocket_cli/resources/aws/builders/codebuild.py +363 -0
  32. pocket_cli/resources/aws/builders/depot.py +84 -0
  33. pocket_cli/resources/aws/builders/docker.py +34 -0
  34. pocket_cli/resources/aws/builders/dockerignore.py +44 -0
  35. pocket_cli/resources/aws/cloudformation.py +790 -0
  36. pocket_cli/resources/aws/ecr.py +145 -0
  37. pocket_cli/resources/aws/efs.py +138 -0
  38. pocket_cli/resources/aws/lambdahandler.py +182 -0
  39. pocket_cli/resources/aws/s3_utils.py +58 -0
  40. pocket_cli/resources/aws/state.py +74 -0
  41. pocket_cli/resources/awscontainer.py +265 -0
  42. pocket_cli/resources/cloudfront.py +491 -0
  43. pocket_cli/resources/cloudfront_acm.py +55 -0
  44. pocket_cli/resources/cloudfront_keys.py +81 -0
  45. pocket_cli/resources/cloudfront_waf.py +67 -0
  46. pocket_cli/resources/dsql.py +142 -0
  47. pocket_cli/resources/neon.py +353 -0
  48. pocket_cli/resources/rds.py +680 -0
  49. pocket_cli/resources/s3.py +307 -0
  50. pocket_cli/resources/tidb.py +298 -0
  51. pocket_cli/resources/upstash.py +152 -0
  52. pocket_cli/resources/vpc.py +67 -0
  53. pocket_cli/templates/cloudformation/awscontainer.yaml +516 -0
  54. pocket_cli/templates/cloudformation/cf_function_api_host.js +5 -0
  55. pocket_cli/templates/cloudformation/cf_function_spa_auth.js +28 -0
  56. pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +8 -0
  57. pocket_cli/templates/cloudformation/cloudfront.yaml +309 -0
  58. pocket_cli/templates/cloudformation/cloudfront_acm.yaml +43 -0
  59. pocket_cli/templates/cloudformation/cloudfront_keys.yaml +32 -0
  60. pocket_cli/templates/cloudformation/cloudfront_waf.yaml +97 -0
  61. pocket_cli/templates/cloudformation/vpc.yaml +213 -0
  62. pocket_cli/templates/init/django-dotenv.env +3 -0
  63. pocket_cli/templates/init/django-settings.py +140 -0
  64. pocket_cli/templates/init/pocket.Dockerfile +26 -0
  65. 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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pocket = pocket_cli.cli.main_cli:main
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.")