magic-pocket-cli 0.2.0__py3-none-any.whl
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.2.0.dist-info/METADATA +14 -0
- magic_pocket_cli-0.2.0.dist-info/RECORD +65 -0
- magic_pocket_cli-0.2.0.dist-info/WHEEL +4 -0
- magic_pocket_cli-0.2.0.dist-info/entry_points.txt +2 -0
- pocket_cli/__init__.py +0 -0
- pocket_cli/cli/__init__.py +0 -0
- pocket_cli/cli/aws_auth.py +48 -0
- pocket_cli/cli/awscontainer_cli.py +328 -0
- pocket_cli/cli/cloudfront_cli.py +116 -0
- pocket_cli/cli/cloudfront_keys_cli.py +68 -0
- pocket_cli/cli/cloudfront_waf_cli.py +68 -0
- pocket_cli/cli/deploy_cli.py +274 -0
- pocket_cli/cli/destroy_cli.py +358 -0
- pocket_cli/cli/dsql_cli.py +60 -0
- pocket_cli/cli/main_cli.py +91 -0
- pocket_cli/cli/migrate_cli.py +148 -0
- pocket_cli/cli/neon_cli.py +97 -0
- pocket_cli/cli/permissions_cli.py +46 -0
- pocket_cli/cli/rds_cli.py +63 -0
- pocket_cli/cli/runtime_config_cli.py +185 -0
- pocket_cli/cli/s3_cli.py +69 -0
- pocket_cli/cli/status_cli.py +56 -0
- pocket_cli/cli/tidb_cli.py +73 -0
- pocket_cli/cli/vpc_cli.py +92 -0
- pocket_cli/cli/waf_cli.py +182 -0
- pocket_cli/django_cli.py +412 -0
- pocket_cli/mediator.py +220 -0
- pocket_cli/resources/__init__.py +0 -0
- pocket_cli/resources/aws/__init__.py +0 -0
- pocket_cli/resources/aws/builders/__init__.py +57 -0
- pocket_cli/resources/aws/builders/codebuild.py +363 -0
- pocket_cli/resources/aws/builders/depot.py +84 -0
- pocket_cli/resources/aws/builders/docker.py +34 -0
- pocket_cli/resources/aws/builders/dockerignore.py +44 -0
- pocket_cli/resources/aws/cloudformation.py +790 -0
- pocket_cli/resources/aws/ecr.py +145 -0
- pocket_cli/resources/aws/efs.py +138 -0
- pocket_cli/resources/aws/lambdahandler.py +182 -0
- pocket_cli/resources/aws/s3_utils.py +58 -0
- pocket_cli/resources/aws/state.py +74 -0
- pocket_cli/resources/awscontainer.py +265 -0
- pocket_cli/resources/cloudfront.py +491 -0
- pocket_cli/resources/cloudfront_acm.py +55 -0
- pocket_cli/resources/cloudfront_keys.py +81 -0
- pocket_cli/resources/cloudfront_waf.py +67 -0
- pocket_cli/resources/dsql.py +142 -0
- pocket_cli/resources/neon.py +353 -0
- pocket_cli/resources/rds.py +680 -0
- pocket_cli/resources/s3.py +307 -0
- pocket_cli/resources/tidb.py +298 -0
- pocket_cli/resources/upstash.py +152 -0
- pocket_cli/resources/vpc.py +67 -0
- pocket_cli/templates/cloudformation/awscontainer.yaml +516 -0
- pocket_cli/templates/cloudformation/cf_function_api_host.js +5 -0
- pocket_cli/templates/cloudformation/cf_function_spa_auth.js +28 -0
- pocket_cli/templates/cloudformation/cf_function_spa_fallback.js +8 -0
- pocket_cli/templates/cloudformation/cloudfront.yaml +309 -0
- pocket_cli/templates/cloudformation/cloudfront_acm.yaml +43 -0
- pocket_cli/templates/cloudformation/cloudfront_keys.yaml +32 -0
- pocket_cli/templates/cloudformation/cloudfront_waf.yaml +97 -0
- pocket_cli/templates/cloudformation/vpc.yaml +213 -0
- pocket_cli/templates/init/django-dotenv.env +3 -0
- pocket_cli/templates/init/django-settings.py +140 -0
- pocket_cli/templates/init/pocket.Dockerfile +26 -0
- pocket_cli/templates/init/pocket_simple.toml +31 -0
pocket_cli/cli/s3_cli.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from pocket.context import Context
|
|
6
|
+
from pocket.utils import echo
|
|
7
|
+
from pocket_cli.resources.s3 import S3
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def s3():
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_s3_resource(stage):
|
|
16
|
+
context = Context.from_toml(stage=stage)
|
|
17
|
+
if not context.s3:
|
|
18
|
+
echo.danger("s3 is not configured for this stage")
|
|
19
|
+
raise Exception("s3 is not configured for this stage")
|
|
20
|
+
return S3(context=context.s3)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@s3.command()
|
|
24
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
25
|
+
def context(stage):
|
|
26
|
+
storage = get_s3_resource(stage)
|
|
27
|
+
pprint(storage.context.model_dump())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@s3.command()
|
|
31
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
32
|
+
def create(stage):
|
|
33
|
+
"""S3 バケットを作成、または既存バケットの設定 (PAB / CORS / versioning /
|
|
34
|
+
lifecycle) を冪等に reconcile する。"""
|
|
35
|
+
storage = get_s3_resource(stage)
|
|
36
|
+
already_exists = storage.exists()
|
|
37
|
+
storage.ensure_exists()
|
|
38
|
+
if already_exists:
|
|
39
|
+
echo.success(
|
|
40
|
+
"Reconciled: bucket %s (PAB / CORS / versioning / lifecycle)"
|
|
41
|
+
% storage.context.bucket_name
|
|
42
|
+
)
|
|
43
|
+
else:
|
|
44
|
+
echo.success("Created: bucket %s" % storage.context.bucket_name)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@s3.command()
|
|
48
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
49
|
+
def destroy(stage):
|
|
50
|
+
storage = get_s3_resource(stage)
|
|
51
|
+
if not storage.exists():
|
|
52
|
+
echo.warning("No S3 bucket found.")
|
|
53
|
+
return
|
|
54
|
+
echo.danger("S3バケットの全データが失われます。")
|
|
55
|
+
click.confirm(
|
|
56
|
+
"バケット '%s' を削除しますか?" % storage.context.bucket_name, abort=True
|
|
57
|
+
)
|
|
58
|
+
storage.delete()
|
|
59
|
+
echo.success("S3 bucket was deleted.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@s3.command()
|
|
63
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
64
|
+
def status(stage):
|
|
65
|
+
storage = get_s3_resource(stage)
|
|
66
|
+
if storage.exists():
|
|
67
|
+
echo.success("Storage found")
|
|
68
|
+
else:
|
|
69
|
+
echo.warning("Storage not found")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from pocket.context import Context
|
|
4
|
+
from pocket.resources.aws.secretsmanager import PocketSecretIsNotReady
|
|
5
|
+
from pocket.utils import echo
|
|
6
|
+
from pocket_cli.cli.deploy_cli import get_resources
|
|
7
|
+
from pocket_cli.resources.awscontainer import AwsContainer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def show_status_message(resource):
|
|
11
|
+
target_name = resource.__class__.__name__
|
|
12
|
+
message = f"{target_name} status: {resource.status}"
|
|
13
|
+
echo_fn = {
|
|
14
|
+
"NOEXIST": echo.info,
|
|
15
|
+
"REQUIRE_UPDATE": echo.warning,
|
|
16
|
+
"PROGRESS": echo.warning,
|
|
17
|
+
"COMPLETED": echo.success,
|
|
18
|
+
"FAILED": echo.danger,
|
|
19
|
+
}[resource.status]
|
|
20
|
+
echo_fn(message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def show_info_message(resource):
|
|
24
|
+
if hasattr(resource, "description"):
|
|
25
|
+
echo.info(resource.description)
|
|
26
|
+
if isinstance(resource, AwsContainer) and resource.context.secrets:
|
|
27
|
+
if resource.context.secrets.managed:
|
|
28
|
+
try:
|
|
29
|
+
_ = resource.context.secrets.pocket_store.arn
|
|
30
|
+
except PocketSecretIsNotReady:
|
|
31
|
+
echo.warning(
|
|
32
|
+
"Because pocket managed secrets is not ready yet, "
|
|
33
|
+
"the context is not ready to use."
|
|
34
|
+
)
|
|
35
|
+
echo.warning("Just use it for reference.")
|
|
36
|
+
echo.info("You can create pocket managed secrets by running the below.")
|
|
37
|
+
echo.info("pocket resource awscontainer secrets create-pocket-managed")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
_ = resource.context.secrets.allowed_sm_resources
|
|
41
|
+
except PocketSecretIsNotReady:
|
|
42
|
+
echo.warning("Please create pocket secrets first.")
|
|
43
|
+
return
|
|
44
|
+
print(resource.context.model_dump_json(indent=2))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.command()
|
|
48
|
+
@click.option("--show-info", is_flag=True, default=False)
|
|
49
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
50
|
+
def status(stage, show_info):
|
|
51
|
+
context = Context.from_toml(stage=stage)
|
|
52
|
+
resources = get_resources(context)
|
|
53
|
+
for resource in resources:
|
|
54
|
+
show_status_message(resource)
|
|
55
|
+
if show_info:
|
|
56
|
+
show_info_message(resource)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from pocket.context import Context
|
|
6
|
+
from pocket.utils import echo
|
|
7
|
+
from pocket_cli.resources.tidb import TiDb
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def tidb():
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_tidb_resource(stage):
|
|
16
|
+
context = Context.from_toml(stage=stage)
|
|
17
|
+
if not context.tidb:
|
|
18
|
+
echo.danger("tidb is not configured for this stage")
|
|
19
|
+
raise ValueError("tidb is not configured for this stage")
|
|
20
|
+
return TiDb(context=context.tidb)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@tidb.command()
|
|
24
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
25
|
+
def context(stage):
|
|
26
|
+
resource = get_tidb_resource(stage)
|
|
27
|
+
pprint(resource.context.model_dump())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@tidb.command()
|
|
31
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
32
|
+
def create(stage):
|
|
33
|
+
resource = get_tidb_resource(stage)
|
|
34
|
+
resource.create()
|
|
35
|
+
echo.success("TiDB cluster and database created")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@tidb.command()
|
|
39
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
40
|
+
def reset_database(stage):
|
|
41
|
+
resource = get_tidb_resource(stage)
|
|
42
|
+
resource.reset_database()
|
|
43
|
+
echo.success("Reset database")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@tidb.command()
|
|
47
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
48
|
+
def delete(stage):
|
|
49
|
+
resource = get_tidb_resource(stage)
|
|
50
|
+
resource.delete_cluster()
|
|
51
|
+
echo.success("Cluster was deleted successfully.")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@tidb.command()
|
|
55
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
56
|
+
def status(stage):
|
|
57
|
+
resource = get_tidb_resource(stage)
|
|
58
|
+
if resource.project:
|
|
59
|
+
echo.success("Project found")
|
|
60
|
+
else:
|
|
61
|
+
echo.warning("Project not found")
|
|
62
|
+
return
|
|
63
|
+
if resource.cluster:
|
|
64
|
+
echo.success(
|
|
65
|
+
"Cluster found: %s (%s)" % (resource.cluster.name, resource.cluster.status)
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
echo.warning("Cluster not found")
|
|
69
|
+
return
|
|
70
|
+
if resource.cluster.status == "ACTIVE":
|
|
71
|
+
echo.success("Database url: %s" % resource.database_url)
|
|
72
|
+
else:
|
|
73
|
+
echo.warning("Cluster status: %s" % resource.cluster.status)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from pocket.general_context import VpcContext
|
|
4
|
+
from pocket.utils import echo
|
|
5
|
+
from pocket_cli.resources.vpc import Vpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
def vpc():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_vpc_resource():
|
|
14
|
+
vpc_context = VpcContext.from_toml()
|
|
15
|
+
return Vpc(vpc_context)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@vpc.command()
|
|
19
|
+
def yaml():
|
|
20
|
+
vpc = get_vpc_resource()
|
|
21
|
+
print(vpc.stack.yaml)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@vpc.command()
|
|
25
|
+
def yaml_diff():
|
|
26
|
+
vpc = get_vpc_resource()
|
|
27
|
+
print(vpc.stack.yaml_diff.to_json(indent=2))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@vpc.command()
|
|
31
|
+
def create():
|
|
32
|
+
vpc = get_vpc_resource()
|
|
33
|
+
if not vpc.context.manage:
|
|
34
|
+
echo.danger("外部 VPC は他で管理されています。")
|
|
35
|
+
return
|
|
36
|
+
if not vpc.status == "NOEXIST":
|
|
37
|
+
echo.warning("AWS vpc is already created.")
|
|
38
|
+
else:
|
|
39
|
+
vpc.create()
|
|
40
|
+
echo.success("Created: vpc")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@vpc.command()
|
|
44
|
+
def update():
|
|
45
|
+
vpc = get_vpc_resource()
|
|
46
|
+
if not vpc.context.manage:
|
|
47
|
+
echo.danger("外部 VPC は他で管理されています。")
|
|
48
|
+
return
|
|
49
|
+
if vpc.status == "NOEXIST":
|
|
50
|
+
echo.warning("vpc has not created yet.")
|
|
51
|
+
return
|
|
52
|
+
if vpc.status == "FAILED":
|
|
53
|
+
echo.danger("vpc has failed. Please check console.")
|
|
54
|
+
return
|
|
55
|
+
if vpc.status == "PROGRESS":
|
|
56
|
+
echo.warning("vpc is updating. Please wait.")
|
|
57
|
+
return
|
|
58
|
+
vpc.update()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@vpc.command()
|
|
62
|
+
def destroy():
|
|
63
|
+
vpc = get_vpc_resource()
|
|
64
|
+
if not vpc.context.manage:
|
|
65
|
+
echo.danger("外部 VPC は他で管理されています。")
|
|
66
|
+
return
|
|
67
|
+
if vpc.stack.consumers:
|
|
68
|
+
echo.danger("VPC に consumer がいるため削除できません:")
|
|
69
|
+
for c in vpc.stack.consumers:
|
|
70
|
+
echo.info(" - %s" % c)
|
|
71
|
+
return
|
|
72
|
+
has_stack = vpc.stack.status != "NOEXIST"
|
|
73
|
+
has_efs = vpc.efs and vpc.efs.exists()
|
|
74
|
+
if not has_stack and not has_efs:
|
|
75
|
+
echo.warning("No VPC resources found.")
|
|
76
|
+
return
|
|
77
|
+
click.confirm("VPC を削除しますか?", abort=True)
|
|
78
|
+
vpc.delete()
|
|
79
|
+
echo.success("VPC was destroyed.")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@vpc.command()
|
|
83
|
+
def status():
|
|
84
|
+
vpc = get_vpc_resource()
|
|
85
|
+
if vpc.status == "COMPLETED":
|
|
86
|
+
echo.success("Vpc has been created.")
|
|
87
|
+
elif vpc.status == "NOEXIST":
|
|
88
|
+
echo.warning("Vpc has not created yet.")
|
|
89
|
+
elif vpc.status == "FAILED":
|
|
90
|
+
echo.danger("Vpc has failed. Please check console.")
|
|
91
|
+
else:
|
|
92
|
+
echo.warning("Vpc stack status: %s" % vpc.stack.status)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""`pocket waf ip ...` — IPSet を CFN を介さず直接更新する CLI。
|
|
2
|
+
|
|
3
|
+
WebACL / IPSet 自体は CFn (CloudFrontWafStack) が us-east-1 に作成し、
|
|
4
|
+
Addresses は CFn template 上は常に空。実際の CIDR 一覧はこの CLI が
|
|
5
|
+
`update_ip_set` boto3 で side-channel 更新する (1 件追加で CFn を回したくない
|
|
6
|
+
ため)。CFn 視点では Addresses は常に drift 状態だが、これは仕様。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import urllib.request
|
|
12
|
+
|
|
13
|
+
import boto3
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from pocket.context import CloudFrontContext, Context
|
|
17
|
+
from pocket.utils import echo
|
|
18
|
+
from pocket_cli.resources.aws.cloudformation import CloudFrontWafStack
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
def waf():
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@waf.group()
|
|
27
|
+
def ip():
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_cf_context(stage: str, name: str) -> CloudFrontContext:
|
|
32
|
+
context = Context.from_toml(stage=stage)
|
|
33
|
+
if not context.cloudfront:
|
|
34
|
+
raise click.ClickException("cloudfront is not configured for this stage")
|
|
35
|
+
if name not in context.cloudfront:
|
|
36
|
+
raise click.ClickException("cloudfront '%s' is not configured" % name)
|
|
37
|
+
cf_ctx = context.cloudfront[name]
|
|
38
|
+
if cf_ctx.waf is None:
|
|
39
|
+
raise click.ClickException(
|
|
40
|
+
"cloudfront '%s' has no [cloudfront.%s.waf] block" % (name, name)
|
|
41
|
+
)
|
|
42
|
+
if not cf_ctx.waf.enable_ip_set:
|
|
43
|
+
raise click.ClickException(
|
|
44
|
+
"cloudfront '%s' is configured with [cloudfront.%s.waf]"
|
|
45
|
+
" enable_ip_set = false, so there is no IPSet to operate on."
|
|
46
|
+
" Set `enable_ip_set = true` (default) to use `pocket waf ip ...`."
|
|
47
|
+
% (name, name)
|
|
48
|
+
)
|
|
49
|
+
return cf_ctx
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_ip_set_meta(cf_ctx: CloudFrontContext) -> tuple[str, str]:
|
|
53
|
+
"""CFn stack output から IPSet の (Name, Id) を取得する。"""
|
|
54
|
+
stack = CloudFrontWafStack(cf_ctx)
|
|
55
|
+
output = stack.output
|
|
56
|
+
if not output:
|
|
57
|
+
raise click.ClickException(
|
|
58
|
+
"WAF stack '%s' が見つかりません。先に `pocket deploy` を実行してください。"
|
|
59
|
+
% stack.name
|
|
60
|
+
)
|
|
61
|
+
name = output.get("IPSetName")
|
|
62
|
+
set_id = output.get("IPSetId")
|
|
63
|
+
if not name or not set_id:
|
|
64
|
+
raise click.ClickException(
|
|
65
|
+
"WAF stack output に IPSetName / IPSetId がありません: %s" % stack.name
|
|
66
|
+
)
|
|
67
|
+
return name, set_id
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _wafv2_client():
|
|
71
|
+
# Scope=CLOUDFRONT は us-east-1 でしか操作できない
|
|
72
|
+
return boto3.client("wafv2", region_name="us-east-1")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _fetch_ip_set(client, set_name: str, set_id: str) -> tuple[list[str], str]:
|
|
76
|
+
res = client.get_ip_set(Name=set_name, Scope="CLOUDFRONT", Id=set_id)
|
|
77
|
+
return list(res["IPSet"]["Addresses"]), res["LockToken"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _write_ip_set(client, set_name: str, set_id: str, addresses: list[str], lock: str):
|
|
81
|
+
client.update_ip_set(
|
|
82
|
+
Name=set_name,
|
|
83
|
+
Scope="CLOUDFRONT",
|
|
84
|
+
Id=set_id,
|
|
85
|
+
Addresses=addresses,
|
|
86
|
+
LockToken=lock,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _detect_self_ipv4() -> str:
|
|
91
|
+
"""1 次: AWS checkip、fallback: ipify。/32 を付けた CIDR を返す。"""
|
|
92
|
+
for url in ("https://checkip.amazonaws.com", "https://api.ipify.org"):
|
|
93
|
+
try:
|
|
94
|
+
with urllib.request.urlopen(url, timeout=5) as r:
|
|
95
|
+
ip = r.read().decode("utf-8").strip()
|
|
96
|
+
if ip:
|
|
97
|
+
return f"{ip}/32"
|
|
98
|
+
except Exception as e: # noqa: BLE001
|
|
99
|
+
echo.warning("IP 検出失敗 (%s): %s" % (url, e))
|
|
100
|
+
raise click.ClickException("自分の Global IP を取得できませんでした")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@ip.command("list")
|
|
104
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
105
|
+
@click.option("--name", required=True, help="CloudFront name (pocket.toml の key)")
|
|
106
|
+
def ip_list(stage, name):
|
|
107
|
+
"""IPSet 内 CIDR 一覧を表示する。"""
|
|
108
|
+
cf_ctx = _get_cf_context(stage, name)
|
|
109
|
+
set_name, set_id = _get_ip_set_meta(cf_ctx)
|
|
110
|
+
client = _wafv2_client()
|
|
111
|
+
addresses, _ = _fetch_ip_set(client, set_name, set_id)
|
|
112
|
+
if not addresses:
|
|
113
|
+
echo.warning(
|
|
114
|
+
"IPSet は空です (deny-all 状態)。"
|
|
115
|
+
"`pocket waf ip add self --name %s --stage %s` で自分の IP を"
|
|
116
|
+
"追加してください。" % (name, stage)
|
|
117
|
+
)
|
|
118
|
+
return
|
|
119
|
+
for cidr in addresses:
|
|
120
|
+
click.echo(cidr)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@ip.command("add")
|
|
124
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
125
|
+
@click.option("--name", required=True, help="CloudFront name (pocket.toml の key)")
|
|
126
|
+
@click.argument("cidr")
|
|
127
|
+
def ip_add(stage, name, cidr):
|
|
128
|
+
"""任意 CIDR を IPSet に追加。`self` で自分の IP を追加 (重複は dedup)。"""
|
|
129
|
+
cf_ctx = _get_cf_context(stage, name)
|
|
130
|
+
set_name, set_id = _get_ip_set_meta(cf_ctx)
|
|
131
|
+
client = _wafv2_client()
|
|
132
|
+
if cidr == "self":
|
|
133
|
+
cidr = _detect_self_ipv4()
|
|
134
|
+
echo.info("検出した自分の IP: %s" % cidr)
|
|
135
|
+
addresses, lock = _fetch_ip_set(client, set_name, set_id)
|
|
136
|
+
if cidr in addresses:
|
|
137
|
+
echo.info("%s は既に登録済みです。" % cidr)
|
|
138
|
+
return
|
|
139
|
+
new_addresses = addresses + [cidr]
|
|
140
|
+
_write_ip_set(client, set_name, set_id, new_addresses, lock)
|
|
141
|
+
echo.success("追加しました: %s (合計 %d entries)" % (cidr, len(new_addresses)))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@ip.command("remove")
|
|
145
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
146
|
+
@click.option("--name", required=True, help="CloudFront name (pocket.toml の key)")
|
|
147
|
+
@click.argument("cidr")
|
|
148
|
+
def ip_remove(stage, name, cidr):
|
|
149
|
+
"""指定 CIDR を IPSet から削除。"""
|
|
150
|
+
cf_ctx = _get_cf_context(stage, name)
|
|
151
|
+
set_name, set_id = _get_ip_set_meta(cf_ctx)
|
|
152
|
+
client = _wafv2_client()
|
|
153
|
+
addresses, lock = _fetch_ip_set(client, set_name, set_id)
|
|
154
|
+
if cidr not in addresses:
|
|
155
|
+
echo.warning("%s は IPSet に存在しません。" % cidr)
|
|
156
|
+
return
|
|
157
|
+
new_addresses = [a for a in addresses if a != cidr]
|
|
158
|
+
_write_ip_set(client, set_name, set_id, new_addresses, lock)
|
|
159
|
+
echo.success("削除しました: %s (残り %d entries)" % (cidr, len(new_addresses)))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@ip.command("clear")
|
|
163
|
+
@click.option("--stage", envvar="POCKET_DEPLOY_STAGE", prompt=True)
|
|
164
|
+
@click.option("--name", required=True, help="CloudFront name (pocket.toml の key)")
|
|
165
|
+
@click.option("--yes", is_flag=True, default=False, help="確認プロンプトをスキップ")
|
|
166
|
+
def ip_clear(stage, name, yes):
|
|
167
|
+
"""IPSet を空にする (= deny-all 状態に戻す)。確認プロンプトあり。"""
|
|
168
|
+
cf_ctx = _get_cf_context(stage, name)
|
|
169
|
+
set_name, set_id = _get_ip_set_meta(cf_ctx)
|
|
170
|
+
client = _wafv2_client()
|
|
171
|
+
addresses, lock = _fetch_ip_set(client, set_name, set_id)
|
|
172
|
+
if not addresses:
|
|
173
|
+
echo.info("IPSet は既に空です。")
|
|
174
|
+
return
|
|
175
|
+
if not yes:
|
|
176
|
+
click.confirm(
|
|
177
|
+
"%d 件の CIDR を全削除して deny-all 状態にします。よろしいですか?"
|
|
178
|
+
% len(addresses),
|
|
179
|
+
abort=True,
|
|
180
|
+
)
|
|
181
|
+
_write_ip_set(client, set_name, set_id, [], lock)
|
|
182
|
+
echo.success("IPSet を全削除しました (deny-all 状態)。")
|