magic-pocket-cli 0.35.0__tar.gz → 0.37.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.
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/PKG-INFO +2 -2
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/backup_cli.py +1 -1
- magic_pocket_cli-0.37.0/pocket_cli/cli/cleanup_deprecated_cli.py +140 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/dsql_cli.py +2 -3
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/inbound_cli.py +53 -36
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/main_cli.py +2 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/backup_common.py +37 -8
- magic_pocket_cli-0.37.0/pocket_cli/resources/aws/backup_legacy.py +124 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/codebuild.py +23 -10
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/backup.py +45 -11
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/dsql.py +6 -3
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/inbound.py +19 -4
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pyproject.toml +2 -2
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/.gitignore +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/__init__.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/__init__.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/aws_auth.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/build_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/cloudfront_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/container_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/deploy_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/destroy_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/image_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/interaction.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/migrate_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/neon_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/permissions_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/rds_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/removed_flags.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/resource_helper.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/runtime_config_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/s3_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/status_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/store_url_helper.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/tidb_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/upstash_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/url_helper.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/vpc_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/cli/waf_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/django_cli.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/mediator.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/migrations.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/__init__.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/__init__.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/__init__.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/context_check.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/depot.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/docker.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/cloudformation.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/ecr.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/efs.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/lambdahandler.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/poll.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/s3_utils.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/stack_backed.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/state.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/cloudfront.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/cloudfront_acm.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/cloudfront_keys.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/cloudfront_waf.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/container.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/inbound_template.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/neon.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/rds.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/s3.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/sqs_alert.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/tidb.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/upstash.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/vpc.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cf_function_basic_auth_prelude.js +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cf_function_host_redirect_prelude.js +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cloudfront.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/container.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/django-dotenv.env +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/django-settings.py +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
- {magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/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.37.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.
|
|
10
|
+
Requires-Dist: magic-pocket<0.38,>=0.37.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
|
|
@@ -38,7 +38,7 @@ def backup():
|
|
|
38
38
|
def cleanup(stage: str, yes: bool):
|
|
39
39
|
"""この stage のバックアップデータ (recovery point) を削除する。
|
|
40
40
|
|
|
41
|
-
[backup] deletable = true
|
|
41
|
+
[backup] deletable = true の宣言が必要。この stage の pocket 管理 vault
|
|
42
42
|
にある、現存する対象 DB (dsql / managed rds) の recovery point だけを消す。
|
|
43
43
|
削除済み cluster の分は ARN で引けないため対象外 (AWS Backup コンソールから
|
|
44
44
|
削除する)。plan (スケジュール) には触らない。
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""pocket cleanup-deprecated: 旧バージョンが残した account 単位リソースの掃除。
|
|
2
|
+
|
|
3
|
+
stage 単位の移行 (`pocket migrate` / deploy の migrate フェーズ) と違い、account
|
|
4
|
+
内の全 stage・全 project が共有していたリソースを対象にする。どの stage からも
|
|
5
|
+
参照されなくなったことを検査した上で、利用者の明示操作でだけ削除する
|
|
6
|
+
(deploy から自動では呼ばない)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from pocket.settings import Settings
|
|
16
|
+
from pocket.utils import echo
|
|
17
|
+
from pocket_cli.cli import interaction
|
|
18
|
+
from pocket_cli.resources.aws.backup_common import (
|
|
19
|
+
LEGACY_BACKUP_ROLE_NAME,
|
|
20
|
+
LEGACY_BACKUP_VAULT_NAME,
|
|
21
|
+
)
|
|
22
|
+
from pocket_cli.resources.aws.backup_legacy import LegacyBackupCleanup
|
|
23
|
+
|
|
24
|
+
_VAULT_DELETE_ATTEMPTS = 12
|
|
25
|
+
_VAULT_DELETE_INTERVAL = 10
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command(name="cleanup-deprecated")
|
|
29
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--delete-recovery-points",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
default=False,
|
|
34
|
+
help="旧 vault に残るバックアップデータ (recovery point) も削除する"
|
|
35
|
+
" (account 内の他 project の分も含む)",
|
|
36
|
+
)
|
|
37
|
+
@click.option("--dry-run", is_flag=True, default=False, help="対象の表示だけ行う")
|
|
38
|
+
@click.option(
|
|
39
|
+
"--yes", "-y", is_flag=True, default=False, help="確認プロンプトをスキップ"
|
|
40
|
+
)
|
|
41
|
+
def cleanup_deprecated(
|
|
42
|
+
stage: str, delete_recovery_points: bool, dry_run: bool, yes: bool
|
|
43
|
+
):
|
|
44
|
+
"""旧バージョンが残した account 共有リソースを削除する。
|
|
45
|
+
|
|
46
|
+
対象は 0.36 以前の backup vault (pocket-backup) とサービスロール
|
|
47
|
+
(forge-pocket-backup-role)。AWS account を共有している全 stage・全 project を
|
|
48
|
+
0.37.0 以上で deploy し終えてから、account/region ごとに 1 回実行する
|
|
49
|
+
(--stage は region の解決に使う)。旧リソースをまだ参照している plan /
|
|
50
|
+
selection があれば中止する。IAM ロールは全 region 共通なので、複数 region で
|
|
51
|
+
pocket を使っている場合は全 region の更新後に実行すること。
|
|
52
|
+
"""
|
|
53
|
+
interaction.set_assume_yes(yes)
|
|
54
|
+
region = Settings.from_toml(stage=stage).region
|
|
55
|
+
cleanup = LegacyBackupCleanup(region)
|
|
56
|
+
_abort_if_referenced(cleanup, region)
|
|
57
|
+
|
|
58
|
+
vault_exists = cleanup.vault_exists()
|
|
59
|
+
role_exists = cleanup.role_exists()
|
|
60
|
+
if not vault_exists and not role_exists:
|
|
61
|
+
echo.success("削除対象の旧リソースはありません (region: %s)。" % region)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
points = cleanup.recovery_points() if vault_exists else []
|
|
65
|
+
delete_vault = vault_exists and (not points or delete_recovery_points)
|
|
66
|
+
if vault_exists:
|
|
67
|
+
echo.info(
|
|
68
|
+
"backup vault: %s (recovery point %d 件)"
|
|
69
|
+
% (LEGACY_BACKUP_VAULT_NAME, len(points))
|
|
70
|
+
)
|
|
71
|
+
if role_exists:
|
|
72
|
+
echo.info("IAM role: %s" % LEGACY_BACKUP_ROLE_NAME)
|
|
73
|
+
if vault_exists and not delete_vault:
|
|
74
|
+
_explain_kept_vault(cleanup, points)
|
|
75
|
+
if dry_run or not (delete_vault or role_exists):
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
_confirm(points if delete_vault else [])
|
|
79
|
+
if role_exists and cleanup.delete_role():
|
|
80
|
+
echo.log("Deleted IAM role: %s" % LEGACY_BACKUP_ROLE_NAME)
|
|
81
|
+
if delete_vault and not _delete_vault(cleanup, points):
|
|
82
|
+
raise click.ClickException(
|
|
83
|
+
"recovery point の削除がまだ反映されておらず、vault を削除できません"
|
|
84
|
+
"でした。少し待って再実行してください。"
|
|
85
|
+
)
|
|
86
|
+
echo.success("旧リソースの掃除が完了しました。")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _abort_if_referenced(cleanup: LegacyBackupCleanup, region: str) -> None:
|
|
90
|
+
references = cleanup.references()
|
|
91
|
+
if not references:
|
|
92
|
+
return
|
|
93
|
+
for ref in references:
|
|
94
|
+
echo.warning(" - %s" % ref)
|
|
95
|
+
raise click.ClickException(
|
|
96
|
+
"旧リソースをまだ参照している設定があります (region: %s)。"
|
|
97
|
+
"該当する stage を 0.37.0 以上の pocket で deploy してから"
|
|
98
|
+
"再実行してください。" % region
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _confirm(points_to_delete: list[dict]) -> None:
|
|
103
|
+
if points_to_delete:
|
|
104
|
+
text = (
|
|
105
|
+
"旧 vault の recovery point %d 件を削除します。account 内の他 project の"
|
|
106
|
+
"バックアップデータも含まれ、復元できなくなります。続行しますか?"
|
|
107
|
+
% len(points_to_delete)
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
text = "上記の旧リソースを削除しますか?"
|
|
111
|
+
interaction.confirm(text, default=False, abort=True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _delete_vault(cleanup: LegacyBackupCleanup, points: list[dict]) -> bool:
|
|
115
|
+
if points:
|
|
116
|
+
cleanup.delete_recovery_points(points)
|
|
117
|
+
echo.log("Deleted %d recovery points" % len(points))
|
|
118
|
+
# recovery point の削除は非同期で、直後の DeleteBackupVault は「空でない」と
|
|
119
|
+
# 拒否される (実測)。反映を少し待つ
|
|
120
|
+
for attempt in range(_VAULT_DELETE_ATTEMPTS):
|
|
121
|
+
if cleanup.delete_vault():
|
|
122
|
+
echo.log("Deleted backup vault: %s" % LEGACY_BACKUP_VAULT_NAME)
|
|
123
|
+
return True
|
|
124
|
+
if attempt + 1 < _VAULT_DELETE_ATTEMPTS:
|
|
125
|
+
time.sleep(_VAULT_DELETE_INTERVAL)
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _explain_kept_vault(cleanup: LegacyBackupCleanup, points: list[dict]) -> None:
|
|
130
|
+
expiry = cleanup.last_expiry(points)
|
|
131
|
+
when = (
|
|
132
|
+
"%s 以降" % expiry.strftime("%Y-%m-%d")
|
|
133
|
+
if expiry
|
|
134
|
+
else "無期限保持のものがあるため自動では空になりません"
|
|
135
|
+
)
|
|
136
|
+
echo.warning(
|
|
137
|
+
"recovery point が残っているため vault は削除しません。保持期限で失効して"
|
|
138
|
+
"空になった後 (%s) に再実行するか、--delete-recovery-points で"
|
|
139
|
+
"データごと削除してください。" % when
|
|
140
|
+
)
|
|
@@ -6,7 +6,6 @@ from botocore.exceptions import ClientError
|
|
|
6
6
|
from pocket.context import Context
|
|
7
7
|
from pocket.utils import echo
|
|
8
8
|
from pocket_cli.cli.resource_helper import require_configured
|
|
9
|
-
from pocket_cli.resources.aws.backup_common import BACKUP_ROLE_NAME, BACKUP_VAULT_NAME
|
|
10
9
|
from pocket_cli.resources.dsql import (
|
|
11
10
|
BACKUP_TERMINAL_STATES,
|
|
12
11
|
DEFAULT_ON_DEMAND_RETENTION_DAYS,
|
|
@@ -77,13 +76,13 @@ def endpoint(stage, format_):
|
|
|
77
76
|
"--vault",
|
|
78
77
|
default=None,
|
|
79
78
|
help="バックアップ先の AWS Backup vault 名"
|
|
80
|
-
" (省略時: pocket 管理の '
|
|
79
|
+
" (省略時: pocket 管理の '{stage}-{project}-...-backup' を自動作成して使用)",
|
|
81
80
|
)
|
|
82
81
|
@click.option(
|
|
83
82
|
"--iam-role-arn",
|
|
84
83
|
default=None,
|
|
85
84
|
help="AWS Backup が assume するロール ARN"
|
|
86
|
-
" (省略時: pocket 管理の '
|
|
85
|
+
" (省略時: pocket 管理の '{stage}-{project}-...-backup-role' を自動作成して使用)",
|
|
87
86
|
)
|
|
88
87
|
@click.option(
|
|
89
88
|
"--retention-days",
|
|
@@ -10,7 +10,8 @@ from botocore.config import Config
|
|
|
10
10
|
from pocket.inbound import SETUP_NOTIFICATION, receipt_id, validate_receipt_id
|
|
11
11
|
from pocket.inbound_context import InboundContext
|
|
12
12
|
from pocket.settings import Settings, parse_handler_ref
|
|
13
|
-
from pocket_cli.
|
|
13
|
+
from pocket_cli.cli import interaction
|
|
14
|
+
from pocket_cli.resources.inbound import Inbound, no_active_rule_set_guide
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@click.group()
|
|
@@ -27,48 +28,58 @@ def inbound(ctx, stage, name):
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
@inbound.command()
|
|
31
|
+
@click.option(
|
|
32
|
+
"--format",
|
|
33
|
+
"output_format",
|
|
34
|
+
type=click.Choice(["text", "json"]),
|
|
35
|
+
default="text",
|
|
36
|
+
help="DNSレコードの出力形式",
|
|
37
|
+
)
|
|
30
38
|
@click.pass_obj
|
|
31
|
-
def init(resource):
|
|
32
|
-
"""
|
|
33
|
-
|
|
39
|
+
def init(resource, output_format):
|
|
40
|
+
"""ドメイン検証を申請し、登録すべきDNSレコードを出力する(初回のみ)。
|
|
41
|
+
|
|
42
|
+
receipt rule setはaccount/regionで1つしかactiveにできない共有物のため、
|
|
43
|
+
project単位の道具であるpocketは作成も有効化もしない。
|
|
44
|
+
"""
|
|
45
|
+
region = resource.context.region
|
|
46
|
+
ses = boto3.client("ses", region_name=region)
|
|
34
47
|
active = ses.describe_active_receipt_rule_set().get("Metadata", {}).get("Name")
|
|
35
48
|
if not active:
|
|
36
|
-
|
|
37
|
-
names = [
|
|
38
|
-
item["Name"]
|
|
39
|
-
for page in ses.get_paginator("list_receipt_rule_sets").paginate()
|
|
40
|
-
for item in page["RuleSets"]
|
|
41
|
-
]
|
|
42
|
-
if name in names:
|
|
43
|
-
raise click.ClickException(
|
|
44
|
-
"pocket-inboundが既にあります。内容を確認し管理者が有効化してください"
|
|
45
|
-
)
|
|
46
|
-
click.confirm(
|
|
47
|
-
"このaccount/regionで初期化を同時実行していないことを確認してください。"
|
|
48
|
-
"共有rule set pocket-inboundを作成・有効化しますか?",
|
|
49
|
-
abort=True,
|
|
50
|
-
)
|
|
51
|
-
if ses.describe_active_receipt_rule_set().get("Metadata"):
|
|
52
|
-
raise click.ClickException("active setが変わりました。再実行してください")
|
|
53
|
-
ses.create_receipt_rule_set(RuleSetName=name)
|
|
54
|
-
if ses.describe_active_receipt_rule_set().get("Metadata"):
|
|
55
|
-
raise click.ClickException(
|
|
56
|
-
"初期化中にactive setが変わりました。有効化を中止します"
|
|
57
|
-
)
|
|
58
|
-
ses.set_active_receipt_rule_set(RuleSetName=name)
|
|
59
|
-
active = ses.describe_active_receipt_rule_set().get("Metadata", {}).get("Name")
|
|
60
|
-
if active != name:
|
|
61
|
-
raise click.ClickException("有効化後のactive setが一致しません")
|
|
49
|
+
raise click.ClickException(no_active_rule_set_guide(region))
|
|
62
50
|
domain = resource.context.config.domain
|
|
63
51
|
result = ses.get_identity_verification_attributes(Identities=[domain])
|
|
64
52
|
identity = result.get("VerificationAttributes", {}).get(domain, {})
|
|
65
53
|
token = identity.get("VerificationToken")
|
|
66
54
|
if not token and identity.get("VerificationStatus") != "Success":
|
|
67
55
|
token = ses.verify_domain_identity(Domain=domain)["VerificationToken"]
|
|
68
|
-
|
|
56
|
+
records = []
|
|
69
57
|
if token:
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
records.append({"type": "TXT", "name": f"_amazonses.{domain}", "value": token})
|
|
59
|
+
records.append(
|
|
60
|
+
{
|
|
61
|
+
"type": "MX",
|
|
62
|
+
"name": domain,
|
|
63
|
+
"priority": 10,
|
|
64
|
+
"value": f"inbound-smtp.{region}.amazonaws.com",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
if output_format == "json":
|
|
68
|
+
payload = {
|
|
69
|
+
"active_rule_set": active,
|
|
70
|
+
"domain": domain,
|
|
71
|
+
"region": region,
|
|
72
|
+
"verification_status": identity.get("VerificationStatus") or "Pending",
|
|
73
|
+
"records": records,
|
|
74
|
+
}
|
|
75
|
+
click.echo(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
76
|
+
return
|
|
77
|
+
click.echo(f"active rule set: {active}")
|
|
78
|
+
for record in records:
|
|
79
|
+
if record["type"] == "TXT":
|
|
80
|
+
click.echo(f"TXT {record['name']} {record['value']}")
|
|
81
|
+
else:
|
|
82
|
+
click.echo(f"MX {record['name']} {record['priority']} {record['value']}")
|
|
72
83
|
click.echo("SES受信対応リージョンとMXの既存配送先を確認してDNSを登録してください。")
|
|
73
84
|
|
|
74
85
|
|
|
@@ -94,11 +105,17 @@ def show_yaml(resource):
|
|
|
94
105
|
|
|
95
106
|
|
|
96
107
|
@inbound.command()
|
|
108
|
+
@click.option(
|
|
109
|
+
"--yes", "-y", is_flag=True, default=False, help="確認プロンプトをスキップ"
|
|
110
|
+
)
|
|
97
111
|
@click.pass_obj
|
|
98
|
-
def destroy(resource):
|
|
112
|
+
def destroy(resource, yes):
|
|
99
113
|
"""無効化・配送猶予・滞留解消後に自分のルールとスタックを削除する。"""
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
interaction.set_assume_yes(yes)
|
|
115
|
+
interaction.confirm(
|
|
116
|
+
"受信停止後36時間以上経過し、原本の照合・必要な退避を終えましたか?",
|
|
117
|
+
default=False,
|
|
118
|
+
abort=True,
|
|
102
119
|
)
|
|
103
120
|
resource.delete()
|
|
104
121
|
|
|
@@ -17,6 +17,7 @@ except ModuleNotFoundError as e:
|
|
|
17
17
|
from pocket_cli.cli import (
|
|
18
18
|
backup_cli,
|
|
19
19
|
build_cli,
|
|
20
|
+
cleanup_deprecated_cli,
|
|
20
21
|
cloudfront_cli,
|
|
21
22
|
cloudfront_keys_cli,
|
|
22
23
|
cloudfront_waf_cli,
|
|
@@ -134,6 +135,7 @@ main.add_command(migrate_cli.migrate)
|
|
|
134
135
|
main.add_command(permissions_cli.permissions)
|
|
135
136
|
main.add_command(waf_cli.waf)
|
|
136
137
|
main.add_command(backup_cli.backup)
|
|
138
|
+
main.add_command(cleanup_deprecated_cli.cleanup_deprecated)
|
|
137
139
|
|
|
138
140
|
|
|
139
141
|
@main.group()
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/backup_common.py
RENAMED
|
@@ -18,9 +18,15 @@ from pocket.utils import echo
|
|
|
18
18
|
# pocket 管理のバックアップ前提リソース。AWS Backup の Default vault /
|
|
19
19
|
# AWSBackupDefaultServiceRole は console 初回操作で作られるもので、API しか
|
|
20
20
|
# 使わないアカウントには存在しないため、pocket 側で冪等に ensure する。
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
# 名前は stage 単位 ({resource_prefix}backup / {resource_prefix}backup-role) で、
|
|
22
|
+
# context が導出する。
|
|
23
|
+
#
|
|
24
|
+
# 0.36 以前は account 共有の固定名だった。vault は改名も recovery point の移動も
|
|
25
|
+
# できず、account 内の他 stage / project と共有なので pocket からは消せない。
|
|
26
|
+
# 旧 vault の recovery point は取得時の lifecycle で失効するまで残り、pocket の
|
|
27
|
+
# list / cleanup の対象外になる (docs の移行手順を参照)
|
|
28
|
+
LEGACY_BACKUP_VAULT_NAME = "pocket-backup"
|
|
29
|
+
LEGACY_BACKUP_ROLE_NAME = "forge-pocket-backup-role"
|
|
24
30
|
_BACKUP_ROLE_POLICIES = (
|
|
25
31
|
"arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup",
|
|
26
32
|
"arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores",
|
|
@@ -45,18 +51,20 @@ def ensure_backup_vault(backup_client, name: str) -> None:
|
|
|
45
51
|
echo.log("Created backup vault: %s" % name)
|
|
46
52
|
|
|
47
53
|
|
|
48
|
-
def ensure_backup_role(
|
|
54
|
+
def ensure_backup_role(
|
|
55
|
+
iam_client, role_name: str, permissions_boundary: str | None
|
|
56
|
+
) -> str:
|
|
49
57
|
"""AWS Backup サービスロールを冪等に ensure して ARN を返す。
|
|
50
58
|
|
|
51
59
|
codebuild の _ensure_role と同型。boundary 必須のアカウントでも作成
|
|
52
60
|
できるよう permissions_boundary を付与する。
|
|
53
61
|
"""
|
|
54
62
|
try:
|
|
55
|
-
return iam_client.get_role(RoleName=
|
|
63
|
+
return iam_client.get_role(RoleName=role_name)["Role"]["Arn"]
|
|
56
64
|
except ClientError as e:
|
|
57
65
|
if e.response["Error"]["Code"] != "NoSuchEntity":
|
|
58
66
|
raise
|
|
59
|
-
echo.log("Creating AWS Backup service role: %s" %
|
|
67
|
+
echo.log("Creating AWS Backup service role: %s" % role_name)
|
|
60
68
|
assume_role_policy = json.dumps(
|
|
61
69
|
{
|
|
62
70
|
"Version": "2012-10-17",
|
|
@@ -70,15 +78,36 @@ def ensure_backup_role(iam_client, permissions_boundary: str | None) -> str:
|
|
|
70
78
|
}
|
|
71
79
|
)
|
|
72
80
|
create_kwargs: dict = {
|
|
73
|
-
"RoleName":
|
|
81
|
+
"RoleName": role_name,
|
|
74
82
|
"AssumeRolePolicyDocument": assume_role_policy,
|
|
75
83
|
}
|
|
76
84
|
if permissions_boundary:
|
|
77
85
|
create_kwargs["PermissionsBoundary"] = permissions_boundary
|
|
78
86
|
role_arn: str = iam_client.create_role(**create_kwargs)["Role"]["Arn"]
|
|
79
87
|
for policy_arn in _BACKUP_ROLE_POLICIES:
|
|
80
|
-
iam_client.attach_role_policy(RoleName=
|
|
88
|
+
iam_client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
|
|
81
89
|
# IAM ロールの伝播待ち (作成直後の StartBackupJob 失敗を避ける)
|
|
82
90
|
echo.log("Waiting for IAM role propagation...")
|
|
83
91
|
time.sleep(10)
|
|
84
92
|
return role_arn
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def delete_backup_role(iam_client, role_name: str) -> bool:
|
|
96
|
+
"""stage 所有の AWS Backup サービスロールを削除する (無ければ False)。
|
|
97
|
+
|
|
98
|
+
ロールは「設定」側の付属物なので destroy で消す (vault と recovery point は
|
|
99
|
+
データなので残す)。復元等で再び必要になれば ensure_backup_role が作り直す。
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
for policy_arn in _BACKUP_ROLE_POLICIES:
|
|
103
|
+
try:
|
|
104
|
+
iam_client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
|
|
105
|
+
except ClientError as e:
|
|
106
|
+
if e.response["Error"]["Code"] != "NoSuchEntity":
|
|
107
|
+
raise
|
|
108
|
+
iam_client.delete_role(RoleName=role_name)
|
|
109
|
+
except ClientError as e:
|
|
110
|
+
if e.response["Error"]["Code"] == "NoSuchEntity":
|
|
111
|
+
return False
|
|
112
|
+
raise
|
|
113
|
+
return True
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""0.36 以前の account 共有 backup リソース (vault / サービスロール) の掃除。
|
|
2
|
+
|
|
3
|
+
0.37.0 で vault / ロールを stage 単位の名前に改めたため、旧名のリソースは
|
|
4
|
+
どのコードからも参照されなくなる。ただし account 内の全 stage・全 project が
|
|
5
|
+
共有していたので、deploy や destroy (= stage 単位の操作) からは消せない。
|
|
6
|
+
「全 stage を更新し終えた」ことを利用者が確認した上で明示的に呼ぶ
|
|
7
|
+
`pocket cleanup-deprecated` がここを使う。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
import boto3
|
|
15
|
+
from botocore.exceptions import ClientError
|
|
16
|
+
|
|
17
|
+
from pocket_cli.resources.aws.backup_common import (
|
|
18
|
+
LEGACY_BACKUP_ROLE_NAME,
|
|
19
|
+
LEGACY_BACKUP_VAULT_NAME,
|
|
20
|
+
delete_backup_role,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LegacyBackupCleanup:
|
|
25
|
+
def __init__(self, region: str) -> None:
|
|
26
|
+
self.region = region
|
|
27
|
+
self._backup = boto3.client("backup", region_name=region)
|
|
28
|
+
self._iam = boto3.client("iam", region_name=region)
|
|
29
|
+
|
|
30
|
+
def references(self) -> list[str]:
|
|
31
|
+
"""旧 vault / 旧ロールをまだ参照している plan・selection の説明一覧。
|
|
32
|
+
|
|
33
|
+
1 件でもあれば、その stage は 0.36 以前のまま (未 deploy) なので、
|
|
34
|
+
消すと定期バックアップが失敗し始める。掃除の前提条件として検査する。
|
|
35
|
+
"""
|
|
36
|
+
found: list[str] = []
|
|
37
|
+
paginator = self._backup.get_paginator("list_backup_plans")
|
|
38
|
+
for page in paginator.paginate():
|
|
39
|
+
for item in page["BackupPlansList"]:
|
|
40
|
+
plan_id = item["BackupPlanId"]
|
|
41
|
+
plan_name = item["BackupPlanName"]
|
|
42
|
+
plan = self._backup.get_backup_plan(BackupPlanId=plan_id)["BackupPlan"]
|
|
43
|
+
if any(
|
|
44
|
+
rule.get("TargetBackupVaultName") == LEGACY_BACKUP_VAULT_NAME
|
|
45
|
+
for rule in plan.get("Rules", [])
|
|
46
|
+
):
|
|
47
|
+
found.append(
|
|
48
|
+
"backup plan %s (保存先が %s)"
|
|
49
|
+
% (plan_name, LEGACY_BACKUP_VAULT_NAME)
|
|
50
|
+
)
|
|
51
|
+
found.extend(self._selection_references(plan_id, plan_name))
|
|
52
|
+
return found
|
|
53
|
+
|
|
54
|
+
def _selection_references(self, plan_id: str, plan_name: str) -> list[str]:
|
|
55
|
+
found: list[str] = []
|
|
56
|
+
suffix = ":role/%s" % LEGACY_BACKUP_ROLE_NAME
|
|
57
|
+
selections = self._backup.list_backup_selections(BackupPlanId=plan_id)
|
|
58
|
+
for item in selections["BackupSelectionsList"]:
|
|
59
|
+
detail = self._backup.get_backup_selection(
|
|
60
|
+
BackupPlanId=plan_id, SelectionId=item["SelectionId"]
|
|
61
|
+
)
|
|
62
|
+
if detail["BackupSelection"].get("IamRoleArn", "").endswith(suffix):
|
|
63
|
+
found.append(
|
|
64
|
+
"backup selection %s / %s (ロールが %s)"
|
|
65
|
+
% (plan_name, item["SelectionName"], LEGACY_BACKUP_ROLE_NAME)
|
|
66
|
+
)
|
|
67
|
+
return found
|
|
68
|
+
|
|
69
|
+
def vault_exists(self) -> bool:
|
|
70
|
+
"""旧 vault があるか。
|
|
71
|
+
|
|
72
|
+
DescribeBackupVault は、vault が 1 つも無い account では存在しない vault に
|
|
73
|
+
AccessDeniedException を返し「未作成」を判定できないため、一覧で調べる。
|
|
74
|
+
"""
|
|
75
|
+
paginator = self._backup.get_paginator("list_backup_vaults")
|
|
76
|
+
for page in paginator.paginate():
|
|
77
|
+
for vault in page["BackupVaultList"]:
|
|
78
|
+
if vault["BackupVaultName"] == LEGACY_BACKUP_VAULT_NAME:
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def recovery_points(self) -> list[dict]:
|
|
83
|
+
points: list[dict] = []
|
|
84
|
+
paginator = self._backup.get_paginator("list_recovery_points_by_backup_vault")
|
|
85
|
+
for page in paginator.paginate(BackupVaultName=LEGACY_BACKUP_VAULT_NAME):
|
|
86
|
+
points.extend(page["RecoveryPoints"])
|
|
87
|
+
return points
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def last_expiry(points: list[dict]) -> datetime | None:
|
|
91
|
+
"""全 recovery point が失効し終える日時 (無期限のものがあれば None)。"""
|
|
92
|
+
expiries = [p.get("CalculatedLifecycle", {}).get("DeleteAt") for p in points]
|
|
93
|
+
if not expiries or any(e is None for e in expiries):
|
|
94
|
+
return None
|
|
95
|
+
return max(expiries)
|
|
96
|
+
|
|
97
|
+
def delete_recovery_points(self, points: list[dict]) -> None:
|
|
98
|
+
for point in points:
|
|
99
|
+
self._backup.delete_recovery_point(
|
|
100
|
+
BackupVaultName=LEGACY_BACKUP_VAULT_NAME,
|
|
101
|
+
RecoveryPointArn=point["RecoveryPointArn"],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def delete_vault(self) -> bool:
|
|
105
|
+
"""旧 vault を削除する。recovery point の削除が未反映で空でなければ False。"""
|
|
106
|
+
try:
|
|
107
|
+
self._backup.delete_backup_vault(BackupVaultName=LEGACY_BACKUP_VAULT_NAME)
|
|
108
|
+
except ClientError as e:
|
|
109
|
+
if e.response["Error"]["Code"] == "InvalidRequestException":
|
|
110
|
+
return False
|
|
111
|
+
raise
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def role_exists(self) -> bool:
|
|
115
|
+
try:
|
|
116
|
+
self._iam.get_role(RoleName=LEGACY_BACKUP_ROLE_NAME)
|
|
117
|
+
except ClientError as e:
|
|
118
|
+
if e.response["Error"]["Code"] == "NoSuchEntity":
|
|
119
|
+
return False
|
|
120
|
+
raise
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
def delete_role(self) -> bool:
|
|
124
|
+
return delete_backup_role(self._iam, LEGACY_BACKUP_ROLE_NAME)
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/codebuild.py
RENAMED
|
@@ -65,7 +65,12 @@ class CodeBuildBuilder:
|
|
|
65
65
|
self.sts = boto3.client("sts", region_name=region)
|
|
66
66
|
|
|
67
67
|
self._project_name = f"{resource_prefix}codebuild"
|
|
68
|
-
self._role_name = f"
|
|
68
|
+
self._role_name = f"{resource_prefix}codebuild-role"
|
|
69
|
+
# 0.36 以前のロール名。forge- は開発環境由来の prefix で pocket の命名規約
|
|
70
|
+
# ({stage}-{project}-...) と無関係だったため改名した。ロールは stage の
|
|
71
|
+
# 所有物なので、新ロールへ付け替えた後に pocket が削除する (改名互換。
|
|
72
|
+
# いずれ削除予定)
|
|
73
|
+
self._legacy_role_name = f"forge-{resource_prefix}codebuild-role"
|
|
69
74
|
self._source_key = f"codebuild/{self._project_name}/source.zip"
|
|
70
75
|
|
|
71
76
|
def build_and_push(
|
|
@@ -85,6 +90,8 @@ class CodeBuildBuilder:
|
|
|
85
90
|
|
|
86
91
|
role_arn = self._ensure_role(account_id)
|
|
87
92
|
self._ensure_project(platform, role_arn)
|
|
93
|
+
# project の serviceRole を新ロールへ付け替えた後なので、旧名ロールは孤児
|
|
94
|
+
self._delete_role(self._legacy_role_name)
|
|
88
95
|
self._upload_source(dockerfile_path)
|
|
89
96
|
|
|
90
97
|
build_id = self._start_build(
|
|
@@ -101,7 +108,8 @@ class CodeBuildBuilder:
|
|
|
101
108
|
def delete(self) -> None:
|
|
102
109
|
"""CodeBuildプロジェクトとIAMロールを削除"""
|
|
103
110
|
self._delete_project()
|
|
104
|
-
self._delete_role()
|
|
111
|
+
self._delete_role(self._role_name)
|
|
112
|
+
self._delete_role(self._legacy_role_name)
|
|
105
113
|
|
|
106
114
|
# --- IAM ロール ---
|
|
107
115
|
|
|
@@ -199,16 +207,14 @@ class CodeBuildBuilder:
|
|
|
199
207
|
time.sleep(10)
|
|
200
208
|
return role_arn
|
|
201
209
|
|
|
202
|
-
def _delete_role(self) -> None:
|
|
210
|
+
def _delete_role(self, role_name: str) -> None:
|
|
203
211
|
try:
|
|
204
212
|
# インラインポリシー削除
|
|
205
|
-
policies = self.iam.list_role_policies(RoleName=
|
|
213
|
+
policies = self.iam.list_role_policies(RoleName=role_name)
|
|
206
214
|
for policy_name in policies["PolicyNames"]:
|
|
207
|
-
self.iam.delete_role_policy(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
self.iam.delete_role(RoleName=self._role_name)
|
|
211
|
-
print(" IAMロール削除: %s" % self._role_name)
|
|
215
|
+
self.iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name)
|
|
216
|
+
self.iam.delete_role(RoleName=role_name)
|
|
217
|
+
print(" IAMロール削除: %s" % role_name)
|
|
212
218
|
except ClientError as e:
|
|
213
219
|
if e.response["Error"]["Code"] == "NoSuchEntity":
|
|
214
220
|
return
|
|
@@ -355,8 +361,15 @@ class CodeBuildBuilder:
|
|
|
355
361
|
return len(resp.get("projects", [])) > 0
|
|
356
362
|
|
|
357
363
|
def role_exists(self) -> bool:
|
|
364
|
+
"""現行名・旧名いずれかのロールがあるか (destroy の対象判定用)。"""
|
|
365
|
+
return any(
|
|
366
|
+
self._role_exists(name)
|
|
367
|
+
for name in (self._role_name, self._legacy_role_name)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _role_exists(self, role_name: str) -> bool:
|
|
358
371
|
try:
|
|
359
|
-
self.iam.get_role(RoleName=
|
|
372
|
+
self.iam.get_role(RoleName=role_name)
|
|
360
373
|
return True
|
|
361
374
|
except ClientError as e:
|
|
362
375
|
if e.response["Error"]["Code"] == "NoSuchEntity":
|
|
@@ -8,7 +8,7 @@ from botocore.exceptions import ClientError
|
|
|
8
8
|
from pocket.resources.base import ResourceStatus
|
|
9
9
|
from pocket.utils import echo
|
|
10
10
|
from pocket_cli.resources.aws.backup_common import (
|
|
11
|
-
|
|
11
|
+
delete_backup_role,
|
|
12
12
|
ensure_backup_role,
|
|
13
13
|
ensure_backup_vault,
|
|
14
14
|
)
|
|
@@ -151,8 +151,10 @@ class Backup:
|
|
|
151
151
|
]
|
|
152
152
|
if not targets:
|
|
153
153
|
return
|
|
154
|
-
ensure_backup_vault(self._backup,
|
|
155
|
-
role_arn = ensure_backup_role(
|
|
154
|
+
ensure_backup_vault(self._backup, self.context.vault_name)
|
|
155
|
+
role_arn = ensure_backup_role(
|
|
156
|
+
self._iam, self.context.role_name, self.context.permissions_boundary
|
|
157
|
+
)
|
|
156
158
|
for plan, arn in targets:
|
|
157
159
|
plan_id = self._ensure_plan(plan)
|
|
158
160
|
self._ensure_selection(plan, plan_id, role_arn, [arn])
|
|
@@ -200,7 +202,7 @@ class Backup:
|
|
|
200
202
|
rules.append(
|
|
201
203
|
{
|
|
202
204
|
"RuleName": rule.name,
|
|
203
|
-
"TargetBackupVaultName":
|
|
205
|
+
"TargetBackupVaultName": self.context.vault_name,
|
|
204
206
|
"ScheduleExpression": rule.schedule_expression,
|
|
205
207
|
"ScheduleExpressionTimezone": self.context.timezone,
|
|
206
208
|
"Lifecycle": lifecycle,
|
|
@@ -251,8 +253,9 @@ class Backup:
|
|
|
251
253
|
) -> None:
|
|
252
254
|
"""対象 DB の ARN を指した selection を確保する。
|
|
253
255
|
|
|
254
|
-
selection には更新 API が無いため、対象 ARN
|
|
255
|
-
(
|
|
256
|
+
selection には更新 API が無いため、対象 ARN (cluster 再作成など) か
|
|
257
|
+
サービスロール (0.36 以前の account 共有ロールからの移行) が変わって
|
|
258
|
+
いたら作り直す。
|
|
256
259
|
"""
|
|
257
260
|
selections = self._backup.list_backup_selections(BackupPlanId=plan_id)
|
|
258
261
|
for item in selections["BackupSelectionsList"]:
|
|
@@ -261,7 +264,11 @@ class Backup:
|
|
|
261
264
|
detail = self._backup.get_backup_selection(
|
|
262
265
|
BackupPlanId=plan_id, SelectionId=item["SelectionId"]
|
|
263
266
|
)
|
|
264
|
-
|
|
267
|
+
current = detail["BackupSelection"]
|
|
268
|
+
if (
|
|
269
|
+
sorted(current.get("Resources", [])) == arns
|
|
270
|
+
and current.get("IamRoleArn") == role_arn
|
|
271
|
+
):
|
|
265
272
|
return
|
|
266
273
|
self._backup.delete_backup_selection(
|
|
267
274
|
BackupPlanId=plan_id, SelectionId=item["SelectionId"]
|
|
@@ -277,7 +284,7 @@ class Backup:
|
|
|
277
284
|
)
|
|
278
285
|
|
|
279
286
|
def delete(self) -> None:
|
|
280
|
-
"""backup plan / selection
|
|
287
|
+
"""backup plan / selection とサービスロールを削除する。
|
|
281
288
|
|
|
282
289
|
recovery point と vault は消さない。バックアップ「設定」は stack の
|
|
283
290
|
付属物だが、バックアップ「データ」は stack より長生きさせるべきで、
|
|
@@ -288,6 +295,10 @@ class Backup:
|
|
|
288
295
|
持たない deploy role でも destroy 自体は完遂させたいので、権限エラーは
|
|
289
296
|
警告に留める。
|
|
290
297
|
"""
|
|
298
|
+
self._delete_plans()
|
|
299
|
+
self._delete_role()
|
|
300
|
+
|
|
301
|
+
def _delete_plans(self) -> None:
|
|
291
302
|
for plan_name in self.context.cleanup_plan_names:
|
|
292
303
|
try:
|
|
293
304
|
plan_id = self._find_plan_id(plan_name)
|
|
@@ -305,6 +316,26 @@ class Backup:
|
|
|
305
316
|
self._delete_plan(plan_id)
|
|
306
317
|
echo.log("Deleted backup plan: %s" % plan_name)
|
|
307
318
|
|
|
319
|
+
def _delete_role(self) -> None:
|
|
320
|
+
"""stage 所有のサービスロールを削除する (selection を消した後に呼ぶ)。
|
|
321
|
+
|
|
322
|
+
0.36 以前の account 共有ロール (forge-pocket-backup-role) は他 stage /
|
|
323
|
+
project が使っている可能性があるため触らない。
|
|
324
|
+
"""
|
|
325
|
+
try:
|
|
326
|
+
if delete_backup_role(self._iam, self.context.role_name):
|
|
327
|
+
echo.log("Deleted backup service role: %s" % self.context.role_name)
|
|
328
|
+
except ClientError as e:
|
|
329
|
+
if e.response["Error"]["Code"] not in (
|
|
330
|
+
"AccessDenied",
|
|
331
|
+
"AccessDeniedException",
|
|
332
|
+
):
|
|
333
|
+
raise
|
|
334
|
+
echo.warning(
|
|
335
|
+
"backup サービスロール (%s) を削除する権限がないため、スキップ"
|
|
336
|
+
"しました。不要なら手動で削除してください。" % self.context.role_name
|
|
337
|
+
)
|
|
338
|
+
|
|
308
339
|
def _delete_plan(self, plan_id: str) -> None:
|
|
309
340
|
"""selection → plan の順に削除する (selection が残ると plan を消せない)。"""
|
|
310
341
|
selections = self._backup.list_backup_selections(BackupPlanId=plan_id)
|
|
@@ -315,7 +346,7 @@ class Backup:
|
|
|
315
346
|
self._backup.delete_backup_plan(BackupPlanId=plan_id)
|
|
316
347
|
|
|
317
348
|
def list_recovery_points(self) -> list[dict] | None:
|
|
318
|
-
"""対象 DB の recovery point 一覧 (pocket
|
|
349
|
+
"""対象 DB の recovery point 一覧 (pocket 管理 vault 内のもの)。
|
|
319
350
|
|
|
320
351
|
現存する cluster の ARN でしか引けない (削除済み cluster の分は対象外 =
|
|
321
352
|
console でしか消せない)。権限が無ければ None を返す (呼び出し側が案内を
|
|
@@ -334,8 +365,11 @@ class Backup:
|
|
|
334
365
|
raise
|
|
335
366
|
return None
|
|
336
367
|
# pocket 管理の vault 分だけを扱う (--vault で利用者の vault に取った
|
|
337
|
-
# オンデマンド分は利用者の管理物とみなし、pocket からは消さない)
|
|
338
|
-
|
|
368
|
+
# オンデマンド分は利用者の管理物とみなし、pocket からは消さない)。0.36 以前の
|
|
369
|
+
# account 共有 vault (pocket-backup) に残る分も同じ理由で対象外
|
|
370
|
+
return [
|
|
371
|
+
p for p in points if p.get("BackupVaultName") == self.context.vault_name
|
|
372
|
+
]
|
|
339
373
|
|
|
340
374
|
def delete_recovery_points(self, points: list[dict]) -> int:
|
|
341
375
|
"""recovery point を削除して件数を返す。
|
|
@@ -15,7 +15,6 @@ from pocket.secret_store import (
|
|
|
15
15
|
)
|
|
16
16
|
from pocket.utils import echo
|
|
17
17
|
from pocket_cli.resources.aws.backup_common import (
|
|
18
|
-
BACKUP_VAULT_NAME,
|
|
19
18
|
ensure_backup_role,
|
|
20
19
|
ensure_backup_vault,
|
|
21
20
|
)
|
|
@@ -216,7 +215,7 @@ class Dsql:
|
|
|
216
215
|
if not self.arn:
|
|
217
216
|
raise ValueError("Cluster not found")
|
|
218
217
|
if vault_name is None:
|
|
219
|
-
vault_name =
|
|
218
|
+
vault_name = self.context.backup_vault_name
|
|
220
219
|
self._ensure_backup_vault(vault_name)
|
|
221
220
|
if iam_role_arn is None:
|
|
222
221
|
iam_role_arn = self._ensure_backup_role()
|
|
@@ -261,7 +260,11 @@ class Dsql:
|
|
|
261
260
|
ensure_backup_vault(self._backup, name)
|
|
262
261
|
|
|
263
262
|
def _ensure_backup_role(self) -> str:
|
|
264
|
-
return ensure_backup_role(
|
|
263
|
+
return ensure_backup_role(
|
|
264
|
+
self._iam,
|
|
265
|
+
self.context.backup_role_name,
|
|
266
|
+
self.context.permissions_boundary,
|
|
267
|
+
)
|
|
265
268
|
|
|
266
269
|
def latest_recovery_point(self) -> dict | None:
|
|
267
270
|
"""現用クラスターの最新 recovery point (完了済み) を返す。"""
|
|
@@ -14,16 +14,31 @@ from pocket_cli.resources.aws.stack_backed import StackBackedResource
|
|
|
14
14
|
from pocket_cli.resources.inbound_template import build_template
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def no_active_rule_set_guide(region: str) -> str:
|
|
18
|
+
"""active setが無いaccount/region向けの案内。
|
|
19
|
+
|
|
20
|
+
receipt rule setはaccount/regionで1つしかactiveにできない共有物なので、
|
|
21
|
+
pocketは作成・有効化しない。名前はSESコンソールが自動作成する
|
|
22
|
+
default-rule-setに揃えて案内する(既にactiveなsetがあれば名前は問わない)。
|
|
23
|
+
"""
|
|
24
|
+
return (
|
|
25
|
+
"active な receipt rule set がありません。"
|
|
26
|
+
"account/regionで1回だけ次を実行してください"
|
|
27
|
+
"(account単位の土台なので、pocketは作成・有効化しません):\n"
|
|
28
|
+
" aws ses create-receipt-rule-set"
|
|
29
|
+
f" --rule-set-name default-rule-set --region {region}\n"
|
|
30
|
+
" aws ses set-active-receipt-rule-set"
|
|
31
|
+
f" --rule-set-name default-rule-set --region {region}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
17
35
|
def resolve_rules(
|
|
18
36
|
active: dict, ctx: InboundContext, deployed: dict | None
|
|
19
37
|
) -> tuple[str, str | None]:
|
|
20
38
|
"""明示指定なしならactive setへ末尾追加。既存bindingの漂流は拒否する。"""
|
|
21
39
|
name = active.get("Metadata", {}).get("Name")
|
|
22
40
|
if not name:
|
|
23
|
-
raise ValueError(
|
|
24
|
-
"active receipt rule set がありません。"
|
|
25
|
-
"pocket resource inbound init を実行してください"
|
|
26
|
-
)
|
|
41
|
+
raise ValueError(no_active_rule_set_guide(ctx.region))
|
|
27
42
|
if ctx.config.rule_set and ctx.config.rule_set != name:
|
|
28
43
|
raise ValueError("明示したrule_setがactive setと一致しません")
|
|
29
44
|
expected = (deployed or {}).get("RuleSet") or ctx.config.rule_set
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "magic-pocket-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.37.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.
|
|
9
|
+
"magic-pocket>=0.37.0,<0.38",
|
|
10
10
|
"click>=8.1.7",
|
|
11
11
|
"jinja2>=3.1.3",
|
|
12
12
|
"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
|
|
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.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/depot.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/builders/docker.py
RENAMED
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/cloudformation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/lambdahandler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/aws/stack_backed.py
RENAMED
|
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.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/resources/inbound_template.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
|
|
File without changes
|
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/cloudformation/vpc.yaml
RENAMED
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/django-dotenv.env
RENAMED
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/django-settings.py
RENAMED
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/pocket.Dockerfile
RENAMED
|
File without changes
|
{magic_pocket_cli-0.35.0 → magic_pocket_cli-0.37.0}/pocket_cli/templates/init/pocket_simple.toml
RENAMED
|
File without changes
|