magic-pocket-cli 0.31.2__tar.gz → 0.32.0__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.
Files changed (80) hide show
  1. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/.gitignore +3 -0
  2. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/PKG-INFO +2 -2
  3. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/aws_auth.py +29 -5
  4. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/deploy_cli.py +5 -0
  5. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/main_cli.py +11 -1
  6. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/django_cli.py +26 -7
  7. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/context_check.py +41 -3
  8. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/lambdahandler.py +12 -1
  9. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pyproject.toml +2 -2
  10. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/__init__.py +0 -0
  11. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/__init__.py +0 -0
  12. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/backup_cli.py +0 -0
  13. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/cloudfront_cli.py +0 -0
  14. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
  15. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
  16. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/container_cli.py +0 -0
  17. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/destroy_cli.py +0 -0
  18. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/dsql_cli.py +0 -0
  19. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/image_cli.py +0 -0
  20. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/interaction.py +0 -0
  21. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/migrate_cli.py +0 -0
  22. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/neon_cli.py +0 -0
  23. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/permissions_cli.py +0 -0
  24. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/rds_cli.py +0 -0
  25. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/removed_flags.py +0 -0
  26. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/resource_helper.py +0 -0
  27. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/runtime_config_cli.py +0 -0
  28. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/s3_cli.py +0 -0
  29. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/status_cli.py +0 -0
  30. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/store_url_helper.py +0 -0
  31. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/tidb_cli.py +0 -0
  32. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/upstash_cli.py +0 -0
  33. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/url_helper.py +0 -0
  34. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/vpc_cli.py +0 -0
  35. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/cli/waf_cli.py +0 -0
  36. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/mediator.py +0 -0
  37. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/migrations.py +0 -0
  38. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/__init__.py +0 -0
  39. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/__init__.py +0 -0
  40. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/backup_common.py +0 -0
  41. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/__init__.py +0 -0
  42. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/codebuild.py +0 -0
  43. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/depot.py +0 -0
  44. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/docker.py +0 -0
  45. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
  46. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/cloudformation.py +0 -0
  47. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/ecr.py +0 -0
  48. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/efs.py +0 -0
  49. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/poll.py +0 -0
  50. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/s3_utils.py +0 -0
  51. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/stack_backed.py +0 -0
  52. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/aws/state.py +0 -0
  53. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/backup.py +0 -0
  54. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/cloudfront.py +0 -0
  55. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/cloudfront_acm.py +0 -0
  56. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/cloudfront_keys.py +0 -0
  57. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/cloudfront_waf.py +0 -0
  58. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/container.py +0 -0
  59. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/dsql.py +0 -0
  60. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/neon.py +0 -0
  61. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/rds.py +0 -0
  62. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/s3.py +0 -0
  63. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/tidb.py +0 -0
  64. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/upstash.py +0 -0
  65. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/resources/vpc.py +0 -0
  66. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
  67. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cf_function_basic_auth_prelude.js +0 -0
  68. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cf_function_host_redirect_prelude.js +0 -0
  69. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
  70. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
  71. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cloudfront.yaml +0 -0
  72. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
  73. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
  74. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
  75. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/container.yaml +0 -0
  76. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
  77. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/init/django-dotenv.env +0 -0
  78. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/init/django-settings.py +0 -0
  79. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
  80. {magic_pocket_cli-0.31.2 → magic_pocket_cli-0.32.0}/pocket_cli/templates/init/pocket_simple.toml +0 -0
@@ -43,3 +43,6 @@ forge.local.toml
43
43
  /example-tidb/pocket.runtime.toml
44
44
  /example-tidb/pocket_cache/
45
45
  /example-tidb/staticfiles/
46
+
47
+ # 参考用に置いた他プロジェクト (公開 repo に混入させない)
48
+ /sample/
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: magic-pocket-cli
3
- Version: 0.31.2
3
+ Version: 0.32.0
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.32,>=0.31.2
10
+ Requires-Dist: magic-pocket<0.33,>=0.32.0
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
@@ -2,24 +2,48 @@ from __future__ import annotations
2
2
 
3
3
  import boto3
4
4
  from botocore.exceptions import (
5
+ BotoCoreError,
5
6
  ClientError,
6
7
  NoCredentialsError,
8
+ ProfileNotFound,
9
+ SSOTokenLoadError,
7
10
  TokenRetrievalError,
11
+ UnauthorizedSSOTokenError,
8
12
  )
9
13
 
10
14
  from pocket.utils import echo
11
15
 
