magic-pocket-cli 0.28.2__tar.gz → 0.30.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.28.2 → magic_pocket_cli-0.30.0}/PKG-INFO +2 -2
- magic_pocket_cli-0.28.2/pocket_cli/cli/awscontainer_cli.py → magic_pocket_cli-0.30.0/pocket_cli/cli/container_cli.py +155 -125
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/deploy_cli.py +40 -27
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/destroy_cli.py +131 -99
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/image_cli.py +11 -20
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/main_cli.py +2 -2
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/migrate_cli.py +34 -2
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/removed_flags.py +1 -1
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/runtime_config_cli.py +17 -14
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/status_cli.py +8 -6
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/store_url_helper.py +4 -4
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/url_helper.py +1 -1
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/django_cli.py +37 -19
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/mediator.py +73 -24
- magic_pocket_cli-0.30.0/pocket_cli/migrations.py +225 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/cloudformation.py +6 -6
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/cloudfront.py +11 -12
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/cloudfront_keys.py +4 -4
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/cloudfront_waf.py +5 -5
- magic_pocket_cli-0.28.2/pocket_cli/resources/awscontainer.py → magic_pocket_cli-0.30.0/pocket_cli/resources/container.py +13 -11
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/rds.py +1 -1
- magic_pocket_cli-0.28.2/pocket_cli/templates/cloudformation/awscontainer.yaml → magic_pocket_cli-0.30.0/pocket_cli/templates/cloudformation/container.yaml +17 -14
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pyproject.toml +2 -2
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/.gitignore +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/__init__.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/__init__.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/aws_auth.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/backup_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/cloudfront_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/cloudfront_keys_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/cloudfront_waf_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/dsql_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/interaction.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/neon_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/permissions_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/rds_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/resource_helper.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/s3_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/tidb_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/upstash_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/vpc_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/cli/waf_cli.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/__init__.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/__init__.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/backup_common.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/__init__.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/codebuild.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/context_check.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/depot.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/docker.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/builders/dockerignore.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/ecr.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/efs.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/lambdahandler.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/poll.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/s3_utils.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/stack_backed.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/aws/state.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/backup.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/cloudfront_acm.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/dsql.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/neon.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/s3.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/tidb.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/upstash.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/resources/vpc.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cf_function_api_host.js +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cf_function_basic_auth_prelude.js +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cf_function_host_redirect_prelude.js +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cf_function_spa_auth.js +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cloudfront.yaml +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cloudfront_acm.yaml +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cloudfront_keys.yaml +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/cloudfront_waf.yaml +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/cloudformation/vpc.yaml +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/init/django-dotenv.env +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/init/django-settings.py +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.0}/pocket_cli/templates/init/pocket.Dockerfile +0 -0
- {magic_pocket_cli-0.28.2 → magic_pocket_cli-0.30.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.30.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.31,>=0.30.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
|
|
@@ -7,104 +7,128 @@ import click
|
|
|
7
7
|
from botocore.exceptions import ClientError
|
|
8
8
|
|
|
9
9
|
from pocket.context import Context
|
|
10
|
-
from pocket.runtime import get_secrets
|
|
10
|
+
from pocket.runtime import get_secrets, resolve_container_name
|
|
11
11
|
from pocket.utils import echo
|
|
12
12
|
from pocket_cli.cli.destroy_cli import (
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
_collect_container_targets,
|
|
14
|
+
_destroy_containers,
|
|
15
15
|
)
|
|
16
|
-
from pocket_cli.cli.resource_helper import require_configured
|
|
17
16
|
from pocket_cli.mediator import Mediator
|
|
18
|
-
from pocket_cli.resources.
|
|
17
|
+
from pocket_cli.resources.container import Container
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
@click.group()
|
|
22
|
-
def
|
|
21
|
+
def container():
|
|
23
22
|
pass
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
def
|
|
25
|
+
def get_container_resource(stage, container_name: str | None = None):
|
|
26
|
+
"""--container 指定 (単一 container なら省略可) の Container resource を返す。"""
|
|
27
27
|
context = Context.from_toml(stage=stage)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
28
|
+
if not context.container:
|
|
29
|
+
raise click.ClickException("container is not configured for this stage")
|
|
30
|
+
try:
|
|
31
|
+
name = resolve_container_name(context, container_name)
|
|
32
|
+
except RuntimeError as e:
|
|
33
|
+
raise click.ClickException(str(e)) from e
|
|
34
|
+
return Container(context.container[name])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_container_option = click.option(
|
|
38
|
+
"--container",
|
|
39
|
+
"container_name",
|
|
40
|
+
default=None,
|
|
41
|
+
help="対象 container 名 (1 つだけなら省略可)",
|
|
42
|
+
)
|
|
33
43
|
|
|
34
44
|
|
|
35
|
-
@
|
|
45
|
+
@container.command()
|
|
36
46
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
@_container_option
|
|
48
|
+
def yaml(stage, container_name):
|
|
49
|
+
c = get_container_resource(stage, container_name)
|
|
50
|
+
print(c.stack.yaml)
|
|
40
51
|
|
|
41
52
|
|
|
42
|
-
@
|
|
53
|
+
@container.command()
|
|
43
54
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
@_container_option
|
|
56
|
+
def yaml_diff(stage, container_name):
|
|
57
|
+
c = get_container_resource(stage, container_name)
|
|
58
|
+
print(c.stack.yaml_diff.to_json(indent=2))
|
|
47
59
|
|
|
48
60
|
|
|
49
|
-
@
|
|
61
|
+
@container.group()
|
|
50
62
|
def secrets():
|
|
51
63
|
pass
|
|
52
64
|
|
|
53
65
|
|
|
66
|
+
def _secrets_views(context: Context):
|
|
67
|
+
"""(ラベル, SecretsContext) の一覧 (shared store + 各 container store)。"""
|
|
68
|
+
views = []
|
|
69
|
+
if context.secrets:
|
|
70
|
+
views.append(("shared", context.secrets))
|
|
71
|
+
for c_name in sorted(context.container):
|
|
72
|
+
sc = context.container[c_name].secrets
|
|
73
|
+
if sc:
|
|
74
|
+
views.append((c_name, sc))
|
|
75
|
+
return views
|
|
76
|
+
|
|
77
|
+
|
|
54
78
|
@secrets.command("list")
|
|
55
79
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
56
80
|
@click.option("--show-values", is_flag=True, default=False)
|
|
57
81
|
def list_secrets(stage, show_values):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if not
|
|
82
|
+
context = Context.from_toml(stage=stage)
|
|
83
|
+
views = _secrets_views(context)
|
|
84
|
+
if not views:
|
|
61
85
|
echo.warning("secrets is not configured for this stage")
|
|
62
86
|
return
|
|
63
|
-
for
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
status = "CREATED" if key in sc.pocket_store.secrets else "NOEXIST"
|
|
78
|
-
print("%s: %s %s" % (key, pocket_secret.type, pocket_secret.options))
|
|
79
|
-
print(" - " + status)
|
|
80
|
-
if (status == "CREATED") and show_values:
|
|
81
|
-
value = sc.pocket_store.secrets[key]
|
|
82
|
-
if isinstance(value, str):
|
|
87
|
+
for label, sc in views:
|
|
88
|
+
echo.info("[%s] %s" % (label, sc.pocket_key))
|
|
89
|
+
for key, spec in sc.user.items():
|
|
90
|
+
effective_store = spec.store or sc.store
|
|
91
|
+
print("%s: %s (store=%s)" % (key, spec.name, effective_store))
|
|
92
|
+
if show_values:
|
|
93
|
+
if effective_store == "sm":
|
|
94
|
+
client = boto3.client("secretsmanager", region_name=sc.region)
|
|
95
|
+
value = client.get_secret_value(SecretId=spec.name)["SecretString"]
|
|
96
|
+
else:
|
|
97
|
+
client = boto3.client("ssm", region_name=sc.region)
|
|
98
|
+
value = client.get_parameter(Name=spec.name, WithDecryption=True)[
|
|
99
|
+
"Parameter"
|
|
100
|
+
]["Value"]
|
|
83
101
|
print(" - " + value)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
for key, pocket_secret in sc.managed.items():
|
|
103
|
+
status = "CREATED" if key in sc.pocket_store.secrets else "NOEXIST"
|
|
104
|
+
print("%s: %s %s" % (key, pocket_secret.type, pocket_secret.options))
|
|
105
|
+
print(" - " + status)
|
|
106
|
+
if (status == "CREATED") and show_values:
|
|
107
|
+
value = sc.pocket_store.secrets[key]
|
|
108
|
+
if isinstance(value, str):
|
|
109
|
+
print(" - " + value)
|
|
110
|
+
else:
|
|
111
|
+
for k, v in value.items():
|
|
112
|
+
print(f" - {k}: {v}")
|
|
87
113
|
|
|
88
114
|
|
|
89
115
|
@secrets.command()
|
|
90
116
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
91
117
|
def create_pocket_managed(stage):
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if not sc:
|
|
118
|
+
context = Context.from_toml(stage=stage)
|
|
119
|
+
if not _secrets_views(context):
|
|
95
120
|
echo.warning("secrets is not configured for this stage")
|
|
96
121
|
return
|
|
97
|
-
mediator = Mediator(
|
|
122
|
+
mediator = Mediator(context)
|
|
98
123
|
mediator.create_pocket_managed_secrets()
|
|
99
124
|
|
|
100
125
|
|
|
101
|
-
def _confirm_delete_pocket_managed_secrets(
|
|
102
|
-
sc = awscontainer.context.secrets
|
|
103
|
-
if not sc:
|
|
104
|
-
echo.warning("secrets is not configured")
|
|
105
|
-
return
|
|
126
|
+
def _confirm_delete_pocket_managed_secrets(views):
|
|
106
127
|
existing_secret_keys = [
|
|
107
|
-
|
|
128
|
+
"%s (%s)" % (key, label)
|
|
129
|
+
for label, sc in views
|
|
130
|
+
for key in sc.managed.keys()
|
|
131
|
+
if key in sc.pocket_store.secrets
|
|
108
132
|
]
|
|
109
133
|
if not existing_secret_keys:
|
|
110
134
|
echo.warning("No pocket managed secets are created yet.")
|
|
@@ -120,41 +144,46 @@ def _confirm_delete_pocket_managed_secrets(awscontainer: AwsContainer):
|
|
|
120
144
|
@secrets.command()
|
|
121
145
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
122
146
|
def delete_pocket_managed(stage):
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if
|
|
126
|
-
|
|
147
|
+
context = Context.from_toml(stage=stage)
|
|
148
|
+
views = _secrets_views(context)
|
|
149
|
+
if not views:
|
|
150
|
+
echo.warning("secrets is not configured")
|
|
151
|
+
return
|
|
152
|
+
_confirm_delete_pocket_managed_secrets(views)
|
|
153
|
+
for _label, sc in views:
|
|
154
|
+
sc.pocket_store.delete_secrets()
|
|
127
155
|
|
|
128
156
|
|
|
129
|
-
@
|
|
157
|
+
@container.command()
|
|
130
158
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
@_container_option
|
|
160
|
+
def create(stage, container_name):
|
|
161
|
+
c = get_container_resource(stage, container_name)
|
|
162
|
+
if not c.status == "NOEXIST":
|
|
134
163
|
echo.warning("AWS lambda container is already created.")
|
|
135
164
|
else:
|
|
136
165
|
mediator = Mediator(Context.from_toml(stage=stage))
|
|
137
|
-
|
|
166
|
+
c.create(mediator)
|
|
138
167
|
echo.success("Created: lambda")
|
|
139
168
|
|
|
140
169
|
|
|
141
|
-
@
|
|
170
|
+
@container.command()
|
|
142
171
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
143
172
|
@click.option("--with-secrets", is_flag=True, default=False)
|
|
144
173
|
@click.option(
|
|
145
174
|
"--yes", "-y", is_flag=True, default=False, help="確認プロンプトをスキップ"
|
|
146
175
|
)
|
|
147
176
|
def destroy(stage, with_secrets, yes):
|
|
148
|
-
"""
|
|
177
|
+
"""Container 関連リソース (全 container) を削除する。
|
|
149
178
|
|
|
150
179
|
トップレベル `pocket destroy` と同じ実装を使う (共有 ECR の削除ガード /
|
|
151
180
|
stack 削除完了待ち / CodeBuild / log group 掃除を含む)。
|
|
152
181
|
"""
|
|
153
182
|
context = Context.from_toml(stage=stage)
|
|
154
|
-
if not context.
|
|
155
|
-
echo.warning("
|
|
183
|
+
if not context.container:
|
|
184
|
+
echo.warning("container is not configured for this stage")
|
|
156
185
|
return
|
|
157
|
-
targets =
|
|
186
|
+
targets = _collect_container_targets(context, with_secrets)
|
|
158
187
|
if not targets:
|
|
159
188
|
echo.warning("削除対象のリソースが見つかりません。")
|
|
160
189
|
return
|
|
@@ -164,49 +193,47 @@ def destroy(stage, with_secrets, yes):
|
|
|
164
193
|
echo.danger("この操作は取り消せません!")
|
|
165
194
|
if not yes:
|
|
166
195
|
click.confirm(
|
|
167
|
-
"stage '%s' の
|
|
196
|
+
"stage '%s' の Container リソースを削除しますか?" % stage, abort=True
|
|
168
197
|
)
|
|
169
|
-
|
|
198
|
+
_destroy_containers(context, with_secrets)
|
|
170
199
|
|
|
171
200
|
|
|
172
|
-
@
|
|
201
|
+
@container.command()
|
|
173
202
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
203
|
+
@_container_option
|
|
204
|
+
def update(stage, container_name):
|
|
205
|
+
c = get_container_resource(stage, container_name)
|
|
206
|
+
if c.status == "NOEXIST":
|
|
177
207
|
echo.warning("AWS lambda has not created yet.")
|
|
178
208
|
return
|
|
179
|
-
if
|
|
209
|
+
if c.status == "FAILED":
|
|
180
210
|
echo.danger("AWS lambda has failed. Please check console.")
|
|
181
211
|
return
|
|
182
|
-
if
|
|
212
|
+
if c.status == "PROGRESS":
|
|
183
213
|
echo.warning("AWS lambda is updating. Please wait.")
|
|
184
214
|
return
|
|
185
215
|
mediator = Mediator(Context.from_toml(stage=stage))
|
|
186
|
-
|
|
216
|
+
c.update(mediator)
|
|
187
217
|
|
|
188
218
|
|
|
189
|
-
@
|
|
219
|
+
@container.command()
|
|
190
220
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
@_container_option
|
|
222
|
+
def status(stage, container_name):
|
|
223
|
+
c = get_container_resource(stage, container_name)
|
|
224
|
+
if c.status == "COMPLETED":
|
|
194
225
|
echo.success("Container is working!!!")
|
|
195
|
-
elif
|
|
226
|
+
elif c.status == "NOEXIST":
|
|
196
227
|
echo.warning("Container has not created yet.")
|
|
197
|
-
elif
|
|
228
|
+
elif c.status == "FAILED":
|
|
198
229
|
echo.danger("Container has failed. Please check console.")
|
|
199
230
|
else:
|
|
200
|
-
echo.warning("Container stack status: %s" %
|
|
231
|
+
echo.warning("Container stack status: %s" % c.stack.status)
|
|
201
232
|
|
|
202
233
|
|
|
203
|
-
def _resolve_lambda_target_handlers(
|
|
204
|
-
context: Context, handler_name: str | None
|
|
205
|
-
) -> list[str]:
|
|
234
|
+
def _resolve_lambda_target_handlers(c_ctx, handler_name: str | None) -> list[str]:
|
|
206
235
|
"""reload-env / status-env の対象 handler を解決する。"""
|
|
207
|
-
|
|
208
|
-
raise RuntimeError("awscontainer is not configured")
|
|
209
|
-
handlers = context.awscontainer.handlers
|
|
236
|
+
handlers = c_ctx.handlers
|
|
210
237
|
if handler_name:
|
|
211
238
|
if handler_name not in handlers:
|
|
212
239
|
raise click.ClickException(
|
|
@@ -217,15 +244,6 @@ def _resolve_lambda_target_handlers(
|
|
|
217
244
|
return list(handlers.keys())
|
|
218
245
|
|
|
219
246
|
|
|
220
|
-
def _function_name(context: Context, handler_key: str) -> str:
|
|
221
|
-
if context.awscontainer is None:
|
|
222
|
-
raise RuntimeError("awscontainer is not configured")
|
|
223
|
-
# deploy 側 (LambdaHandlerContext.function_name = resource_prefix + key) と同じ
|
|
224
|
-
# 正準名を参照する。slug から再構成すると prefix_template / namespace
|
|
225
|
-
# (既定 `pocket`) を取りこぼすため、handler context の値をそのまま使う。
|
|
226
|
-
return context.awscontainer.handlers[handler_key].function_name
|
|
227
|
-
|
|
228
|
-
|
|
229
247
|
def _fetch_lambda_env(client, function_name: str) -> dict[str, str]:
|
|
230
248
|
"""Lambda の現状 Environment.Variables を取得する。"""
|
|
231
249
|
try:
|
|
@@ -240,12 +258,13 @@ def _fetch_lambda_env(client, function_name: str) -> dict[str, str]:
|
|
|
240
258
|
return dict(config.get("Environment", {}).get("Variables", {}))
|
|
241
259
|
|
|
242
260
|
|
|
243
|
-
@
|
|
261
|
+
@container.command("reload-env")
|
|
244
262
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
263
|
+
@_container_option
|
|
245
264
|
@click.option(
|
|
246
265
|
"--handler", default=None, help="特定 handler のみ対象 (省略時は全 handler)"
|
|
247
266
|
)
|
|
248
|
-
def reload_env(stage, handler):
|
|
267
|
+
def reload_env(stage, container_name, handler):
|
|
249
268
|
"""SSM/Secrets Manager の最新値で Lambda env を即時更新する (CFn を介さない)。
|
|
250
269
|
|
|
251
270
|
deploy 時の CFn snapshot を base に、secrets (managed + user) の最新値を
|
|
@@ -257,19 +276,24 @@ def reload_env(stage, handler):
|
|
|
257
276
|
実体は CLI で直接更新、次 deploy で自己治癒)。
|
|
258
277
|
"""
|
|
259
278
|
context = Context.from_toml(stage=stage)
|
|
260
|
-
if not context.
|
|
261
|
-
raise click.ClickException("[
|
|
279
|
+
if not context.container:
|
|
280
|
+
raise click.ClickException("[container.<name>] が設定されていません")
|
|
281
|
+
try:
|
|
282
|
+
c_name = resolve_container_name(context, container_name)
|
|
283
|
+
except RuntimeError as e:
|
|
284
|
+
raise click.ClickException(str(e)) from e
|
|
285
|
+
c_ctx = context.container[c_name]
|
|
262
286
|
|
|
263
|
-
fresh_secrets = get_secrets(stage)
|
|
287
|
+
fresh_secrets = get_secrets(stage, container=c_name)
|
|
264
288
|
if not fresh_secrets:
|
|
265
289
|
echo.warning("secrets が宣言されていません。何もしません。")
|
|
266
290
|
return
|
|
267
291
|
|
|
268
|
-
lambda_client = boto3.client("lambda", region_name=
|
|
269
|
-
targets = _resolve_lambda_target_handlers(
|
|
292
|
+
lambda_client = boto3.client("lambda", region_name=c_ctx.region)
|
|
293
|
+
targets = _resolve_lambda_target_handlers(c_ctx, handler)
|
|
270
294
|
|
|
271
295
|
for h_name in targets:
|
|
272
|
-
function_name =
|
|
296
|
+
function_name = c_ctx.handlers[h_name].function_name
|
|
273
297
|
current = _fetch_lambda_env(lambda_client, function_name)
|
|
274
298
|
new_env = {**current, **fresh_secrets}
|
|
275
299
|
if new_env == current:
|
|
@@ -288,24 +312,30 @@ def reload_env(stage, handler):
|
|
|
288
312
|
echo.log(" - %s" % k)
|
|
289
313
|
|
|
290
314
|
|
|
291
|
-
@
|
|
315
|
+
@container.command("status-env")
|
|
292
316
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
317
|
+
@_container_option
|
|
293
318
|
@click.option(
|
|
294
319
|
"--handler", default=None, help="特定 handler のみ対象 (省略時は全 handler)"
|
|
295
320
|
)
|
|
296
|
-
def status_env(stage, handler):
|
|
321
|
+
def status_env(stage, container_name, handler):
|
|
297
322
|
"""Lambda の現在 env と SSM/SM 上の宣言値の drift を表示する。"""
|
|
298
323
|
context = Context.from_toml(stage=stage)
|
|
299
|
-
if not context.
|
|
300
|
-
raise click.ClickException("[
|
|
324
|
+
if not context.container:
|
|
325
|
+
raise click.ClickException("[container.<name>] が設定されていません")
|
|
326
|
+
try:
|
|
327
|
+
c_name = resolve_container_name(context, container_name)
|
|
328
|
+
except RuntimeError as e:
|
|
329
|
+
raise click.ClickException(str(e)) from e
|
|
330
|
+
c_ctx = context.container[c_name]
|
|
301
331
|
|
|
302
|
-
fresh_secrets = get_secrets(stage)
|
|
303
|
-
lambda_client = boto3.client("lambda", region_name=
|
|
304
|
-
targets = _resolve_lambda_target_handlers(
|
|
332
|
+
fresh_secrets = get_secrets(stage, container=c_name)
|
|
333
|
+
lambda_client = boto3.client("lambda", region_name=c_ctx.region)
|
|
334
|
+
targets = _resolve_lambda_target_handlers(c_ctx, handler)
|
|
305
335
|
|
|
306
336
|
any_drift = False
|
|
307
337
|
for h_name in targets:
|
|
308
|
-
function_name =
|
|
338
|
+
function_name = c_ctx.handlers[h_name].function_name
|
|
309
339
|
current = _fetch_lambda_env(lambda_client, function_name)
|
|
310
340
|
drift = [k for k in fresh_secrets if current.get(k) != fresh_secrets[k]]
|
|
311
341
|
echo.info(
|
|
@@ -321,20 +351,20 @@ def status_env(stage, handler):
|
|
|
321
351
|
any_drift = True
|
|
322
352
|
if any_drift:
|
|
323
353
|
echo.warning(
|
|
324
|
-
"drift
|
|
325
|
-
"`pocket resource awscontainer reload-env` で同期できます。"
|
|
354
|
+
"drift があります。`pocket resource container reload-env` で同期できます。"
|
|
326
355
|
)
|
|
327
356
|
else:
|
|
328
357
|
echo.success("drift なし。Lambda env と secrets は同期されています。")
|
|
329
358
|
|
|
330
359
|
|
|
331
|
-
@
|
|
360
|
+
@container.command()
|
|
332
361
|
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
362
|
+
@_container_option
|
|
333
363
|
@click.option("--openpath")
|
|
334
|
-
def url(stage, openpath):
|
|
335
|
-
|
|
336
|
-
if
|
|
337
|
-
if endpoint :=
|
|
364
|
+
def url(stage, container_name, openpath):
|
|
365
|
+
c = get_container_resource(stage, container_name)
|
|
366
|
+
if c.status == "COMPLETED":
|
|
367
|
+
if endpoint := c.endpoints.get("wsgi"):
|
|
338
368
|
echo.success(f"wsgi url: {endpoint}")
|
|
339
369
|
if openpath:
|
|
340
370
|
webbrowser.open(endpoint + "/" + openpath)
|
|
@@ -5,16 +5,17 @@ import click
|
|
|
5
5
|
|
|
6
6
|
from pocket.context import Context, deploy_hash_report
|
|
7
7
|
from pocket.utils import echo
|
|
8
|
+
from pocket_cli import migrations
|
|
8
9
|
from pocket_cli.cli import interaction
|
|
9
10
|
from pocket_cli.cli.removed_flags import removed_skip_check_existing
|
|
10
11
|
from pocket_cli.mediator import Mediator
|
|
11
12
|
from pocket_cli.resources.aws.state import StateStore, create_state_store
|
|
12
|
-
from pocket_cli.resources.awscontainer import AwsContainer
|
|
13
13
|
from pocket_cli.resources.backup import Backup
|
|
14
14
|
from pocket_cli.resources.cloudfront import CloudFront
|
|
15
15
|
from pocket_cli.resources.cloudfront_acm import CloudFrontAcm
|
|
16
16
|
from pocket_cli.resources.cloudfront_keys import CloudFrontKeys
|
|
17
17
|
from pocket_cli.resources.cloudfront_waf import CloudFrontWaf
|
|
18
|
+
from pocket_cli.resources.container import Container
|
|
18
19
|
from pocket_cli.resources.dsql import Dsql
|
|
19
20
|
from pocket_cli.resources.neon import Neon
|
|
20
21
|
from pocket_cli.resources.rds import Rds
|
|
@@ -25,10 +26,12 @@ from pocket_cli.resources.vpc import Vpc
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def _append_infra_resources(resources, context: Context, state_bucket: str):
|
|
28
|
-
"""VPC / RDS / CloudFrontKeys /
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
"""VPC / RDS / CloudFrontKeys / Container をまとめて追加"""
|
|
30
|
+
managed_vpc_names: set[str] = set()
|
|
31
|
+
for c_ctx in context.container.values():
|
|
32
|
+
if c_ctx.vpc and c_ctx.vpc.manage and c_ctx.vpc.name not in managed_vpc_names:
|
|
33
|
+
managed_vpc_names.add(c_ctx.vpc.name)
|
|
34
|
+
resources.append(Vpc(c_ctx.vpc))
|
|
32
35
|
if context.dsql:
|
|
33
36
|
resources.append(Dsql(context.dsql))
|
|
34
37
|
if context.rds:
|
|
@@ -42,14 +45,14 @@ def _append_infra_resources(resources, context: Context, state_bucket: str):
|
|
|
42
45
|
for _name, cf_ctx in context.cloudfront.items():
|
|
43
46
|
if cf_ctx.signing_key:
|
|
44
47
|
resources.append(CloudFrontKeys(cf_ctx))
|
|
45
|
-
|
|
48
|
+
for c_name in sorted(context.container):
|
|
46
49
|
resources.append(
|
|
47
|
-
|
|
48
|
-
context.
|
|
50
|
+
Container(
|
|
51
|
+
context.container[c_name],
|
|
49
52
|
state_bucket=state_bucket,
|
|
50
53
|
rds_context=context.rds,
|
|
51
54
|
dsql_context=context.dsql,
|
|
52
|
-
scheduler_context=context.scheduler,
|
|
55
|
+
scheduler_context=context.scheduler.get(c_name),
|
|
53
56
|
)
|
|
54
57
|
)
|
|
55
58
|
|
|
@@ -174,25 +177,30 @@ def deploy_resources(context: Context, *, state_bucket: str = ""):
|
|
|
174
177
|
hook()
|
|
175
178
|
|
|
176
179
|
|
|
177
|
-
def build_image(context: Context, *, tag: str) -> str:
|
|
178
|
-
"""
|
|
180
|
+
def build_image(context: Context, *, tag: str) -> list[str]:
|
|
181
|
+
"""全 container image を指定 tag で build & push する (deploy はしない)。
|
|
179
182
|
|
|
180
183
|
build once 用。codebuild backend は source upload に state bucket を要するため、
|
|
181
|
-
deploy と同様に先に state bucket を確保してから build
|
|
184
|
+
deploy と同様に先に state bucket を確保してから build する。
|
|
185
|
+
戻り値は ecr_name:tag のリスト (container 名順)。
|
|
182
186
|
"""
|
|
183
|
-
if context.
|
|
184
|
-
raise click.ClickException("
|
|
187
|
+
if not context.container:
|
|
188
|
+
raise click.ClickException("container がこの stage に設定されていません。")
|
|
185
189
|
state_store = _create_state_store(context)
|
|
186
190
|
state_store.ensure_bucket()
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
targets = []
|
|
192
|
+
for c_name in sorted(context.container):
|
|
193
|
+
c_ctx = context.container[c_name]
|
|
194
|
+
echo.log("Building container '%s'..." % c_name)
|
|
195
|
+
Container(c_ctx, state_bucket=state_store.bucket_name).build(tag)
|
|
196
|
+
targets.append(f"{c_ctx.ecr_name}:{tag}")
|
|
197
|
+
return targets
|
|
190
198
|
|
|
191
199
|
|
|
192
200
|
def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
|
|
193
201
|
"""deploy / promote 共通のパイプライン本体。
|
|
194
202
|
|
|
195
|
-
promote
|
|
203
|
+
promote 時は各 container の promote_commit_hash が設定済みで、
|
|
196
204
|
deploy_init 内の image build が retag に置き換わる以外は deploy と同一。
|
|
197
205
|
"""
|
|
198
206
|
# DEPLOY_HASH の解決結果を deploy 時に 1 回可視化する (env 伝播漏れで
|
|
@@ -206,6 +214,9 @@ def _deploy_pipeline(context: Context, *, openpath=None, skip_frontend=False):
|
|
|
206
214
|
state_bucket = state_store.bucket_name
|
|
207
215
|
deploy_init_resources(context, state_bucket=state_bucket)
|
|
208
216
|
deploy_resources(context, state_bucket=state_bucket)
|
|
217
|
+
# リリース跨ぎ移行の掃除フェーズ (旧配置の削除は deploy 成功後にしか
|
|
218
|
+
# できないため、cloudfront 切替完了後のここで毎回呼ぶ。冪等)
|
|
219
|
+
migrations.run_deploy_cleanup(context)
|
|
209
220
|
upload_managed_assets(context)
|
|
210
221
|
if not skip_frontend:
|
|
211
222
|
deploy_frontend(context)
|
|
@@ -254,9 +265,10 @@ def promote(stage: str, commit_hash, openpath, skip_frontend, yes):
|
|
|
254
265
|
interaction.set_assume_yes(yes)
|
|
255
266
|
check_aws_credentials()
|
|
256
267
|
context = Context.from_toml(stage=stage)
|
|
257
|
-
if context.
|
|
258
|
-
raise click.ClickException("
|
|
259
|
-
context.
|
|
268
|
+
if not context.container:
|
|
269
|
+
raise click.ClickException("container がこの stage に設定されていません。")
|
|
270
|
+
for c_ctx in context.container.values():
|
|
271
|
+
c_ctx.promote_commit_hash = commit_hash
|
|
260
272
|
_deploy_pipeline(context, openpath=openpath, skip_frontend=skip_frontend)
|
|
261
273
|
|
|
262
274
|
|
|
@@ -276,10 +288,11 @@ def _get_deploy_url(context: Context) -> str | None:
|
|
|
276
288
|
if domain:
|
|
277
289
|
return f"https://{domain}"
|
|
278
290
|
|
|
279
|
-
# フォールバック: API Gateway
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
291
|
+
# フォールバック: API Gateway (container 名順で最初に見つかった endpoint)
|
|
292
|
+
for c_name in sorted(context.container):
|
|
293
|
+
endpoints = Container(context.container[c_name]).endpoints
|
|
294
|
+
if "wsgi" in endpoints:
|
|
295
|
+
return endpoints["wsgi"]
|
|
296
|
+
if endpoints:
|
|
297
|
+
return next(iter(endpoints.values()))
|
|
285
298
|
return None
|