magic-pocket-cli 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/PKG-INFO +2 -2
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/awscontainer_cli.py +6 -3
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/deploy_cli.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/destroy_cli.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/neon_cli.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/rds_cli.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/waf_cli.py +1 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/django_cli.py +5 -5
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/depot.py +1 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/cloudformation.py +6 -2
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/ecr.py +2 -2
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/efs.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/lambdahandler.py +23 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/awscontainer.py +31 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/cloudfront.py +7 -3
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/cloudfront_waf.py +2 -1
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/neon.py +10 -4
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/rds.py +24 -15
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/tidb.py +2 -2
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/upstash.py +5 -3
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pyproject.toml +2 -2
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/.gitignore +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/__init__.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/__init__.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/aws_auth.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/cloudfront_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/dsql_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/main_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/migrate_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/permissions_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/runtime_config_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/s3_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/status_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/tidb_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/cli/vpc_cli.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/mediator.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/__init__.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/__init__.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/__init__.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/codebuild.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/docker.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/s3_utils.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/state.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/cloudfront_acm.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/cloudfront_keys.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/dsql.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/s3.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/vpc.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/awscontainer.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cloudfront.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/django-dotenv.env +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/django-settings.py +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
- {magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/pocket_simple.toml +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: magic-pocket-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CLI and deploy tools for magic-pocket.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: awscrt>=0.19.0
|
|
7
7
|
Requires-Dist: click>=8.1.7
|
|
8
8
|
Requires-Dist: deepdiff>=6.7.1
|
|
9
9
|
Requires-Dist: jinja2>=3.1.3
|
|
10
|
-
Requires-Dist: magic-pocket>=0.2.
|
|
10
|
+
Requires-Dist: magic-pocket>=0.2.2
|
|
11
11
|
Requires-Dist: pathspec>=1.0.4
|
|
12
12
|
Requires-Dist: python-on-whales>=0.68.0
|
|
13
13
|
Requires-Dist: pyyaml>=6.0.1
|
|
@@ -191,7 +191,8 @@ def _resolve_lambda_target_handlers(
|
|
|
191
191
|
context: Context, handler_name: str | None
|
|
192
192
|
) -> list[str]:
|
|
193
193
|
"""reload-env / status-env の対象 handler を解決する。"""
|
|
194
|
-
|
|
194
|
+
if context.awscontainer is None:
|
|
195
|
+
raise RuntimeError("awscontainer is not configured")
|
|
195
196
|
handlers = context.awscontainer.handlers
|
|
196
197
|
if handler_name:
|
|
197
198
|
if handler_name not in handlers:
|
|
@@ -204,7 +205,8 @@ def _resolve_lambda_target_handlers(
|
|
|
204
205
|
|
|
205
206
|
|
|
206
207
|
def _function_name(context: Context, handler_key: str) -> str:
|
|
207
|
-
|
|
208
|
+
if context.awscontainer is None:
|
|
209
|
+
raise RuntimeError("awscontainer is not configured")
|
|
208
210
|
# deploy 側 (LambdaHandlerContext.function_name = resource_prefix + key) と同じ
|
|
209
211
|
# 正準名を参照する。slug から再構成すると prefix_template / namespace
|
|
210
212
|
# (既定 `pocket`) を取りこぼすため、handler context の値をそのまま使う。
|
|
@@ -306,7 +308,8 @@ def status_env(stage, handler):
|
|
|
306
308
|
any_drift = True
|
|
307
309
|
if any_drift:
|
|
308
310
|
echo.warning(
|
|
309
|
-
"drift
|
|
311
|
+
"drift があります。"
|
|
312
|
+
"`pocket resource awscontainer reload-env` で同期できます。"
|
|
310
313
|
)
|
|
311
314
|
else:
|
|
312
315
|
echo.success("drift なし。Lambda env と secrets は同期されています。")
|
|
@@ -70,7 +70,8 @@ def get_resources(context: Context, *, state_bucket: str = ""):
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _create_state_store(context: Context) -> StateStore:
|
|
73
|
-
|
|
73
|
+
if not context.general:
|
|
74
|
+
raise RuntimeError("general context is not configured")
|
|
74
75
|
resource_prefix = context.general.prefix_template.format(
|
|
75
76
|
stage=context.stage,
|
|
76
77
|
project=context.project_name,
|
|
@@ -19,7 +19,8 @@ from pocket_cli.resources.vpc import Vpc
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _create_state_store(context: Context) -> StateStore:
|
|
22
|
-
|
|
22
|
+
if not context.general:
|
|
23
|
+
raise RuntimeError("general context is not configured")
|
|
23
24
|
resource_prefix = context.general.prefix_template.format(
|
|
24
25
|
stage=context.stage,
|
|
25
26
|
project=context.project_name,
|
|
@@ -53,7 +53,8 @@ def branch_out(stage, base_stage):
|
|
|
53
53
|
base_neon = get_neon_resource(base_stage)
|
|
54
54
|
if not base_neon.working:
|
|
55
55
|
raise Exception("Base stage is not working")
|
|
56
|
-
|
|
56
|
+
if not base_neon.branch:
|
|
57
|
+
raise RuntimeError("base stage branch is not resolved")
|
|
57
58
|
neon.create_branch(base_neon.branch)
|
|
58
59
|
echo.success("New branch was created")
|
|
59
60
|
|
|
@@ -56,7 +56,8 @@ def destroy(stage):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def _print_endpoint(r: Rds):
|
|
59
|
-
|
|
59
|
+
if not r.cluster:
|
|
60
|
+
raise RuntimeError("RDS cluster is not available")
|
|
60
61
|
echo.success("Endpoint: %s" % r.cluster.get("Endpoint", ""))
|
|
61
62
|
echo.success("Port: %s" % r.cluster.get("Port", ""))
|
|
62
63
|
echo.success("Database: %s" % r.context.database_name)
|
|
@@ -91,7 +91,7 @@ def _detect_self_ipv4() -> str:
|
|
|
91
91
|
"""1 次: AWS checkip、fallback: ipify。/32 を付けた CIDR を返す。"""
|
|
92
92
|
for url in ("https://checkip.amazonaws.com", "https://api.ipify.org"):
|
|
93
93
|
try:
|
|
94
|
-
with urllib.request.urlopen(url, timeout=5) as r:
|
|
94
|
+
with urllib.request.urlopen(url, timeout=5) as r: # noqa: S310 固定 URL (checkip/ipify) + timeout 済
|
|
95
95
|
ip = r.read().decode("utf-8").strip()
|
|
96
96
|
if ip:
|
|
97
97
|
return f"{ip}/32"
|
|
@@ -189,7 +189,7 @@ def build(stage: str, allow_dirty: bool):
|
|
|
189
189
|
try:
|
|
190
190
|
tag = get_commit_hash()
|
|
191
191
|
except RuntimeError as e:
|
|
192
|
-
raise click.ClickException(str(e))
|
|
192
|
+
raise click.ClickException(str(e)) from e
|
|
193
193
|
context = Context.from_toml(stage=stage)
|
|
194
194
|
target = build_image(context, tag=tag)
|
|
195
195
|
echo.success("built and pushed: %s" % target)
|
|
@@ -281,10 +281,10 @@ def upload_collected_staticfiles(stage: str):
|
|
|
281
281
|
echo.info("Bucket: %s" % s3_bucket_name)
|
|
282
282
|
echo.info("Location: %s" % s3_location)
|
|
283
283
|
echo.info("Uploading static files...")
|
|
284
|
-
run(
|
|
284
|
+
run( # noqa: S602 aws s3 sync を pocket.toml 設定値から構築 (信頼境界内)
|
|
285
285
|
"aws s3 sync %s s3://%s/%s/ --delete"
|
|
286
286
|
% (local_storage["OPTIONS"]["location"], s3_bucket_name, s3_location),
|
|
287
|
-
shell=True,
|
|
287
|
+
shell=True, # nosemgrep
|
|
288
288
|
check=True,
|
|
289
289
|
)
|
|
290
290
|
|
|
@@ -319,7 +319,7 @@ def collectstatic_locally(stage: str):
|
|
|
319
319
|
set_staticfiles_override_env(local_storage)
|
|
320
320
|
cmd = _build_python_command(["manage.py", "collectstatic", "--noinput"])
|
|
321
321
|
project_dir = _get_project_dir(stage)
|
|
322
|
-
run(cmd, check=True, cwd=project_dir)
|
|
322
|
+
run(cmd, check=True, cwd=project_dir) # noqa: S603 shell=False + 制御された引数
|
|
323
323
|
clear_staticfiles_override_env()
|
|
324
324
|
|
|
325
325
|
|
|
@@ -409,4 +409,4 @@ def upload(storage, stage, delete, dryrun):
|
|
|
409
409
|
if dryrun:
|
|
410
410
|
cmd += " --dryrun"
|
|
411
411
|
print(cmd)
|
|
412
|
-
run(cmd, shell=True, check=True)
|
|
412
|
+
run(cmd, shell=True, check=True) # noqa: S602 aws s3 sync を pocket.toml 設定値から構築 (信頼境界内) # nosemgrep
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/depot.py
RENAMED
|
@@ -77,7 +77,7 @@ class DepotBuilder:
|
|
|
77
77
|
env[key] = value
|
|
78
78
|
if self.project_id:
|
|
79
79
|
env["DEPOT_PROJECT_ID"] = self.project_id
|
|
80
|
-
subprocess.run(cmd, check=True, env=env)
|
|
80
|
+
subprocess.run(cmd, check=True, env=env) # noqa: S603 shell=False + 制御された引数
|
|
81
81
|
print("Depot ビルド完了")
|
|
82
82
|
|
|
83
83
|
def delete(self) -> None:
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/cloudformation.py
RENAMED
|
@@ -645,10 +645,14 @@ class ContainerStack(Stack):
|
|
|
645
645
|
return {}
|
|
646
646
|
|
|
647
647
|
def _resolve_vpc_zone_count(self) -> int:
|
|
648
|
-
|
|
648
|
+
if not self.context.vpc:
|
|
649
|
+
raise RuntimeError("VPC context is required")
|
|
649
650
|
vpc_stack = VpcStack(self.context.vpc)
|
|
650
651
|
output = vpc_stack.output
|
|
651
|
-
|
|
652
|
+
if not output:
|
|
653
|
+
raise RuntimeError(
|
|
654
|
+
f"VPC stack '{vpc_stack.name}' の output が取得できません"
|
|
655
|
+
)
|
|
652
656
|
prefix = vpc_stack.export["private_subnet_"]
|
|
653
657
|
count = 0
|
|
654
658
|
for i in range(1, 20):
|
|
@@ -110,11 +110,11 @@ class Ecr:
|
|
|
110
110
|
repositoryName=self.name,
|
|
111
111
|
imageIds=[{"imageTag": source_tag}],
|
|
112
112
|
)["images"]
|
|
113
|
-
except self.client.exceptions.RepositoryNotFoundException:
|
|
113
|
+
except self.client.exceptions.RepositoryNotFoundException as e:
|
|
114
114
|
raise ValueError(
|
|
115
115
|
"ECR repository '%s' が存在しません。"
|
|
116
116
|
"先に `pocket django build` を実行してください。" % self.name
|
|
117
|
-
)
|
|
117
|
+
) from e
|
|
118
118
|
if not images:
|
|
119
119
|
raise ValueError(
|
|
120
120
|
"image :%s が ECR repository '%s' に存在しません。"
|
|
@@ -123,7 +123,8 @@ class Efs:
|
|
|
123
123
|
def status(self) -> ResourceStatus:
|
|
124
124
|
if not self.exists():
|
|
125
125
|
return "NOEXIST"
|
|
126
|
-
|
|
126
|
+
if not self.description:
|
|
127
|
+
raise RuntimeError("EFS description is not available")
|
|
127
128
|
if self.description["LifeCycleState"] != "available":
|
|
128
129
|
return "PROGRESS"
|
|
129
130
|
if self.lifecycle_policies != [
|
|
@@ -79,6 +79,29 @@ class LambdaHandler:
|
|
|
79
79
|
if wait:
|
|
80
80
|
self.wait_update()
|
|
81
81
|
|
|
82
|
+
def get_environment(self) -> dict[str, str]:
|
|
83
|
+
"""Lambda の現状 Environment.Variables を取得する (ImportValue / secret は
|
|
84
|
+
解決済みの実値が返る)。function 未作成なら空 dict。"""
|
|
85
|
+
try:
|
|
86
|
+
config = self.client.get_function_configuration(FunctionName=self.name)
|
|
87
|
+
except ClientError:
|
|
88
|
+
return {}
|
|
89
|
+
return dict(config.get("Environment", {}).get("Variables", {}))
|
|
90
|
+
|
|
91
|
+
def update_environment(self, env: dict[str, str], wait=True):
|
|
92
|
+
"""Environment.Variables を side-channel で直接更新する。
|
|
93
|
+
|
|
94
|
+
`update()` は update_function_code (code のみ) で Environment を更新しない。
|
|
95
|
+
deploy 時に CFn を介さず env を同期したい用途 (DEPLOY_HASH 同期 / reload-env)
|
|
96
|
+
で使う。
|
|
97
|
+
"""
|
|
98
|
+
self.client.update_function_configuration(
|
|
99
|
+
FunctionName=self.name, Environment={"Variables": env}
|
|
100
|
+
)
|
|
101
|
+
print(f"lambda function {self.name} environment was updated.")
|
|
102
|
+
if wait:
|
|
103
|
+
self.wait_update()
|
|
104
|
+
|
|
82
105
|
def wait_update(self, interval=3, limit=60):
|
|
83
106
|
print(f"waiting lambda fanction {self.name} update.", end="", flush=True)
|
|
84
107
|
for _i in range(limit // interval):
|
|
@@ -232,6 +232,37 @@ class AwsContainer:
|
|
|
232
232
|
if not self.stack.yaml_synced:
|
|
233
233
|
self.stack.update()
|
|
234
234
|
self.stack.wait_status("COMPLETED", timeout=600, interval=10)
|
|
235
|
+
self.ensure_post_deploy_state()
|
|
236
|
+
|
|
237
|
+
def ensure_post_deploy_state(self, mediator: Mediator | None = None):
|
|
238
|
+
"""deploy 完了後に Lambda env の DEPLOY_HASH を冪等に同期する。
|
|
239
|
+
|
|
240
|
+
LambdaHandler.update() は update_function_code (code) のみで Environment を
|
|
241
|
+
更新しない。Lambda env は CFn stack.update() 経由でしか書き換わらないため、
|
|
242
|
+
stack が status==COMPLETED / yaml_synced と判定されて update() がスキップ
|
|
243
|
+
されると DEPLOY_HASH が旧値に固着する。すると Django は古い hash の static
|
|
244
|
+
URL (/static/<旧hash>/...) を生成する一方、CloudFront の deploy_hash 関数は
|
|
245
|
+
毎 deploy 追従するため、静的アセットが全滅する (403)。
|
|
246
|
+
|
|
247
|
+
cloudfront の KVS 書き込みと同じ philosophy で、deploy フロー末尾で stack
|
|
248
|
+
状態によらず side-channel で冪等に同期する (reload-env / waf ip と同様)。
|
|
249
|
+
deploy_resources の post-deploy hook からも呼ばれるため、wait_status が
|
|
250
|
+
timeout して次 deploy で update() がスキップされた場合でも自己治癒する。
|
|
251
|
+
"""
|
|
252
|
+
deploy_hash = self.context.envs.get("DEPLOY_HASH")
|
|
253
|
+
if not deploy_hash:
|
|
254
|
+
return
|
|
255
|
+
for key, handler in self.handlers.items():
|
|
256
|
+
if handler.status == "NOEXIST":
|
|
257
|
+
continue
|
|
258
|
+
current = handler.get_environment()
|
|
259
|
+
if current.get("DEPLOY_HASH") == deploy_hash:
|
|
260
|
+
continue
|
|
261
|
+
echo.log(
|
|
262
|
+
"[%s] Lambda env DEPLOY_HASH を同期します (%s → %s)"
|
|
263
|
+
% (key, current.get("DEPLOY_HASH"), deploy_hash)
|
|
264
|
+
)
|
|
265
|
+
handler.update_environment({**current, "DEPLOY_HASH": deploy_hash})
|
|
235
266
|
|
|
236
267
|
def get_host(self, key: str):
|
|
237
268
|
handler = self.handlers[key]
|
|
@@ -204,7 +204,9 @@ class CloudFront:
|
|
|
204
204
|
if route.build and not skip_build:
|
|
205
205
|
echo.info("ビルド実行: %s" % route.build)
|
|
206
206
|
try:
|
|
207
|
-
|
|
207
|
+
# route.build は pocket.toml の build コマンド (設定者 =
|
|
208
|
+
# デプロイ実行者)。意図的な shell 実行のため S602 / semgrep を抑制。
|
|
209
|
+
subprocess.run(route.build, shell=True, check=True) # noqa: S602 # nosemgrep
|
|
208
210
|
except subprocess.CalledProcessError as e:
|
|
209
211
|
echo.danger(
|
|
210
212
|
"build コマンドが失敗しました (exit %d): %s"
|
|
@@ -224,7 +226,8 @@ class CloudFront:
|
|
|
224
226
|
|
|
225
227
|
def _upload_route(self, route: RouteContext):
|
|
226
228
|
s3_prefix = (route.origin_path + route.path_pattern.rstrip("/*")).lstrip("/")
|
|
227
|
-
|
|
229
|
+
if not route.build_dir:
|
|
230
|
+
raise RuntimeError("route.build_dir is not set")
|
|
228
231
|
local_dir = Path(route.build_dir)
|
|
229
232
|
uploaded_keys: set[str] = set()
|
|
230
233
|
for file in local_dir.rglob("*"):
|
|
@@ -338,7 +341,8 @@ class CloudFront:
|
|
|
338
341
|
def _ensure_redirect_from_website(self):
|
|
339
342
|
if not self.context.redirect_from:
|
|
340
343
|
return
|
|
341
|
-
|
|
344
|
+
if not self.context.domain:
|
|
345
|
+
raise RuntimeError("domain is required when redirect_from is set")
|
|
342
346
|
for redirect_from in self.context.redirect_from:
|
|
343
347
|
self.s3_client.put_bucket_website(
|
|
344
348
|
Bucket=redirect_from.domain,
|
|
@@ -45,7 +45,8 @@ class CloudFrontWaf:
|
|
|
45
45
|
echo.log("WAF (IPSet + WebACL) を作成中 (us-east-1)...")
|
|
46
46
|
self.stack.create()
|
|
47
47
|
self.stack.wait_status("COMPLETED", timeout=300, interval=10)
|
|
48
|
-
|
|
48
|
+
if self.context.waf is None:
|
|
49
|
+
raise RuntimeError("waf context is not configured")
|
|
49
50
|
if self.context.waf.enable_ip_set:
|
|
50
51
|
echo.info(
|
|
51
52
|
"WAF を作成しました。`pocket waf ip add self --name %s --stage %s` "
|
|
@@ -80,7 +80,7 @@ class NeonApi:
|
|
|
80
80
|
|
|
81
81
|
def get(self, path):
|
|
82
82
|
logger.info("GET %s" % self.endpoint + path)
|
|
83
|
-
res = requests.get(self.endpoint + path, headers=self.header)
|
|
83
|
+
res = requests.get(self.endpoint + path, headers=self.header, timeout=30)
|
|
84
84
|
logger.debug(res.status_code)
|
|
85
85
|
logger.debug(json.dumps(res.json(), indent=2))
|
|
86
86
|
if 200 <= res.status_code < 300:
|
|
@@ -95,7 +95,9 @@ class NeonApi:
|
|
|
95
95
|
def post(self, path, data=None):
|
|
96
96
|
logger.warning("POST %s" % self.endpoint + path)
|
|
97
97
|
logger.debug(json.dumps(data, indent=2))
|
|
98
|
-
res = requests.post(
|
|
98
|
+
res = requests.post(
|
|
99
|
+
self.endpoint + path, headers=self.header, json=data, timeout=30
|
|
100
|
+
)
|
|
99
101
|
logger.debug(res.status_code)
|
|
100
102
|
logger.debug(json.dumps(res.json(), indent=2))
|
|
101
103
|
if 200 <= res.status_code < 300:
|
|
@@ -109,7 +111,9 @@ class NeonApi:
|
|
|
109
111
|
def delete(self, path, data=None):
|
|
110
112
|
logger.warning("DELETE %s" % self.endpoint + path)
|
|
111
113
|
logger.debug(json.dumps(data, indent=2))
|
|
112
|
-
res = requests.delete(
|
|
114
|
+
res = requests.delete(
|
|
115
|
+
self.endpoint + path, headers=self.header, json=data, timeout=30
|
|
116
|
+
)
|
|
113
117
|
logger.debug(res.status_code)
|
|
114
118
|
logger.debug(json.dumps(res.json(), indent=2))
|
|
115
119
|
if 200 <= res.status_code < 300:
|
|
@@ -192,7 +196,9 @@ class Neon:
|
|
|
192
196
|
組織キー: GET /projects で一覧取得し name で検索。
|
|
193
197
|
プロジェクトスコープキー: エラーから project_id を取得し直接アクセス。
|
|
194
198
|
"""
|
|
195
|
-
res = requests.get(
|
|
199
|
+
res = requests.get(
|
|
200
|
+
self.api.endpoint + "projects", headers=self.api.header, timeout=30
|
|
201
|
+
)
|
|
196
202
|
if 200 <= res.status_code < 300:
|
|
197
203
|
for p in res.json().get("projects", []):
|
|
198
204
|
if p["name"] == self.context.project_name:
|
|
@@ -106,9 +106,9 @@ class Rds:
|
|
|
106
106
|
|
|
107
107
|
@property
|
|
108
108
|
def master_user_secret_arn(self) -> str | None:
|
|
109
|
-
if self.context.password_strategy == "static":
|
|
109
|
+
if self.context.password_strategy == "static": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
110
110
|
# secret_store=ssm の場合は Secrets Manager に secret を作らない。
|
|
111
|
-
if self.context.secret_store != "sm":
|
|
111
|
+
if self.context.secret_store != "sm": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
112
112
|
return None
|
|
113
113
|
return self._static_secret["ARN"] if self._static_secret else None
|
|
114
114
|
if self.cluster and "MasterUserSecret" in self.cluster:
|
|
@@ -118,8 +118,8 @@ class Rds:
|
|
|
118
118
|
@property
|
|
119
119
|
def static_ssm_param_name(self) -> str | None:
|
|
120
120
|
"""static + secret_store=ssm のとき、認証情報を保存する SSM パラメータ名。"""
|
|
121
|
-
if self.context.password_strategy == "static" and (
|
|
122
|
-
self.context.secret_store == "ssm"
|
|
121
|
+
if self.context.password_strategy == "static" and ( # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
122
|
+
self.context.secret_store == "ssm" # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
123
123
|
):
|
|
124
124
|
return self.context.credentials_secret_name
|
|
125
125
|
return None
|
|
@@ -128,7 +128,7 @@ class Rds:
|
|
|
128
128
|
def master_user_secret_kms_key_id(self) -> str | None:
|
|
129
129
|
# static は既定の aws/secretsmanager キーで暗号化するため、
|
|
130
130
|
# secretsmanager:GetSecretValue のみで復号でき KMS の明示付与は不要。
|
|
131
|
-
if self.context.password_strategy == "static":
|
|
131
|
+
if self.context.password_strategy == "static": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
132
132
|
return None
|
|
133
133
|
if self.cluster and "MasterUserSecret" in self.cluster:
|
|
134
134
|
return self.cluster["MasterUserSecret"].get("KmsKeyId")
|
|
@@ -206,7 +206,7 @@ class Rds:
|
|
|
206
206
|
|
|
207
207
|
不一致なら status が REQUIRE_UPDATE を返し、update() が移行する。
|
|
208
208
|
"""
|
|
209
|
-
if self.context.password_strategy == "aws-managed":
|
|
209
|
+
if self.context.password_strategy == "aws-managed": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
210
210
|
return self._actual_is_managed()
|
|
211
211
|
# 望む = static
|
|
212
212
|
if self._actual_is_managed():
|
|
@@ -215,13 +215,16 @@ class Rds:
|
|
|
215
215
|
return self._store_has_credential(self.context.secret_store)
|
|
216
216
|
|
|
217
217
|
def _get_vpc_stack(self) -> VpcStack:
|
|
218
|
+
if not self.context.vpc:
|
|
219
|
+
raise RuntimeError("vpc context is not configured")
|
|
218
220
|
return VpcStack(self.context.vpc)
|
|
219
221
|
|
|
220
222
|
def _get_vpc_subnet_ids(self) -> list[str]:
|
|
221
223
|
vpc_stack = self._get_vpc_stack()
|
|
222
224
|
output = vpc_stack.output
|
|
223
225
|
export = vpc_stack.export
|
|
224
|
-
|
|
226
|
+
if not output:
|
|
227
|
+
raise RuntimeError("VPC stack output is not available")
|
|
225
228
|
prefix = export["private_subnet_"]
|
|
226
229
|
subnet_ids = []
|
|
227
230
|
for i in range(1, 20):
|
|
@@ -230,14 +233,16 @@ class Rds:
|
|
|
230
233
|
subnet_ids.append(output[key])
|
|
231
234
|
else:
|
|
232
235
|
break
|
|
233
|
-
|
|
236
|
+
if not subnet_ids:
|
|
237
|
+
raise RuntimeError("No private subnets found in VPC stack")
|
|
234
238
|
return subnet_ids
|
|
235
239
|
|
|
236
240
|
def _get_vpc_id(self) -> str:
|
|
237
241
|
vpc_stack = self._get_vpc_stack()
|
|
238
242
|
output = vpc_stack.output
|
|
239
243
|
export = vpc_stack.export
|
|
240
|
-
|
|
244
|
+
if not output:
|
|
245
|
+
raise RuntimeError("VPC stack output is not available")
|
|
241
246
|
return output[export["vpc_id"]]
|
|
242
247
|
|
|
243
248
|
def state_info(self):
|
|
@@ -260,7 +265,8 @@ class Rds:
|
|
|
260
265
|
def deploy_init(self):
|
|
261
266
|
if not self.context.managed:
|
|
262
267
|
return
|
|
263
|
-
|
|
268
|
+
if not self.context.vpc:
|
|
269
|
+
raise RuntimeError("vpc context is not configured")
|
|
264
270
|
vpc_stack = Vpc(self.context.vpc).stack
|
|
265
271
|
if not self.context.vpc.manage:
|
|
266
272
|
if vpc_stack.status == "NOEXIST":
|
|
@@ -272,6 +278,8 @@ class Rds:
|
|
|
272
278
|
|
|
273
279
|
def create(self):
|
|
274
280
|
# VPC スタックの完了を待つ
|
|
281
|
+
if not self.context.vpc:
|
|
282
|
+
raise RuntimeError("vpc context is not configured")
|
|
275
283
|
if self.context.vpc.manage:
|
|
276
284
|
Vpc(self.context.vpc).stack.wait_status("COMPLETED")
|
|
277
285
|
subnet_ids = self._get_vpc_subnet_ids()
|
|
@@ -305,7 +313,7 @@ class Rds:
|
|
|
305
313
|
)
|
|
306
314
|
sg_id = sg_res["GroupId"]
|
|
307
315
|
|
|
308
|
-
static = self.context.password_strategy == "static"
|
|
316
|
+
static = self.context.password_strategy == "static" # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
309
317
|
# static の場合、ここで設定した平文パスワードを後段で secret に保存する。
|
|
310
318
|
password: str | None = None
|
|
311
319
|
|
|
@@ -398,7 +406,8 @@ class Rds:
|
|
|
398
406
|
# この secret を MasterUserSecret 相当として Lambda へ渡すため、ローテーション
|
|
399
407
|
# 用 Lambda は付けない (= 自動ローテーションしない)。
|
|
400
408
|
if static:
|
|
401
|
-
|
|
409
|
+
if password is None:
|
|
410
|
+
raise RuntimeError("password must be set for static credentials")
|
|
402
411
|
self._store_static_credentials(password)
|
|
403
412
|
|
|
404
413
|
echo.success("Aurora cluster is now available.")
|
|
@@ -497,7 +506,7 @@ class Rds:
|
|
|
497
506
|
|
|
498
507
|
def _migrate_password(self) -> None:
|
|
499
508
|
"""クラスタを望む password_strategy / secret_store に合わせて移行する。"""
|
|
500
|
-
if self.context.password_strategy == "aws-managed":
|
|
509
|
+
if self.context.password_strategy == "aws-managed": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
501
510
|
self._migrate_to_managed()
|
|
502
511
|
else:
|
|
503
512
|
self._migrate_to_static()
|
|
@@ -533,7 +542,7 @@ class Rds:
|
|
|
533
542
|
echo.success("Master password is now pocket-managed (static).")
|
|
534
543
|
return
|
|
535
544
|
# クラスタは既に static。store 変更 (sm⇄ssm) を試みる (パスワード変更なし)。
|
|
536
|
-
other = "sm" if self.context.secret_store == "ssm" else "ssm"
|
|
545
|
+
other = "sm" if self.context.secret_store == "ssm" else "ssm" # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
537
546
|
existing = self._read_credential_from_store(other)
|
|
538
547
|
if existing is not None:
|
|
539
548
|
echo.log(
|
|
@@ -601,7 +610,7 @@ class Rds:
|
|
|
601
610
|
raise
|
|
602
611
|
|
|
603
612
|
# 5. static: pocket 所有の認証情報を store から削除
|
|
604
|
-
if self.context.password_strategy == "static":
|
|
613
|
+
if self.context.password_strategy == "static": # noqa: S105 戦略名/保存先種別であって secret 値ではない
|
|
605
614
|
self._delete_static_credentials()
|
|
606
615
|
|
|
607
616
|
def _delete_static_credentials(self) -> None:
|
|
@@ -53,7 +53,7 @@ class TiDbApi:
|
|
|
53
53
|
logger.info("%s %s" % (method, url))
|
|
54
54
|
if data:
|
|
55
55
|
logger.debug(json.dumps(data, indent=2))
|
|
56
|
-
res = requests.request(method, url, auth=self.auth, json=data)
|
|
56
|
+
res = requests.request(method, url, auth=self.auth, json=data, timeout=30)
|
|
57
57
|
logger.debug(res.status_code)
|
|
58
58
|
logger.debug(json.dumps(res.json(), indent=2))
|
|
59
59
|
if 200 <= res.status_code < 300:
|
|
@@ -226,7 +226,7 @@ class TiDb:
|
|
|
226
226
|
"public": {
|
|
227
227
|
"authorizedNetworks": [
|
|
228
228
|
{
|
|
229
|
-
"startIpAddress": "0.0.0.0",
|
|
229
|
+
"startIpAddress": "0.0.0.0", # noqa: S104 TiDB 許可ネットワークの設定値 (socket bind ではない)
|
|
230
230
|
"endIpAddress": "255.255.255.255",
|
|
231
231
|
"displayName": "Allow all",
|
|
232
232
|
}
|
|
@@ -34,19 +34,21 @@ class UpstashApi:
|
|
|
34
34
|
self.auth = (email, api_key)
|
|
35
35
|
|
|
36
36
|
def get(self, path: str) -> requests.Response:
|
|
37
|
-
res = requests.get(f"{self.base_url}/{path}", auth=self.auth)
|
|
37
|
+
res = requests.get(f"{self.base_url}/{path}", auth=self.auth, timeout=30)
|
|
38
38
|
if 200 <= res.status_code < 300:
|
|
39
39
|
return res
|
|
40
40
|
raise RuntimeError("Upstash API error: %s %s" % (res.status_code, res.text))
|
|
41
41
|
|
|
42
42
|
def post(self, path: str, data: dict) -> requests.Response:
|
|
43
|
-
res = requests.post(
|
|
43
|
+
res = requests.post(
|
|
44
|
+
f"{self.base_url}/{path}", auth=self.auth, json=data, timeout=30
|
|
45
|
+
)
|
|
44
46
|
if 200 <= res.status_code < 300:
|
|
45
47
|
return res
|
|
46
48
|
raise RuntimeError("Upstash API error: %s %s" % (res.status_code, res.text))
|
|
47
49
|
|
|
48
50
|
def delete(self, path: str) -> requests.Response:
|
|
49
|
-
res = requests.delete(f"{self.base_url}/{path}", auth=self.auth)
|
|
51
|
+
res = requests.delete(f"{self.base_url}/{path}", auth=self.auth, timeout=30)
|
|
50
52
|
if 200 <= res.status_code < 300:
|
|
51
53
|
return res
|
|
52
54
|
raise RuntimeError("Upstash API error: %s %s" % (res.status_code, res.text))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "magic-pocket-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "CLI and deploy tools for magic-pocket."
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"magic-pocket>=0.2.
|
|
7
|
+
"magic-pocket>=0.2.2",
|
|
8
8
|
"click>=8.1.7",
|
|
9
9
|
"jinja2>=3.1.3",
|
|
10
10
|
"python-on-whales>=0.68.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/__init__.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/codebuild.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/docker.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/resources/aws/builders/dockerignore.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/cloudformation/vpc.yaml
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/django-dotenv.env
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/django-settings.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/pocket.Dockerfile
RENAMED
|
File without changes
|
{magic_pocket_cli-0.2.0 → magic_pocket_cli-0.2.2}/pocket_cli/templates/init/pocket_simple.toml
RENAMED
|
File without changes
|