16
+ # 認証情報が無効なときに botocore が送出する例外。CLI 層 (PocketCLI.invoke) が
17
+ # これを捕捉して traceback でなく認証ガイドに変換する (どのサブコマンドでも
18
+ # boto3 呼び出しの途中で送出されうるため、precheck では網羅できない)。
19
+ CREDENTIAL_EXCEPTIONS = (
20
+ TokenRetrievalError,
21
+ UnauthorizedSSOTokenError,
22
+ SSOTokenLoadError,
23
+ NoCredentialsError,
24
+ ProfileNotFound,
25
+ )
26
+
27
+
28
+ def print_credential_guide(e: BotoCoreError) -> None:
29
+ """認証系例外を 1 行の原因 + 認証手順の案内に変換して表示する"""
30
+ if isinstance(e, (TokenRetrievalError, UnauthorizedSSOTokenError)):
31
+ _print_auth_guide("SSO トークンの有効期限が切れています。")
32
+ elif isinstance(e, SSOTokenLoadError):
33
+ _print_auth_guide("SSO セッションが見つかりません (未ログイン)。")
34
+ elif isinstance(e, ProfileNotFound):
35
+ _print_auth_guide(str(e))
36
+ else:
37
+ _print_auth_guide("AWS 認証情報が見つかりません。")
38
+
12
39
 
13
40
  def check_aws_credentials() -> None:
14
41
  """AWS 認証情報が有効か確認し、無効ならガイドを表示して終了する"""
15
42
  try:
16
43
  sts = boto3.client("sts")
17
44
  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 認証情報が見つかりません。")
45
+ except CREDENTIAL_EXCEPTIONS as e:
46
+ print_credential_guide(e)
23
47
  raise SystemExit(1) from None
24
48
  except ClientError as e:
25
49
  if e.response["Error"]["Code"] in (
@@ -9,6 +9,9 @@ from pocket_cli import migrations
9
9
  from pocket_cli.cli import interaction
10
10
  from pocket_cli.cli.removed_flags import removed_skip_check_existing
11
11
  from pocket_cli.mediator import Mediator
12
+ from pocket_cli.resources.aws.builders.context_check import (
13
+ resummarize_world_read_warnings,
14
+ )
12
15
  from pocket_cli.resources.aws.state import StateStore, create_state_store
13
16
  from pocket_cli.resources.backup import Backup
14
17
  from pocket_cli.resources.cloudfront import CloudFront
@@ -220,6 +223,8 @@ def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
220
223
  upload_managed_assets(context)
221
224
  if not skip_frontend:
222
225
  deploy_frontend(context)
226
+ # build 時の other-read 警告はログに埋もれて気付けないため最後に再掲する
227
+ resummarize_world_read_warnings()
223
228
  # デプロイ完了後の URL 表示
224
229
  url = _get_deploy_url(context)
225
230
  if url:
@@ -1,3 +1,5 @@
1
+ import logging
2
+
1
3
  import click
2
4
 
3
5
  from pocket import __version__
@@ -24,6 +26,7 @@ from pocket_cli.cli import (
24
26
  vpc_cli,
25
27
  waf_cli,
26
28
  )
29
+ from pocket_cli.cli.aws_auth import CREDENTIAL_EXCEPTIONS, print_credential_guide
27
30
 
28
31
 
29
32
  class PocketCLI(click.Group):
@@ -33,11 +36,18 @@ class PocketCLI(click.Group):
33
36
  except ValueError as e:
34
37
  click.echo(f"エラー: {e}", err=True)
35
38
  ctx.exit(1)
39
+ except CREDENTIAL_EXCEPTIONS as e:
40
+ # SSO 失効等で botocore の traceback が 100 行流れると「login し直せば
41
+ # よい」という結論に辿り着けない。原因 1 行 + 認証手順に変換する
42
+ print_credential_guide(e)
43
+ ctx.exit(1)
36
44
 
37
45
 
38
46
  @click.group(cls=PocketCLI)
39
47
  def main():
40
- pass
48
+ # SSO token の refresh 失敗時に botocore.tokens が WARNING + traceback を
49
+ # 出すのを抑制する (失敗自体は上の CREDENTIAL_EXCEPTIONS 捕捉で案内される)
50
+ logging.getLogger("botocore.tokens").setLevel(logging.ERROR)
41
51
 
42
52
 
43
53
  @main.command()
@@ -83,8 +83,14 @@ def _update_dotenv(jinja2_env):
83
83
  @click.option(
84
84
  "--yes", "-y", is_flag=True, default=False, help="確認プロンプトをスキップ"
85
85
  )
86
+ @click.option(
87
+ "--skip-migrate",
88
+ is_flag=True,
89
+ default=False,
90
+ help="migrate を実行しない (確認も出さない)。-y と併用可",
91
+ )
86
92
  @removed_skip_check_existing
87
- def deploy(stage: str, openpath, yes):
93
+ def deploy(stage: str, openpath, yes, skip_migrate):
88
94
  from pocket_cli.cli.deploy_cli import deploy as pocket_deploy
89
95
 
90
96
  # pocket deploy を実行(インフラ + SPA フロントエンド)
@@ -96,11 +102,16 @@ def deploy(stage: str, openpath, yes):
96
102
  skip_frontend=False,
97
103
  yes=yes,
98
104
  )
