magic-pocket-cli 0.7.2__tar.gz → 0.8.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.
Files changed (66) hide show
  1. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/PKG-INFO +2 -2
  2. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/lambdahandler.py +18 -0
  3. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/rds.py +146 -59
  4. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pyproject.toml +2 -2
  5. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/.gitignore +0 -0
  6. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/__init__.py +0 -0
  7. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/__init__.py +0 -0
  8. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/aws_auth.py +0 -0
  9. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/awscontainer_cli.py +0 -0
  10. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/cloudfront_cli.py +0 -0
  11. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
  12. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
  13. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/deploy_cli.py +0 -0
  14. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/destroy_cli.py +0 -0
  15. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/dsql_cli.py +0 -0
  16. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/main_cli.py +0 -0
  17. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/migrate_cli.py +0 -0
  18. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/neon_cli.py +0 -0
  19. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/permissions_cli.py +0 -0
  20. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/rds_cli.py +0 -0
  21. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/runtime_config_cli.py +0 -0
  22. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/s3_cli.py +0 -0
  23. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/status_cli.py +0 -0
  24. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/store_url_helper.py +0 -0
  25. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/tidb_cli.py +0 -0
  26. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/upstash_cli.py +0 -0
  27. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/vpc_cli.py +0 -0
  28. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/cli/waf_cli.py +0 -0
  29. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/django_cli.py +0 -0
  30. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/mediator.py +0 -0
  31. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/__init__.py +0 -0
  32. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/__init__.py +0 -0
  33. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/builders/__init__.py +0 -0
  34. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/builders/codebuild.py +0 -0
  35. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/builders/depot.py +0 -0
  36. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/builders/docker.py +0 -0
  37. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
  38. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/cloudformation.py +0 -0
  39. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/ecr.py +0 -0
  40. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/efs.py +0 -0
  41. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/s3_utils.py +0 -0
  42. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/aws/state.py +0 -0
  43. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/awscontainer.py +0 -0
  44. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/cloudfront.py +0 -0
  45. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/cloudfront_acm.py +0 -0
  46. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/cloudfront_keys.py +0 -0
  47. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/cloudfront_waf.py +0 -0
  48. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/dsql.py +0 -0
  49. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/neon.py +0 -0
  50. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/s3.py +0 -0
  51. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/tidb.py +0 -0
  52. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/upstash.py +0 -0
  53. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/resources/vpc.py +0 -0
  54. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/awscontainer.yaml +0 -0
  55. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
  56. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
  57. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
  58. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cloudfront.yaml +0 -0
  59. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
  60. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
  61. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
  62. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
  63. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/init/django-dotenv.env +0 -0
  64. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/init/django-settings.py +0 -0
  65. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
  66. {magic_pocket_cli-0.7.2 → magic_pocket_cli-0.8.1}/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.7.2
3
+ Version: 0.8.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.7.2
10
+ Requires-Dist: magic-pocket>=0.8.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
@@ -11,11 +11,17 @@ from botocore.exceptions import ClientError
11
11
  from pydantic import BaseModel, Field
12
12
 
13
13
  from pocket.resources.base import ResourceStatus
14
+ from pocket.utils import MANAGE_HANDLER_SUCCESS_SENTINEL
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from pocket.context import LambdaHandlerContext
17
18
 
18
19
 
20
+ class ManagementCommandFailed(Exception):
21
+ """management_command_handler の invoke が失敗した (成功センチネルが出ないまま
22
+ Lambda 実行が終わった) ことを示す。CLI を非ゼロ終了させ false green を防ぐ。"""
23
+
24
+
19
25
  class Configuration(BaseModel):
20
26
  hash: str | None = Field(alias="CodeSha256", default=None)
21
27
  last_update_status: str | None = Field(alias="LastUpdateStatus", default=None)
@@ -132,6 +138,10 @@ class LambdaHandler:
132
138
  events = self._find_events(start_pattern, created_at)
133
139
  print("Log stream found: %s" % events[0]["logStreamName"])
