magic-pocket-cli 0.40.0__tar.gz → 0.41.1__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.40.0 → magic_pocket_cli-0.41.1}/PKG-INFO +2 -2
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/deploy_cli.py +102 -8
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/destroy_cli.py +1 -0
- magic_pocket_cli-0.41.1/pocket_cli/cli/permissions_cli.py +96 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/django_cli.py +69 -17
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/__init__.py +2 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/codebuild.py +20 -13
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/cloudformation.py +88 -39
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/iam_roles.py +293 -49
- magic_pocket_cli-0.41.1/pocket_cli/resources/aws/stage_roles.py +88 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/backup.py +5 -1
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/cloudfront.py +11 -2
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/container.py +1 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/dsql.py +1 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/rds.py +0 -10
- magic_pocket_cli-0.41.1/pocket_cli/templates/cloudformation/cf_function_cache_control.js +8 -0
- magic_pocket_cli-0.41.1/pocket_cli/templates/cloudformation/cf_function_deploy_hash_strip.js +19 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cloudfront.yaml +69 -28
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/container.yaml +10 -4
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pyproject.toml +2 -2
- magic_pocket_cli-0.40.0/pocket_cli/cli/permissions_cli.py +0 -46
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/.gitignore +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/__init__.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/__init__.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/aws_auth.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/backup_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/build_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/cleanup_deprecated_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/cloudfront_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/container_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/dsql_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/image_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/inbound_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/interaction.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/main_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/migrate_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/neon_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/rds_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/removed_flags.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/resource_helper.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/runtime_config_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/s3_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/status_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/store_url_helper.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/tidb_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/upstash_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/url_helper.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/vpc_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/cli/waf_cli.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/mediator.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/migrations.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/__init__.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/__init__.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/backup_common.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/backup_legacy.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/context_check.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/depot.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/docker.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/ecr.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/efs.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/lambdahandler.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/poll.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/s3_utils.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/stack_backed.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/state.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/cloudfront_acm.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/cloudfront_keys.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/cloudfront_waf.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/inbound.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/inbound_domain.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/inbound_template.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/neon.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/s3.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/sqs_alert.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/tidb.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/upstash.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/vpc.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cf_function_basic_auth_prelude.js +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cf_function_host_redirect_prelude.js +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/init/django-dotenv.env +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/init/django-settings.py +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
- {magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/templates/init/pocket_simple.toml +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: magic-pocket-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.41.1
|
|
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.
|
|
10
|
+
Requires-Dist: magic-pocket<0.42,>=0.41.1
|
|
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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import webbrowser
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ from pocket_cli.mediator import Mediator
|
|
|
12
13
|
from pocket_cli.resources.aws.builders.context_check import (
|
|
13
14
|
resummarize_world_read_warnings,
|
|
14
15
|
)
|
|
16
|
+
from pocket_cli.resources.aws.stage_roles import verify_stage_roles
|
|
15
17
|
from pocket_cli.resources.aws.state import StateStore, create_state_store
|
|
16
18
|
from pocket_cli.resources.backup import Backup
|
|
17
19
|
from pocket_cli.resources.cloudfront import CloudFront
|
|
@@ -112,15 +114,53 @@ def deploy_init_resources(context: Context, *, state_bucket: str = ""):
|
|
|
112
114
|
resource.deploy_init()
|
|
113
115
|
|
|
114
116
|
|
|
115
|
-
def deploy_frontend(
|
|
117
|
+
def deploy_frontend(
|
|
118
|
+
context: Context,
|
|
119
|
+
*,
|
|
120
|
+
skip_build: bool = False,
|
|
121
|
+
exclude: frozenset[tuple[str, str]] = frozenset(),
|
|
122
|
+
):
|
|
123
|
+
"""uploadable な route を upload する。
|
|
124
|
+
|
|
125
|
+
exclude は upload 済みの (cloudfront, route) で、対象から外す。
|
|
126
|
+
"""
|
|
116
127
|
for _name, cf_ctx in context.cloudfront.items():
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
routes = [
|
|
129
|
+
r for r in cf_ctx.uploadable_routes if (cf_ctx.name, r.name) not in exclude
|
|
130
|
+
]
|
|
131
|
+
if not routes:
|
|
119
132
|
continue
|
|
133
|
+
cf = CloudFront(cf_ctx)
|
|
120
134
|
if cf.status == "NOEXIST":
|
|
121
135
|
echo.warning("CloudFront '%s' が未作成です。スキップします。" % cf_ctx.name)
|
|
122
136
|
continue
|
|
123
|
-
cf.upload(skip_build=skip_build)
|
|
137
|
+
cf.upload(skip_build=skip_build, routes=routes)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def deploy_versioned_frontend(context: Context) -> frozenset[tuple[str, str]]:
|
|
141
|
+
"""versioning route を Lambda 更新より先に upload する。
|
|
142
|
+
|
|
143
|
+
Lambda が新しい版の URL を出し始めた後に upload すると、その間に読まれた
|
|
144
|
+
ファイルは upload 前の内容 (新規ファイルなら 403) が新しい URL のキャッシュに入り、
|
|
145
|
+
ブラウザに versioned_max_age の間残る (KN1676)。versioning route の URL は版ごとに
|
|
146
|
+
変わるので、先に upload しても稼働中の版の表示は壊れない。
|
|
147
|
+
|
|
148
|
+
SPA など versioning の無い route は対象外 (HTML が Lambda より先に切り替わる)。
|
|
149
|
+
CloudFront が未作成 (初回 deploy) の場合は upload せず、deploy_frontend に任せる。
|
|
150
|
+
戻り値は upload した (cloudfront, route) で、deploy_frontend の exclude に渡す。
|
|
151
|
+
"""
|
|
152
|
+
uploaded: set[tuple[str, str]] = set()
|
|
153
|
+
for _name, cf_ctx in context.cloudfront.items():
|
|
154
|
+
routes = [r for r in cf_ctx.uploadable_routes if r.versioning]
|
|
155
|
+
if not routes:
|
|
156
|
+
continue
|
|
157
|
+
cf = CloudFront(cf_ctx)
|
|
158
|
+
if cf.status == "NOEXIST":
|
|
159
|
+
continue
|
|
160
|
+
echo.log("versioning route を Lambda 更新より先に upload します...")
|
|
161
|
+
cf.upload(routes=routes)
|
|
162
|
+
uploaded.update((cf_ctx.name, r.name) for r in routes)
|
|
163
|
+
return frozenset(uploaded)
|
|
124
164
|
|
|
125
165
|
|
|
126
166
|
def upload_managed_assets(context: Context):
|
|
@@ -215,11 +255,20 @@ def build_image(context: Context, *, tag: str) -> list[str]:
|
|
|
215
255
|
return targets
|
|
216
256
|
|
|
217
257
|
|
|
218
|
-
def _deploy_pipeline(
|
|
258
|
+
def _deploy_pipeline(
|
|
259
|
+
context: Context,
|
|
260
|
+
*,
|
|
261
|
+
openpath=None,
|
|
262
|
+
skip_frontend=False,
|
|
263
|
+
before_lambda_update: Callable[[], None] | None = None,
|
|
264
|
+
):
|
|
219
265
|
"""deploy / promote 共通のパイプライン本体。
|
|
220
266
|
|
|
221
267
|
promote 時は各 container の promote_commit_hash が設定済みで、
|
|
222
268
|
deploy_init 内の image build が retag に置き換わる以外は deploy と同一。
|
|
269
|
+
|
|
270
|
+
before_lambda_update は image build の後・Lambda 更新 (deploy_resources) の前に
|
|
271
|
+
呼ばれる。`pocket django deploy` が static を先に upload するのに使う。
|
|
223
272
|
"""
|
|
224
273
|
# DEPLOY_HASH の解決結果を deploy 時に 1 回可視化する (env 伝播漏れで
|
|
225
274
|
# 黙って git short hash に落ちる footgun の早期発見用)。
|
|
@@ -230,12 +279,20 @@ def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
|
|
|
230
279
|
deploy_hash_message = deploy_hash_report(context)
|
|
231
280
|
if deploy_hash_message:
|
|
232
281
|
echo.info(deploy_hash_message)
|
|
282
|
+
# [iam] external_roles: 事前に作られた role を、何かを変更する前に検査する
|
|
283
|
+
verify_stage_roles(context)
|
|
233
284
|
# CodeBuildがソースアップロードにstate bucketを必要とするため、先に作成
|
|
234
285
|
state_store = _create_state_store(context)
|
|
235
286
|
state_store.ensure_bucket()
|
|
236
287
|
state_bucket = state_store.bucket_name
|
|
237
288
|
check_removed_inbound(context, state_store)
|
|
238
289
|
deploy_init_resources(context, state_bucket=state_bucket)
|
|
290
|
+
# static は Lambda が新しい版の URL を出し始める前に upload する (KN1676)
|
|
291
|
+
uploaded_routes: frozenset[tuple[str, str]] = frozenset()
|
|
292
|
+
if not skip_frontend:
|
|
293
|
+
uploaded_routes = deploy_versioned_frontend(context)
|
|
294
|
+
if before_lambda_update is not None:
|
|
295
|
+
before_lambda_update()
|
|
239
296
|
deploy_resources(context, state_bucket=state_bucket)
|
|
240
297
|
cleanup_unused_inbound_domains(context, state_store)
|
|
241
298
|
# リリース跨ぎ移行の掃除フェーズ (旧配置の削除は deploy 成功後にしか
|
|
@@ -243,7 +300,7 @@ def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
|
|
|
243
300
|
migrations.run_deploy_cleanup(context)
|
|
244
301
|
upload_managed_assets(context)
|
|
245
302
|
if not skip_frontend:
|
|
246
|
-
deploy_frontend(context)
|
|
303
|
+
deploy_frontend(context, exclude=uploaded_routes)
|
|
247
304
|
# build 時の other-read 警告はログに埋もれて気付けないため最後に再掲する
|
|
248
305
|
resummarize_world_read_warnings()
|
|
249
306
|
# DLQ アラートの email 購読が未確認のままだと通知が届かないため、
|
|
@@ -269,12 +326,29 @@ def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
|
|
|
269
326
|
)
|
|
270
327
|
@removed_skip_check_existing
|
|
271
328
|
def deploy(stage: str, openpath, skip_frontend, yes):
|
|
329
|
+
run_deploy(stage, openpath=openpath, skip_frontend=skip_frontend, yes=yes)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def run_deploy(
|
|
333
|
+
stage: str,
|
|
334
|
+
*,
|
|
335
|
+
openpath=None,
|
|
336
|
+
skip_frontend: bool = False,
|
|
337
|
+
yes: bool = False,
|
|
338
|
+
before_lambda_update: Callable[[], None] | None = None,
|
|
339
|
+
):
|
|
340
|
+
"""`pocket deploy` の本体。`pocket django deploy` からも呼ばれる。"""
|
|
272
341
|
from pocket_cli.cli.aws_auth import check_aws_credentials
|
|
273
342
|
|
|
274
343
|
interaction.set_assume_yes(yes)
|
|
275
344
|
check_aws_credentials()
|
|
276
345
|
context = Context.from_toml(stage=stage)
|
|
277
|
-
_deploy_pipeline(
|
|
346
|
+
_deploy_pipeline(
|
|
347
|
+
context,
|
|
348
|
+
openpath=openpath,
|
|
349
|
+
skip_frontend=skip_frontend,
|
|
350
|
+
before_lambda_update=before_lambda_update,
|
|
351
|
+
)
|
|
278
352
|
|
|
279
353
|
|
|
280
354
|
@click.command()
|
|
@@ -292,6 +366,21 @@ def promote(stage: str, commit_hash, openpath, skip_frontend, yes):
|
|
|
292
366
|
`pocket build` で push した image に :<stage> タグを移し、
|
|
293
367
|
インフラ/Lambda を更新する。image build は行わない (build once の昇格)。
|
|
294
368
|
"""
|
|
369
|
+
run_promote(
|
|
370
|
+
stage, commit_hash, openpath=openpath, skip_frontend=skip_frontend, yes=yes
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def run_promote(
|
|
375
|
+
stage: str,
|
|
376
|
+
commit_hash: str,
|
|
377
|
+
*,
|
|
378
|
+
openpath=None,
|
|
379
|
+
skip_frontend: bool = False,
|
|
380
|
+
yes: bool = False,
|
|
381
|
+
before_lambda_update: Callable[[], None] | None = None,
|
|
382
|
+
):
|
|
383
|
+
"""`pocket promote` の本体。`pocket django promote` からも呼ばれる。"""
|
|
295
384
|
from pocket_cli.cli.aws_auth import check_aws_credentials
|
|
296
385
|
|
|
297
386
|
interaction.set_assume_yes(yes)
|
|
@@ -301,7 +390,12 @@ def promote(stage: str, commit_hash, openpath, skip_frontend, yes):
|
|
|
301
390
|
raise click.ClickException("container がこの stage に設定されていません。")
|
|
302
391
|
for c_ctx in context.container.values():
|
|
303
392
|
c_ctx.promote_commit_hash = commit_hash
|
|
304
|
-
_deploy_pipeline(
|
|
393
|
+
_deploy_pipeline(
|
|
394
|
+
context,
|
|
395
|
+
openpath=openpath,
|
|
396
|
+
skip_frontend=skip_frontend,
|
|
397
|
+
before_lambda_update=before_lambda_update,
|
|
398
|
+
)
|
|
305
399
|
|
|
306
400
|
|
|
307
401
|
def _get_deploy_url(context: Context) -> str | None:
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""`pocket permissions` サブコマンド群。
|
|
2
|
+
|
|
3
|
+
- `list`: `pocket.toml` の構成から deploy に必要な AWS IAM Action を出力する。
|
|
4
|
+
外部の IAM Role プロビジョニング処理が GitHub Actions デプロイ用 IAM Role を
|
|
5
|
+
作る際の inline policy 生成に使用することを想定。
|
|
6
|
+
- `roles`: pocket の Lambda / scheduler / CodeBuild / AWS Backup が使う IAM role
|
|
7
|
+
の定義を出力する。`[iam] external_roles = true` で role を事前に作る用途。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
import boto3
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from pocket.context import Context
|
|
18
|
+
from pocket.permissions import compute_actions
|
|
19
|
+
from pocket.settings import Settings
|
|
20
|
+
from pocket_cli.resources.aws.stage_roles import stage_role_specs
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group()
|
|
24
|
+
def permissions():
|
|
25
|
+
"""IAM 権限関連のサブコマンド。"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@permissions.command("list")
|
|
29
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--format",
|
|
32
|
+
"format_",
|
|
33
|
+
type=click.Choice(["text", "json"]),
|
|
34
|
+
default="text",
|
|
35
|
+
show_default=True,
|
|
36
|
+
help='出力形式。text: 1 行 1 Action / json: {"actions": [...]}',
|
|
37
|
+
)
|
|
38
|
+
def list_(stage: str, format_: str):
|
|
39
|
+
"""pocket.toml から必要な AWS Action 一覧を出力する。
|
|
40
|
+
|
|
41
|
+
docs/permissions/aws.md のテーブルに基づき、`[cloudfront]` / `[rds]` /
|
|
42
|
+
`[ses]` などの設定有無に応じて必要 Action を組み立てる。粒度は
|
|
43
|
+
ワイルドカード中心 (`cloudformation:*` 等)。
|
|
44
|
+
"""
|
|
45
|
+
settings = Settings.from_toml(stage=stage)
|
|
46
|
+
actions = compute_actions(settings)
|
|
47
|
+
if format_ == "json":
|
|
48
|
+
click.echo(json.dumps({"actions": actions}, indent=2))
|
|
49
|
+
else:
|
|
50
|
+
for action in actions:
|
|
51
|
+
click.echo(action)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@permissions.command("roles")
|
|
55
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
56
|
+
@click.option(
|
|
57
|
+
"--account-id",
|
|
58
|
+
help="role の ARN と policy に埋め込む AWS account ID。省略時は"
|
|
59
|
+
" sts:GetCallerIdentity で取得する。",
|
|
60
|
+
)
|
|
61
|
+
def roles(stage: str, account_id: str | None):
|
|
62
|
+
"""pocket が使う IAM role の定義を JSON で出力する。
|
|
63
|
+
|
|
64
|
+
role ごとに名前・ARN・信頼ポリシー・managed policy・inline policy を出す。
|
|
65
|
+
policy 中の CloudFormation 変数 (${AWS::Region} 等) は展開済みなので、
|
|
66
|
+
そのまま IAM の CreateRole / PutRolePolicy に渡せる。`[iam] external_roles
|
|
67
|
+
= true` の stage では、deploy がこの定義と実際の role を突き合わせる。
|
|
68
|
+
"""
|
|
69
|
+
context = Context.from_toml(stage=stage)
|
|
70
|
+
if not context.general:
|
|
71
|
+
raise click.ClickException("[general] が見つかりません")
|
|
72
|
+
region = context.general.region
|
|
73
|
+
account = (
|
|
74
|
+
account_id
|
|
75
|
+
or (boto3.client("sts", region_name=region).get_caller_identity()["Account"])
|
|
76
|
+
)
|
|
77
|
+
specs = stage_role_specs(context, account_id=account)
|
|
78
|
+
output = {
|
|
79
|
+
"stage": stage,
|
|
80
|
+
"region": region,
|
|
81
|
+
"account_id": account,
|
|
82
|
+
"external_roles": context.external_roles,
|
|
83
|
+
"roles": [
|
|
84
|
+
{
|
|
85
|
+
"name": spec.name,
|
|
86
|
+
"arn": f"arn:aws:iam::{account}:role/{spec.name}",
|
|
87
|
+
"assume_role_policy": spec.assume_role_policy,
|
|
88
|
+
"managed_policy_arns": spec.managed_policy_arns,
|
|
89
|
+
"inline_policies": spec.resolved_inline_policies(
|
|
90
|
+
region=region, account_id=account
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
for spec in specs
|
|
94
|
+
],
|
|
95
|
+
}
|
|
96
|
+
click.echo(json.dumps(output, indent=2, ensure_ascii=False))
|
|
@@ -16,8 +16,10 @@ from pocket.django.utils import get_storages, resolve_django_container
|
|
|
16
16
|
from pocket.utils import echo
|
|
17
17
|
from pocket_cli.cli import interaction
|
|
18
18
|
from pocket_cli.cli.build_cli import build
|
|
19
|
+
from pocket_cli.cli.deploy_cli import run_deploy, run_promote
|
|
19
20
|
from pocket_cli.cli.removed_flags import removed_skip_check_existing
|
|
20
21
|
from pocket_cli.resources.container import Container
|
|
22
|
+
from pocket_cli.resources.s3 import S3
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@click.group()
|
|
@@ -92,23 +94,65 @@ def _update_dotenv(jinja2_env: Environment):
|
|
|
92
94
|
)
|
|
93
95
|
@removed_skip_check_existing
|
|
94
96
|
def deploy(stage: str, openpath, yes, skip_migrate):
|
|
95
|
-
from pocket_cli.cli.deploy_cli import deploy as pocket_deploy
|
|
96
|
-
|
|
97
97
|
# pocket deploy を実行(インフラ + SPA フロントエンド)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
stage=stage,
|
|
98
|
+
static_handled: list[bool] = []
|
|
99
|
+
run_deploy(
|
|
100
|
+
stage,
|
|
102
101
|
openpath=None,
|
|
103
102
|
skip_frontend=False,
|
|
104
103
|
yes=yes,
|
|
104
|
+
before_lambda_update=lambda: static_handled.append(
|
|
105
|
+
_deploystatic_before_lambda_update(stage)
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
_django_post_deploy(
|
|
109
|
+
stage,
|
|
110
|
+
yes=yes,
|
|
111
|
+
openpath=openpath,
|
|
112
|
+
skip_migrate=skip_migrate,
|
|
113
|
+
static_handled=any(static_handled),
|
|
105
114
|
)
|
|
106
|
-
_django_post_deploy(stage, yes=yes, openpath=openpath, skip_migrate=skip_migrate)
|
|
107
115
|
|
|
108
116
|
|
|
109
|
-
def
|
|
117
|
+
def _deploystatic_before_lambda_update(stage: str) -> bool:
|
|
118
|
+
"""static を Lambda 更新より先に upload する。deploystatic を済ませたら True。
|
|
119
|
+
|
|
120
|
+
Lambda が新しい版の URL を出し始めた後に upload すると、その間に読まれた
|
|
121
|
+
ファイルは upload 前の内容が新しい URL のキャッシュに入り、ブラウザに長期間残る
|
|
122
|
+
(KN1676)。collectstatic の失敗も Lambda を更新する前に分かる。
|
|
123
|
+
|
|
124
|
+
bucket が未作成 (初回 deploy) の場合と、staticfiles が S3 でない場合は False を
|
|
125
|
+
返し、従来どおり deploy 後の `_django_post_deploy` に任せる。確認に No と
|
|
126
|
+
答えた場合も True を返す (deploy 後にもう一度聞かない)。
|
|
127
|
+
"""
|
|
128
|
+
context = Context.from_toml(stage=stage)
|
|
129
|
+
if _staticfiles_publish_mode(context) == "command":
|
|
130
|
+
return False
|
|
131
|
+
c = resolve_django_container(context)
|
|
132
|
+
storage = c.django.storages.get("staticfiles") if c and c.django else None
|
|
133
|
+
if not storage or storage.store != "s3":
|
|
134
|
+
return False
|
|
135
|
+
if not context.s3 or not S3(context.s3).exists():
|
|
136
|
+
return False
|
|
137
|
+
if interaction.confirm("deploystatic?", default=True):
|
|
138
|
+
collectstatic_locally(stage, link=_staticfiles_link(context))
|
|
139
|
+
upload_collected_staticfiles(stage)
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _django_post_deploy(
|
|
144
|
+
stage: str,
|
|
145
|
+
*,
|
|
146
|
+
yes: bool,
|
|
147
|
+
openpath,
|
|
148
|
+
skip_migrate: bool = False,
|
|
149
|
+
static_handled: bool = False,
|
|
150
|
+
):
|
|
110
151
|
"""deploy / promote 共通の Django 固有後処理 (collectstatic + migrate + URL)。
|
|
111
152
|
|
|
153
|
+
static_handled=True は、Lambda 更新の前に deploystatic を済ませた
|
|
154
|
+
(`_deploystatic_before_lambda_update`) ことを表し、ここでは繰り返さない。
|
|
155
|
+
|
|
112
156
|
skip_migrate=True なら migrate は確認ごと省く。`-y` は「聞かれたことに全部
|
|
113
157
|
yes」の意味を保ちたいので、非対話で migrate を外す手段はフラグ側に置く
|
|
114
158
|
(DB が到達不能でもインフラ更新だけ通したい、という状況が実在する)。
|
|
@@ -116,7 +160,9 @@ def _django_post_deploy(stage: str, *, yes: bool, openpath, skip_migrate: bool =
|
|
|
116
160
|
if yes:
|
|
117
161
|
interaction.set_assume_yes(True)
|
|
118
162
|
context = Context.from_toml(stage=stage)
|
|
119
|
-
if
|
|
163
|
+
if static_handled:
|
|
164
|
+
pass
|
|
165
|
+
elif _staticfiles_publish_mode(context) == "command":
|
|
120
166
|
echo.info(
|
|
121
167
|
'staticfiles is publish = "command": skipping deploystatic. '
|
|
122
168
|
"Publish with `pocket django deploystatic --stage %s`." % stage
|
|
@@ -163,18 +209,24 @@ def promote(stage: str, commit_hash, openpath, yes, skip_migrate):
|
|
|
163
209
|
を更新する。image build は行わない (build once の昇格)。静的アセットは deploy 時
|
|
164
210
|
と同様にローカルでビルドして upload する。
|
|
165
211
|
"""
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
pocket_promote,
|
|
171
|
-
stage=stage,
|
|
172
|
-
commit_hash=commit_hash,
|
|
212
|
+
static_handled: list[bool] = []
|
|
213
|
+
run_promote(
|
|
214
|
+
stage,
|
|
215
|
+
commit_hash,
|
|
173
216
|
openpath=None,
|
|
174
217
|
skip_frontend=False,
|
|
175
218
|
yes=yes,
|
|
219
|
+
before_lambda_update=lambda: static_handled.append(
|
|
220
|
+
_deploystatic_before_lambda_update(stage)
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
_django_post_deploy(
|
|
224
|
+
stage,
|
|
225
|
+
yes=yes,
|
|
226
|
+
openpath=openpath,
|
|
227
|
+
skip_migrate=skip_migrate,
|
|
228
|
+
static_handled=any(static_handled),
|
|
176
229
|
)
|
|
177
|
-
_django_post_deploy(stage, yes=yes, openpath=openpath, skip_migrate=skip_migrate)
|
|
178
230
|
|
|
179
231
|
|
|
180
232
|
# 旧コマンドは同じ Click command の別名として維持する。
|
{magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/__init__.py
RENAMED
|
@@ -27,6 +27,7 @@ def create_builder(
|
|
|
27
27
|
resource_prefix: str,
|
|
28
28
|
state_bucket: str,
|
|
29
29
|
permissions_boundary: str | None = None,
|
|
30
|
+
external_roles: bool = False,
|
|
30
31
|
) -> Builder:
|
|
31
32
|
backend = build_context.backend
|
|
32
33
|
|
|
@@ -44,6 +45,7 @@ def create_builder(
|
|
|
44
45
|
state_bucket=state_bucket,
|
|
45
46
|
compute_type=build_context.compute_type,
|
|
46
47
|
permissions_boundary=permissions_boundary,
|
|
48
|
+
external_roles=external_roles,
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
if backend == "depot":
|
{magic_pocket_cli-0.40.0 → magic_pocket_cli-0.41.1}/pocket_cli/resources/aws/builders/codebuild.py
RENAMED
|
@@ -50,6 +50,7 @@ class CodeBuildBuilder:
|
|
|
50
50
|
state_bucket: str,
|
|
51
51
|
compute_type: str = "BUILD_GENERAL1_MEDIUM",
|
|
52
52
|
permissions_boundary: str | None = None,
|
|
53
|
+
external_roles: bool = False,
|
|
53
54
|
) -> None:
|
|
54
55
|
self.region = region
|
|
55
56
|
self.resource_prefix = resource_prefix
|
|
@@ -58,6 +59,8 @@ class CodeBuildBuilder:
|
|
|
58
59
|
self.permissions_boundary = (
|
|
59
60
|
os.environ.get("CODEBUILD_PERMISSIONS_BOUNDARY") or permissions_boundary
|
|
60
61
|
)
|
|
62
|
+
# [iam] external_roles。role は利用者が事前に作り、pocket は作成も削除もしない
|
|
63
|
+
self.external_roles = external_roles
|
|
61
64
|
|
|
62
65
|
self.codebuild = boto3.client("codebuild", region_name=region)
|
|
63
66
|
self.iam = boto3.client("iam", region_name=region)
|
|
@@ -91,7 +94,8 @@ class CodeBuildBuilder:
|
|
|
91
94
|
role_arn = self._ensure_role(account_id)
|
|
92
95
|
self._ensure_project(platform, role_arn)
|
|
93
96
|
# project の serviceRole を新ロールへ付け替えた後なので、旧名ロールは孤児
|
|
94
|
-
self.
|
|
97
|
+
if not self.external_roles:
|
|
98
|
+
self._delete_role(self._legacy_role_name)
|
|
95
99
|
self._upload_source(dockerfile_path)
|
|
96
100
|
|
|
97
101
|
build_id = self._start_build(
|
|
@@ -106,26 +110,29 @@ class CodeBuildBuilder:
|
|
|
106
110
|
print("CodeBuild ビルド完了")
|
|
107
111
|
|
|
108
112
|
def delete(self) -> None:
|
|
109
|
-
"""CodeBuildプロジェクトとIAMロールを削除"""
|
|
113
|
+
"""CodeBuildプロジェクトとIAMロールを削除 (external_roles の role は残す)"""
|
|
110
114
|
self._delete_project()
|
|
115
|
+
if self.external_roles:
|
|
116
|
+
return
|
|
111
117
|
self._delete_role(self._role_name)
|
|
112
118
|
self._delete_role(self._legacy_role_name)
|
|
113
119
|
|
|
114
120
|
# --- IAM ロール ---
|
|
115
121
|
|
|
116
|
-
def
|
|
117
|
-
return iam_roles.
|
|
118
|
-
self.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
permissions_boundary=self.permissions_boundary,
|
|
126
|
-
),
|
|
122
|
+
def role_spec(self, account_id: str) -> iam_roles.RoleSpec:
|
|
123
|
+
return iam_roles.codebuild_role(
|
|
124
|
+
name=self._role_name,
|
|
125
|
+
region=self.region,
|
|
126
|
+
account_id=account_id,
|
|
127
|
+
state_bucket=self.state_bucket,
|
|
128
|
+
project_name=self._project_name,
|
|
129
|
+
permissions_boundary=self.permissions_boundary,
|
|
130
|
+
external=self.external_roles,
|
|
127
131
|
)
|
|
128
132
|
|
|
133
|
+
def _ensure_role(self, account_id: str) -> str:
|
|
134
|
+
return iam_roles.ensure_role(self.iam, self.role_spec(account_id))
|
|
135
|
+
|
|
129
136
|
def _delete_role(self, role_name: str) -> None:
|
|
130
137
|
iam_roles.delete_role(self.iam, role_name)
|
|
131
138
|
|