99
- _django_post_deploy(stage, yes=yes, openpath=openpath)
105
+ _django_post_deploy(stage, yes=yes, openpath=openpath, skip_migrate=skip_migrate)
106
+
100
107
 
108
+ def _django_post_deploy(stage: str, *, yes: bool, openpath, skip_migrate: bool = False):
109
+ """deploy / promote 共通の Django 固有後処理 (collectstatic + migrate + URL)。
101
110
 
102
- def _django_post_deploy(stage: str, *, yes: bool, openpath):
103
- """deploy / promote 共通の Django 固有後処理 (collectstatic + migrate + URL)。"""
111
+ skip_migrate=True なら migrate は確認ごと省く。`-y` は「聞かれたことに全部
112
+ yes」の意味を保ちたいので、非対話で migrate を外す手段はフラグ側に置く
113
+ (DB が到達不能でもインフラ更新だけ通したい、という状況が実在する)。
114
+ """
104
115
  if yes:
105
116
  interaction.set_assume_yes(True)
106
117
  context = Context.from_toml(stage=stage)
@@ -112,7 +123,9 @@ def _django_post_deploy(stage: str, *, yes: bool, openpath):
112
123
  elif interaction.confirm("deploystatic?", default=True):
113
124
  collectstatic_locally(stage, link=_staticfiles_link(context))
114
125
  upload_collected_staticfiles(stage)
115
- if interaction.confirm("migrate?", default=True):
126
+ if skip_migrate:
127
+ echo.info("--skip-migrate: migrate をスキップしました。")
128
+ elif interaction.confirm("migrate?", default=True):
116
129
  # handler 解決は migrate 実行が確定してから行う (management handler
117
130
  # 未設定 stage で deploy 本体成功後に例外になり URL 表示へ到達しない
118
131
  # のを防ぐ)
@@ -135,8 +148,14 @@ def _django_post_deploy(stage: str, *, yes: bool, openpath):
135
148
  @click.option(
136
149
  "--yes", "-y", is_flag=True, default=False, help="確認プロンプトをスキップ"
137
150
  )
151
+ @click.option(
152
+ "--skip-migrate",
153
+ is_flag=True,
154
+ default=False,
155
+ help="migrate を実行しない (確認も出さない)。-y と併用可",
156
+ )
138
157
  @removed_skip_check_existing