134
140
  printed = []
141
+ # 成功センチネル (management_command_handler が例外なく完了したときだけ印字)
142
+ # を REPORT 行までに観測できたかで成否を判定する。非同期 invoke では
143
+ # ハンドラの例外が呼び出し側に伝わらないため、ログ経由で判定する。
144
+ success_seen = False
135
145
  sleep_seconds = 5
136
146
  for _i in range(timeout_seconds // sleep_seconds):
137
147
  res = self.logs_client.filter_log_events(
@@ -145,8 +155,16 @@ class LambdaHandler:
145
155
  for message in messages[len(printed) :]:
146
156
  print(message.strip())
147
157
  printed.append(message)
158
+ if MANAGE_HANDLER_SUCCESS_SENTINEL in message:
159
+ success_seen = True
148
160
  time.sleep(0.05)
149
161
  if message.startswith(report_prefix):
162
+ if not success_seen:
163
+ raise ManagementCommandFailed(
164
+ "management command handler did not complete successfully "
165
+ "(no success marker before REPORT). "
166
+ "上の CloudWatch ログの traceback を確認してください。"
167
+ )
150
168
  return
151
169
  time.sleep(sleep_seconds)
152
170
  print("Timeout %s seconds. Please check logs in cloudwatch." % timeout_seconds)
@@ -44,8 +44,9 @@ class Rds:
44
44
  self._sm_client = boto3.client("secretsmanager", region_name=context.region)
45
45
  self._ssm_client = boto3.client("ssm", region_name=context.region)
46
46
 
47
- @cached_property
48
- def cluster(self) -> dict | None:
47
+ def _describe_cluster(self) -> dict | None:
48
+ """クラスタを都度 API で引く (キャッシュしない)。create() の存在判定など、
49
+ 作成前後で最新値が要る箇所はこちらを使う。"""
49
50
  try:
50
51
  res = self._rds_client.describe_db_clusters(
51
52
  DBClusterIdentifier=self.context.cluster_identifier
@@ -60,7 +61,10 @@ class Rds:
60
61
  raise
61
62
 
62
63
  @cached_property
63
- def instance(self) -> dict | None:
64
+ def cluster(self) -> dict | None:
65
+ return self._describe_cluster()
66
+
67
+ def _describe_instance(self) -> dict | None:
64
68
  try:
65
69
  res = self._rds_client.describe_db_instances(
66
70
  DBInstanceIdentifier=self.context.instance_identifier
@@ -75,7 +79,10 @@ class Rds:
75
79
  raise
76
80
 
77
81
  @cached_property
78
- def _security_group(self) -> dict | None:
82
+ def instance(self) -> dict | None:
83
+ return self._describe_instance()
84
+
85
+ def _describe_security_group(self) -> dict | None:
79
86
  res = self._ec2_client.describe_security_groups(
80
87
  Filters=[
81
88
  {"Name": "tag:Name", "Values": [self.context.security_group_name]},
@@ -86,6 +93,10 @@ class Rds:
86
93
  return groups[0]
87
94
  return None
88
95
 
96
+ @cached_property
97
+ def _security_group(self) -> dict | None:
98
+ return self._describe_security_group()
99
+
89
100
  @property
90
101
  def security_group_id(self) -> str | None:
91
102
  if self._security_group:
@@ -276,26 +287,34 @@ class Rds:
276
287
  # managed VPC の COMPLETED 待ちは deploy_resources で行う
277
288
  # (deploy_init 時点ではまだ VPC が作成されていない場合がある)
278
289
 
279
- def create(self):
280
- # VPC スタックの完了を待つ
281
- if not self.context.vpc:
282
- raise RuntimeError("vpc context is not configured")
283
- if self.context.vpc.manage:
284
- Vpc(self.context.vpc).stack.wait_status("COMPLETED")
285
- subnet_ids = self._get_vpc_subnet_ids()
286
- vpc_id = self._get_vpc_id()
287
-
288
- # 1. DB Subnet Group
289
- echo.log("Creating DB Subnet Group: %s" % self.context.subnet_group_name)
290
- self._rds_client.create_db_subnet_group(
291
- DBSubnetGroupName=self.context.subnet_group_name,
292
- DBSubnetGroupDescription="Aurora subnet group for %s"
293
- % self.context.cluster_identifier,
294
- SubnetIds=subnet_ids,
295
- Tags=[{"Key": "Name", "Value": self.context.subnet_group_name}],
296
- )
290
+ def _ensure_subnet_group(self, subnet_ids: list[str]) -> None:
291
+ """DB Subnet Group を作成 (既存なら再利用)。"""
292
+ try:
293
+ echo.log("Creating DB Subnet Group: %s" % self.context.subnet_group_name)
294
+ self._rds_client.create_db_subnet_group(
295
+ DBSubnetGroupName=self.context.subnet_group_name,
296
+ DBSubnetGroupDescription="Aurora subnet group for %s"
297
+ % self.context.cluster_identifier,
298
+ SubnetIds=subnet_ids,
299
+ Tags=[{"Key": "Name", "Value": self.context.subnet_group_name}],
300
+ )
301
+ except ClientError as e:
302
+ if e.response["Error"]["Code"] != "DBSubnetGroupAlreadyExistsFault":
303
+ raise
304
+ echo.log(
305
+ "DB Subnet Group %s already exists; reusing."
306
+ % self.context.subnet_group_name
307
+ )
297
308
 
298
- # 2. Security Group
309
+ def _ensure_security_group(self, vpc_id: str) -> str:
310
+ """RDS 用 Security Group を作成 (Name タグで既存を検出したら再利用)。"""
311
+ existing_sg = self._describe_security_group()
312
+ if existing_sg:
313
+ echo.log(
314
+ "Security Group %s already exists; reusing."
315
+ % self.context.security_group_name
316
+ )
317
+ return existing_sg["GroupId"]
299
318
  echo.log("Creating Security Group: %s" % self.context.security_group_name)
300
319
  sg_res = self._ec2_client.create_security_group(
301
320
  GroupName=self.context.security_group_name,
@@ -311,20 +330,25 @@ class Rds:
311
330
  }
312
331
  ],
313
332
  )
314
- sg_id = sg_res["GroupId"]
333
+ return sg_res["GroupId"]
315
334
 
316
- static = self.context.password_strategy == "static" # noqa: S105 戦略名/保存先種別であって secret 値ではない
317
- # static の場合、ここで設定した平文パスワードを後段で secret に保存する。
318
- password: str | None = None
335
+ def _create_or_restore_cluster(
336
+ self, sg_id: str, static: bool
337
+ ) -> tuple[bool, str | None]:
338
+ """クラスタを作成/復元 (既存なら skip)。
319
339
 
320
- # 3. Aurora クラスター作成 (snapshot_identifier があれば復元)
340
+ 戻り値は (このセッションで新規作成/復元したか, 生成した master password)
341
+ """
342
+ if self._describe_cluster() is not None:
343
+ echo.log(
344
+ "Aurora cluster %s already exists; skipping creation."
345
+ % self.context.cluster_identifier
346
+ )
347
+ return False, None
321
348
  if self.context.snapshot_identifier:
322
349
  echo.log(
323
350
  "Restoring Aurora cluster %s from snapshot %s"
324
- % (
325
- self.context.cluster_identifier,
326
- self.context.snapshot_identifier,
327
- )
351
+ % (self.context.cluster_identifier, self.context.snapshot_identifier)
328
352
  )
329
353
  self._rds_client.restore_db_cluster_from_snapshot(
330
354
  DBClusterIdentifier=self.context.cluster_identifier,
@@ -340,31 +364,40 @@ class Rds:
340
364
  },
341
365
  Tags=[{"Key": "Name", "Value": self.context.cluster_identifier}],
342
366
  )
367
+ return True, None
368
+ echo.log("Creating Aurora cluster: %s" % self.context.cluster_identifier)
369
+ password: str | None = None
370
+ password_kwargs: dict = {}
371
+ if static:
372
+ password = _generate_master_password()
373
+ password_kwargs["MasterUserPassword"] = password
343
374
  else:
344
- echo.log("Creating Aurora cluster: %s" % self.context.cluster_identifier)
345
- password_kwargs: dict = {}
346
- if static:
347
- password = _generate_master_password()
348
- password_kwargs["MasterUserPassword"] = password
349
- else:
350
- password_kwargs["ManageMasterUserPassword"] = True
351
- self._rds_client.create_db_cluster(
352
- DBClusterIdentifier=self.context.cluster_identifier,
353
- Engine="aurora-postgresql",
354
- EngineMode="provisioned",
355
- DatabaseName=self.context.database_name,
356
- MasterUsername=self.context.master_username,
357
- DBSubnetGroupName=self.context.subnet_group_name,
358
- VpcSecurityGroupIds=[sg_id],
359
- ServerlessV2ScalingConfiguration={
360
- "MinCapacity": self.context.min_capacity,
361
- "MaxCapacity": self.context.max_capacity,
362
- },
363
- Tags=[{"Key": "Name", "Value": self.context.cluster_identifier}],
364
- **password_kwargs,
365
- )
375
+ password_kwargs["ManageMasterUserPassword"] = True
376
+ self._rds_client.create_db_cluster(
377
+ DBClusterIdentifier=self.context.cluster_identifier,
378
+ Engine="aurora-postgresql",
379
+ EngineMode="provisioned",
380
+ DatabaseName=self.context.database_name,
381
+ MasterUsername=self.context.master_username,
382
+ DBSubnetGroupName=self.context.subnet_group_name,
383
+ VpcSecurityGroupIds=[sg_id],
384
+ ServerlessV2ScalingConfiguration={
385
+ "MinCapacity": self.context.min_capacity,
386
+ "MaxCapacity": self.context.max_capacity,
387
+ },
388
+ Tags=[{"Key": "Name", "Value": self.context.cluster_identifier}],
389
+ **password_kwargs,
390
+ )
391
+ return True, password
366
392
 
367
- # 4. Aurora インスタンス作成
393
+ def _ensure_instance(self) -> None:
394
+ """Aurora インスタンスを作成 (既存なら skip)。"""
395
+ if self._describe_instance() is not None:
396
+ echo.log(
397
+ "Aurora instance %s already exists; skipping creation."
398
+ % self.context.instance_identifier
399
+ )
400
+ return
368
401
  echo.log("Creating Aurora instance: %s" % self.context.instance_identifier)
369
402
  self._rds_client.create_db_instance(
370
403
  DBInstanceIdentifier=self.context.instance_identifier,
@@ -374,14 +407,48 @@ class Rds:
374
407
  Tags=[{"Key": "Name", "Value": self.context.instance_identifier}],
375
408
  )
376
409
 
377
- # 5. クラスター available を待機(最大30分)
410
+ def create(self):
411
+ # VPC スタックの完了を待つ
412
+ if not self.context.vpc:
413
+ raise RuntimeError("vpc context is not configured")
414
+ if self.context.vpc.manage:
415
+ Vpc(self.context.vpc).stack.wait_status("COMPLETED")
416
+ subnet_ids = self._get_vpc_subnet_ids()
417
+ vpc_id = self._get_vpc_id()
418
+
419
+ # 各ステップは「既に在れば再利用」で冪等にしてある。途中で失敗した
420
+ # deploy の再実行や、一部リソースだけ先行作成済みのケースでも
421
+ # AlreadyExists で落ちず、未完了のステップから続行できる。
422
+
423
+ static = self.context.password_strategy == "static" # noqa: S105 戦略名/保存先種別であって secret 値ではない
424
+
425
+ # 1. DB Subnet Group / 2. Security Group
426
+ self._ensure_subnet_group(subnet_ids)
427
+ sg_id = self._ensure_security_group(vpc_id)
428
+
429
+ # 3. Aurora クラスター (snapshot があれば復元)。cluster_created =
430
+ # このセッションで新規作成したか。password 切替 modify や static secret の
431
+ # 保存は「新規作成時のみ」実施する (再実行で static パスワードを作り直さない。
432
+ # 既存クラスタの調整は update() が担う)。
433
+ cluster_created, password = self._create_or_restore_cluster(sg_id, static)
434
+
435
+ # 4. Aurora インスタンス
436
+ self._ensure_instance()
437
+
438
+ # 5. クラスター/インスタンス available を待機(最大30分)。
439
+ # cluster available だけでは instance がまだ creating のことがあるため、
440
+ # 後続の modify_db_cluster (password 切替) の前に instance available まで
441
+ # 待つ。待たないと restore 直後の modify が反映されない/失敗しうる。
378
442
  echo.log("Waiting for Aurora cluster to become available...")
379
443
  self._wait_cluster_available(timeout=1800)
444
+ echo.log("Waiting for Aurora instance to become available...")
445
+ self._wait_instance_available(timeout=1800)
380
446
 
381
447
  # 6. snapshot から復元した場合、マスターパスワードを設定し直す。
382
448
  # (RestoreDBClusterFromSnapshot は snapshot の元パスワードを引き継ぐため、
383
449
  # pocket が参照できる認証情報を改めて確立する必要がある)
384
- if self.context.snapshot_identifier:
450
+ # 新規に復元したときだけ実施 (再実行時の再切替を避ける)。
451
+ if cluster_created and self.context.snapshot_identifier:
385
452
  if static:
386
453
  echo.log("Setting a pocket-managed static master password...")
387
454
  password = _generate_master_password()
@@ -404,8 +471,8 @@ class Rds:
404
471
 
405
472
  # 7. static: 生成したパスワードを pocket 所有の secret に保存する。
406
473
  # この secret を MasterUserSecret 相当として Lambda へ渡すため、ローテーション
407
- # 用 Lambda は付けない (= 自動ローテーションしない)
408
- if static:
474
+ # 用 Lambda は付けない (= 自動ローテーションしない)。新規作成時のみ。
475
+ if cluster_created and static:
409
476
  if password is None:
410
477
  raise RuntimeError("password must be set for static credentials")
411
478
  self._store_static_credentials(password)
@@ -646,6 +713,26 @@ class Rds:
646
713
  "Cluster did not become available within %s seconds" % timeout
647
714
  )
648
715
 
716
+ def _wait_instance_available(self, timeout: int = 1800, interval: int = 10):
717
+ for i in range(timeout // interval):
718
+ try:
719
+ res = self._rds_client.describe_db_instances(
720
+ DBInstanceIdentifier=self.context.instance_identifier
721
+ )
722
+ status = res["DBInstances"][0]["DBInstanceStatus"]
723
+ if status == "available":
724
+ print("")
725
+ return
726
+ except ClientError:
727
+ pass
728
+ if i == 0:
729
+ print("Waiting for instance to be available", end="", flush=True)
730
+ print(".", end="", flush=True)
731
+ time.sleep(interval)
732
+ raise TimeoutError(
733
+ "Instance did not become available within %s seconds" % timeout
734
+ )
735
+
649
736
  def _wait_instance_deleted(self, timeout: int = 600, interval: int = 10):
650
737
  for i in range(timeout // interval):
651
738
  try:
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "magic-pocket-cli"
3
- version = "0.7.2"
3
+ version = "0.8.1"
4
4
  description = "CLI and deploy tools for magic-pocket."
5
5
  requires-python = ">=3.10"
6
6
  dependencies = [
7
- "magic-pocket>=0.7.2",
7
+ "magic-pocket>=0.8.1",
8
8
  "click>=8.1.7",
9
9
  "jinja2>=3.1.3",
10
10
  "python-on-whales>=0.68.0",