139
- def promote(stage: str, commit_hash, openpath, yes):
158
+ def promote(stage: str, commit_hash, openpath, yes, skip_migrate):
140
159
  """build 済みの :<commit-hash> image へ stage を向けて deploy する (再ビルドなし)。
141
160
 
142
161
  `pocket django build` で push した image に :<stage> タグを移し、インフラ/Lambda
@@ -154,7 +173,7 @@ def promote(stage: str, commit_hash, openpath, yes):
154
173
  skip_frontend=False,
155
174
  yes=yes,
156
175
  )
157
- _django_post_deploy(stage, yes=yes, openpath=openpath)
176
+ _django_post_deploy(stage, yes=yes, openpath=openpath, skip_migrate=skip_migrate)
158
177
 
159
178
 
160
179
  @django.command()
@@ -26,6 +26,12 @@ from pocket_cli.resources.aws.builders.dockerignore import (
26
26
 
27
27
  _MAX_LISTED = 10
28
28
 
29
+ # この process の build で警告した other-read 無しファイル ("path (mode)" 形式)。
30
+ # build ログに警告が埋もれて INIT 失敗まで気付けない実害があったため、deploy
31
+ # 終了時の再掲 (deploy_cli) と INIT 失敗エラーの原因提示 (lambdahandler) に使う。
32
+ # multi-container で同じ context を複数回 build しても重複しては積まない。
33
+ _warned_files: list[str] = []
34
+
29
35
  # runtime が INIT フェーズで読む設定ファイル。other-read が無いまま COPY される
30
36
  # と確実に INIT 失敗する (Rust runtime は pocket.toml を上方探索、Python runtime
31
37
  # は pocket.runtime.toml を読む) ため、警告でなくエラーに昇格する。
@@ -57,9 +63,16 @@ def warn_files_without_world_read(context_dir: Path) -> list[str]:
57
63
  unreadable = find_files_without_world_read(context_dir)
58
64
  critical = [r for r in unreadable if Path(r).name in _INIT_CRITICAL_NAMES]
59
65
  if unreadable:
60
- listed = ", ".join(unreadable[:_MAX_LISTED])
61
- if len(unreadable) > _MAX_LISTED:
62
- listed += " (他 %d 件)" % (len(unreadable) - _MAX_LISTED)
66
+ with_mode = [
67
+ "%s (%o)" % (rel, os.stat(context_dir / rel).st_mode & 0o777)
68
+ for rel in unreadable
69
+ ]
70
+ for entry in with_mode:
71
+ if entry not in _warned_files:
72
+ _warned_files.append(entry)
73
+ listed = ", ".join(with_mode[:_MAX_LISTED])
74
+ if len(with_mode) > _MAX_LISTED:
75
+ listed += " (他 %d 件)" % (len(with_mode) - _MAX_LISTED)
63
76
  echo.warning(
64
77
  "build context に other-read の無いファイルがあります (%d 件): %s。"
65
78
  "Lambda の実行ユーザーは非 root のため、このまま image に COPY されると"
@@ -79,3 +92,28 @@ def warn_files_without_world_read(context_dir: Path) -> list[str]:
79
92
  % ", ".join(critical)
80
93
  )
81
94
  return unreadable
95
+
96
+
97
+ def warned_files_with_mode() -> list[str]:
98
+ """この process の build で警告済みの other-read 無しファイル ("path (mode)")"""
99
+ return list(_warned_files)
100
+
101
+
102
+ def resummarize_world_read_warnings() -> None:
103
+ """build 時の other-read 警告を deploy 終了時に再掲する。
104
+
105
+ build ログの途中に出る警告は大量の出力に埋もれて気付けず、INIT 失敗まで
106
+ 到達してしまう実害があった (2026-08-26 受領 feedback)。deploy の最後に
107
+ ファイル名 + mode を再掲して、その場で chmod できるようにする。
108
+ """
109
+ if not _warned_files:
110
+ return
111
+ listed = ", ".join(_warned_files[:_MAX_LISTED])
112
+ if len(_warned_files) > _MAX_LISTED:
113
+ listed += " (他 %d 件)" % (len(_warned_files) - _MAX_LISTED)
114
+ echo.warning(
115
+ "注意: build context に other-read の無いファイルがありました (%d 件): "
116
+ "%s。Dockerfile で `COPY --chmod` 等により正規化していない場合、Lambda の"
117
+ "非 root 実行ユーザーが読めず INIT フェーズで失敗します。"
118
+ "`chmod 644 <file>` で修正してください。" % (len(_warned_files), listed)
119
+ )
@@ -12,6 +12,7 @@ from pydantic import BaseModel, Field
12
12
 
13
13
  from pocket.resources.base import ResourceStatus
14
14
  from pocket.utils import MANAGE_HANDLER_SUCCESS_SENTINEL
15
+ from pocket_cli.resources.aws.builders.context_check import warned_files_with_mode
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from pocket.context import LambdaHandlerContext
@@ -196,7 +197,7 @@ class LambdaHandler:
196
197
  if message.startswith(report_prefix):
197
198
  if not success_seen:
198
199
  if _looks_like_init_failure("\n".join(printed)):
199
- raise ManagementCommandFailed(
200
+ message = (
200
201
  "Lambda が INIT フェーズで失敗しました "
201
202
  "(Runtime.Unknown / INIT_REPORT ... Status: error)。"
202
203
  "これはアプリの例外ではなく runtime 側の問題の可能性が"
@@ -211,9 +212,19 @@ class LambdaHandler:
211
212
  "PermissionError になります。ログに PermissionError が"
212
213
  "あれば該当ファイルを chmod 644 して再 deploy して"
213
214
  "ください。"
215
+ )
216
+ detected = warned_files_with_mode()
217
+ if detected:
218
+ message += (
219
+ "今回の build では other-read の無いファイルを"
220
+ "検出済みです ((2) が濃厚): %s。"
221
+ % ", ".join(detected)
222
+ )
223
+ message += (
214
224
  "アプリの traceback ではなく上の INIT_REPORT / "
215
225
  "Runtime.Unknown 行を確認してください。"
216
226
  )
227
+ raise ManagementCommandFailed(message)
217
228
  raise ManagementCommandFailed(
218
229
  "management command handler did not complete successfully "
219
230
  "(no success marker before REPORT). "
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "magic-pocket-cli"
3
- version = "0.31.2"
3
+ version = "0.32.0"
4
4
  description = "CLI and deploy tools for magic-pocket."
5
5
  requires-python = ">=3.10"
6
6
  dependencies = [
7
7
  # lib は同 minor に束縛する。0.x では minor が breaking の単位のため、
8
8
  # 上限なしだと cli を pin した消費者にも lib の破壊的変更が届いてしまう
9
- "magic-pocket>=0.31.2,<0.32",
9
+ "magic-pocket>=0.32.0,<0.33",
10
10
  "click>=8.1.7",
11
11
  "jinja2>=3.1.3",
12
12
  "python-on-whales>=0.68.0",