magic-pocket 0.1.0__py2.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.
Files changed (50) hide show
  1. magic_pocket-0.1.0.dist-info/LICENSE +21 -0
  2. magic_pocket-0.1.0.dist-info/METADATA +47 -0
  3. magic_pocket-0.1.0.dist-info/RECORD +50 -0
  4. magic_pocket-0.1.0.dist-info/WHEEL +5 -0
  5. magic_pocket-0.1.0.dist-info/entry_points.txt +3 -0
  6. pocket/__init__.py +3 -0
  7. pocket/cli/__init__.py +0 -0
  8. pocket/cli/awscontainer_cli.py +184 -0
  9. pocket/cli/deploy_cli.py +72 -0
  10. pocket/cli/main_cli.py +41 -0
  11. pocket/cli/neon_cli.py +97 -0
  12. pocket/cli/s3_cli.py +62 -0
  13. pocket/cli/spa_cli.py +82 -0
  14. pocket/cli/status_cli.py +58 -0
  15. pocket/cli/vpc_cli.py +69 -0
  16. pocket/context.py +306 -0
  17. pocket/django/__init__.py +9 -0
  18. pocket/django/context.py +70 -0
  19. pocket/django/django_cli.py +170 -0
  20. pocket/django/lambda_handlers.py +65 -0
  21. pocket/django/runtime.py +53 -0
  22. pocket/django/settings.py +50 -0
  23. pocket/django/urls.py +50 -0
  24. pocket/django/utils.py +132 -0
  25. pocket/general_context.py +106 -0
  26. pocket/general_settings.py +61 -0
  27. pocket/mediator.py +118 -0
  28. pocket/resources/__init__.py +0 -0
  29. pocket/resources/aws/__init__.py +0 -0
  30. pocket/resources/aws/cloudformation.py +214 -0
  31. pocket/resources/aws/ecr.py +110 -0
  32. pocket/resources/aws/efs.py +138 -0
  33. pocket/resources/aws/lambdahandler.py +182 -0
  34. pocket/resources/aws/secretsmanager.py +104 -0
  35. pocket/resources/awscontainer.py +180 -0
  36. pocket/resources/base.py +3 -0
  37. pocket/resources/neon.py +316 -0
  38. pocket/resources/s3.py +158 -0
  39. pocket/resources/spa.py +309 -0
  40. pocket/resources/vpc.py +55 -0
  41. pocket/runtime.py +94 -0
  42. pocket/settings.py +275 -0
  43. pocket/templates/cloudformation/awscontainer.yaml +361 -0
  44. pocket/templates/cloudformation/spa.yaml +86 -0
  45. pocket/templates/cloudformation/vpc.yaml +213 -0
  46. pocket/templates/init/django-dotenv.env +3 -0
  47. pocket/templates/init/django-settings.py +140 -0
  48. pocket/templates/init/pocket.Dockerfile +32 -0
  49. pocket/templates/init/pocket_simple.toml +31 -0
  50. pocket/utils.py +102 -0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Masaaki Yasui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.1
2
+ Name: magic-pocket
3
+ Version: 0.1.0
4
+ Summary: Deploy system for Django projects.
5
+ Author-email: Masaaki Yasui <yasu@worgue.com>
6
+ Description-Content-Type: text/markdown
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Requires-Dist: click>=8.1.7
9
+ Requires-Dist: tomli>=1.1.0 ; python_version < '3.11'
10
+ Requires-Dist: mergedeep>=1.3.4
11
+ Requires-Dist: pydantic>=2.5.3
12
+ Requires-Dist: pydantic-settings>=2.1.0
13
+ Requires-Dist: boto3>=1.34.28
14
+ Requires-Dist: rich>=13.7.0
15
+ Requires-Dist: deepdiff>=6.7.1
16
+ Requires-Dist: pyyaml>=6.0.1
17
+ Requires-Dist: python-on-whales>=0.68.0
18
+ Requires-Dist: jinja2>=3.1.3
19
+ Requires-Dist: awslambdaric>=2.0.10
20
+ Requires-Dist: apig_wsgi>=2.18.0
21
+ Requires-Dist: django-storages>=1.14.2,!=1.14.3
22
+ Requires-Dist: django>=4.2.0 ; extra == "django"
23
+ Project-URL: Documentation, https://worgue.github.io/magic-pocket/
24
+ Project-URL: Source, https://github.com/worgue/magic-pocket
25
+ Provides-Extra: django
26
+
27
+ # Magic Pocket - Django Serverless Deployment
28
+
29
+ ドキュメントはこちら
30
+ https://worgue.github.io/magic-pocket/
31
+
32
+
33
+ magic-pocket の目標は、サーバレスDjangoです。Djangoを以下の環境にデプロイします。
34
+
35
+ - AWS Lambda
36
+ - Neon Postgres
37
+ - S3 storages
38
+
39
+ ## Motivation
40
+
41
+ 小規模な個人プロジェクトを、1人で複数同時に運用するため開発されたライブラリです。
42
+ 気軽に作り、飽きたら放っておく、というスタイルで運用するため、以下の要件が最終目標になっています。
43
+
44
+ - サーバーの保守が不要
45
+ - 使わない間のコストが不要
46
+ - 環境を作る時にやる気を出す必要なし
47
+
@@ -0,0 +1,50 @@
1
+ pocket/__init__.py,sha256=JGyTWg48tN0-P6BH2WFWaLHOSilzYOpsrShIbcL_I30,64
2
+ pocket/context.py,sha256=-xPhwwB-4U5Q87BZKME5gi8KO22GcfyVvSel7-TCK60,10231
3
+ pocket/general_context.py,sha256=5QKnxIGCkp33zRNqemmmvR8fqoedagil6txMoMi426E,3206
4
+ pocket/general_settings.py,sha256=RD4ht0pjSuqlYLeKnJTG7WNwmG78JaLFAIe-CCrkHew,1931
5
+ pocket/mediator.py,sha256=iq1_JzviruWrhrAD61dSozuua7twN7AxW8Ez7hBJrQs,4597
6
+ pocket/runtime.py,sha256=0hlbNgIfvzu1uttB4RRDllK2lXkz8e45mbXvkZlBrms,3287
7
+ pocket/settings.py,sha256=u6FvECLsTSeRXLz8xRY8qEgTOIdSVo1cww4pcFefc-o,9039
8
+ pocket/utils.py,sha256=oGUIjHosuMQWvoAaz3XqjgLlyJtapBF-PX7MMrSK4Hg,2612
9
+ pocket/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ pocket/cli/awscontainer_cli.py,sha256=PkuCFAtcrAdJ91XClfPfR5yzydyfuQ6RGj1r25BORgc,5911
11
+ pocket/cli/deploy_cli.py,sha256=XzxUGyqSw606bYcmE1gVWMy7UWaCJzSxpYOsBtKND0Y,2430
12
+ pocket/cli/main_cli.py,sha256=1WlUSNCqHzfvcCImPXitSSNvRYHyrucGGQMzw9l_Exg,673
13
+ pocket/cli/neon_cli.py,sha256=ry2MMR63vzbRrHnYta8Bv0DT6rDpsfgeC11y7KCPpJc,2462
14
+ pocket/cli/s3_cli.py,sha256=4-2xfZ30YQzGrzyQKHz4RfQL2NWB7uwbRZTjFig8Pao,1486
15
+ pocket/cli/spa_cli.py,sha256=eHwGkb5AjRFuRYnmb3fxzp_znr8DPV7Q7EPYLoWyZPY,1901
16
+ pocket/cli/status_cli.py,sha256=66kifOIF8HD-VWhAkmNfWEZj7BfvLQlmfjMsSvehbD0,2059
17
+ pocket/cli/vpc_cli.py,sha256=FpEKZgsHyfowRGnLQK3q2dR9JPJQr3IXbhn0ZiQNr5w,1613
18
+ pocket/django/__init__.py,sha256=nvNCBm-9BLVWmIUpmd_OETtWMFwQ7_fJZqE3PmLP_-4,177
19
+ pocket/django/context.py,sha256=xuCfZxL0IthUpijke3zb3_FLX-qutBKK6_OtofYoOxk,2305
20
+ pocket/django/django_cli.py,sha256=L_bbtXpdINNtpAPozAphIfu2-NOaa5UoSJIqjv3JmzI,6147
21
+ pocket/django/lambda_handlers.py,sha256=IRvMx1qCa9kIvFRVmOxaxNwfBS-pt39hyGfdz51EDmg,1968
22
+ pocket/django/runtime.py,sha256=Dq-dxgwiRJnKQ-99yuh5E3AtO3JocC4XTmNgx8vkKpc,1504
23
+ pocket/django/settings.py,sha256=8Gg8FgOLlHpMcJF0bdFMwBTEkGTXS2Rc0uyG5H2Lgcs,1594
24
+ pocket/django/urls.py,sha256=nkAiNEVjI9pUw0Z1t9cunynarNe_9Usrw_prBhwEL-c,1349
25
+ pocket/django/utils.py,sha256=HqWznmI-QaWWcO1Po758oQXWK1tGaSlz7855FOcsLcQ,4670
26
+ pocket/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ pocket/resources/awscontainer.py,sha256=o2Ize_2CNR5Na3kmnZftvHBygOOzbi4gStu_8gGMeow,6187
28
+ pocket/resources/base.py,sha256=xOwos3J8RQgVxIpWw9IVYL3QKHHCAAtX6XJujsgBc6c,117
29
+ pocket/resources/neon.py,sha256=Op9KoYdRvCVAfqRjqZYmyDXpKfofZXQQC16dSxljYzE,9869
30
+ pocket/resources/s3.py,sha256=oGWPDUunmo8JS4g_ZeHefiRyGJKg9POwU_mG4ANgjtc,5251
31
+ pocket/resources/spa.py,sha256=PFN_IhNYRjbiffC2HEXbLACP4ztbCi81eyCqA0foRwg,11055
32
+ pocket/resources/vpc.py,sha256=5TbIBegzCBCOgltchwjFPNjgNJC-MTrj6f0ME3NUSHk,1302
33
+ pocket/resources/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ pocket/resources/aws/cloudformation.py,sha256=B8d5lFTcd0U_RyKsf0hMAUV7K4AAZz2OXCXGwl6Ohfc,6175
35
+ pocket/resources/aws/ecr.py,sha256=fDX3wMxShu2v0Q93KFlglJzf0emcZ5MFpoUuhMW_PqQ,3205
36
+ pocket/resources/aws/efs.py,sha256=Y2OOSMCC0khdbvEbvG6bCoWnzLC5sunnO39SO_iuih0,3932
37
+ pocket/resources/aws/lambdahandler.py,sha256=KvIrxO6Akghi2jWnwtJ1IZN2PFnL0PGg0ZXqGUMYrY4,6629
38
+ pocket/resources/aws/secretsmanager.py,sha256=cGbp0nL52bvooNLpttD2y7rmXlQLR934IpuSJSYVc-o,3987
39
+ pocket/templates/cloudformation/awscontainer.yaml,sha256=4JW37R2I8y1g1guwLYn9iCDdH-al0sEyW6EKaDMHKG0,12443
40
+ pocket/templates/cloudformation/spa.yaml,sha256=zEs9JcL-BBqUP_7fIFlA-ummqSa7n46X-nJS8K96vc8,2691
41
+ pocket/templates/cloudformation/vpc.yaml,sha256=jV11OzAVhjOn36uO3PKWDpdEFqJD9TreCFV177lbcdY,5085
42
+ pocket/templates/init/django-dotenv.env,sha256=sgYd_76armFBDDOVEYB4B0PZp0UR8soEf5RJ0NwVwEk,73
43
+ pocket/templates/init/django-settings.py,sha256=pmTtaJcyJuhTTr95DLQbZewUAPYlhnEGrTBeZkLfFZo,3600
44
+ pocket/templates/init/pocket.Dockerfile,sha256=_-yYd73xSVC-yyG2bu2Rkp4tP1a22lx6zeOG4djbNzk,911
45
+ pocket/templates/init/pocket_simple.toml,sha256=RlZNvJ5OtTJyrlEk6Ex3AIvGaZJvfe3N3C8piKLrmVM,849
46
+ magic_pocket-0.1.0.dist-info/entry_points.txt,sha256=zwGA18mmLk4cdBpb7hJEQqrpS7-e7H6GNsBV1F4_eX0,51
47
+ magic_pocket-0.1.0.dist-info/LICENSE,sha256=6sI-S9egZe33_sWOqLD7eyix1d7xlHn_oiB-3mlXrZw,1080
48
+ magic_pocket-0.1.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
49
+ magic_pocket-0.1.0.dist-info/METADATA,sha256=c0Iur_M7LHnZH7P588esOgBgae1JaKRgoORh83-9H-w,1613
50
+ magic_pocket-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pocket=pocket.cli.main_cli:main
3
+
pocket/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Deploy system for Django projects."""
2
+
3
+ __version__ = "0.1.0"
pocket/cli/__init__.py ADDED
File without changes
@@ -0,0 +1,184 @@
1
+ import webbrowser
2
+
3
+ import click
4
+
5
+ from ..context import Context
6
+ from ..mediator import Mediator
7
+ from ..resources.awscontainer import AwsContainer
8
+ from ..utils import echo
9
+
10
+
11
+ @click.group()
12
+ def awscontainer():
13
+ pass
14
+
15
+
16
+ def get_awscontainer_resource(stage):
17
+ context = Context.from_toml(stage=stage)
18
+ if not context.awscontainer:
19
+ echo.danger("awscontainer is not configured for this stage")
20
+ raise Exception("awscontainer is not configured for this stage")
21
+ return AwsContainer(context=context.awscontainer)
22
+
23
+
24
+ @awscontainer.command()
25
+ @click.option("--stage", prompt=True)
26
+ def yaml(stage):
27
+ ac = get_awscontainer_resource(stage)
28
+ print(ac.stack.yaml)
29
+
30
+
31
+ @awscontainer.command()
32
+ @click.option("--stage", prompt=True)
33
+ def yaml_diff(stage):
34
+ ac = get_awscontainer_resource(stage)
35
+ print(ac.stack.yaml_diff.to_json(indent=2))
36
+
37
+
38
+ @awscontainer.group()
39
+ def secretsmanager():
40
+ pass
41
+
42
+
43
+ @secretsmanager.command()
44
+ @click.option("--stage", prompt=True)
45
+ @click.option("--show-values", is_flag=True, default=False)
46
+ def list(stage, show_values):
47
+ ac = get_awscontainer_resource(stage)
48
+ sm = ac.context.secretsmanager
49
+ if not sm:
50
+ echo.warning("secretsmanager is not configured for this stage")
51
+ return
52
+ for key, arn in sm.secrets.items():
53
+ print("%s: %s" % (key, arn))
54
+ if show_values:
55
+ print(" - " + sm.resource.user_secrets[key])
56
+ for key, pocket_secret in sm.pocket_secrets.items():
57
+ status = "CREATED" if key in sm.resource.pocket_secrets else "NOEXIST"
58
+ print("%s: %s %s" % (key, pocket_secret.type, pocket_secret.options))
59
+ print(" - " + status)
60
+ if (status == "CREATED") and show_values:
61
+ value = sm.resource.pocket_secrets[key]
62
+ if isinstance(value, str):
63
+ print(" - " + value)
64
+ else:
65
+ for k, v in value.items():
66
+ print(f" - {k}: {v}")
67
+
68
+
69
+ @secretsmanager.command()
70
+ @click.option("--stage", prompt=True)
71
+ def create_pocket_managed(stage):
72
+ ac = get_awscontainer_resource(stage)
73
+ sm = ac.context.secretsmanager
74
+ if not sm:
75
+ echo.warning("secretsmanager is not configured for this stage")
76
+ return
77
+ mediator = Mediator(Context.from_toml(stage=stage))
78
+ mediator.create_pocket_managed_secrets()
79
+
80
+
81
+ def _confirm_delete_pocket_managed_secrets(awscontainer: AwsContainer):
82
+ sm = awscontainer.context.secretsmanager
83
+ if not sm:
84
+ echo.warning("secretsmanager is not configured")
85
+ return
86
+ existing_secret_keys = [
87
+ key for key in sm.pocket_secrets.keys() if key in sm.resource.pocket_secrets
88
+ ]
89
+ if not existing_secret_keys:
90
+ echo.warning("No pocket managed secets are created yet.")
91
+ return
92
+ echo.warning("You are deleting pocket managed secrets.")
93
+ echo.info("Deleting secrets:")
94
+ for key in existing_secret_keys:
95
+ echo.info(" - " + key)
96
+ echo.danger("This data cannot be restored!")
97
+ click.confirm("Do you realy want to delete pocket managed secrets?", abort=True)
98
+
99
+
100
+ @secretsmanager.command()
101
+ @click.option("--stage", prompt=True)
102
+ def delete_pocket_managed(stage):
103
+ ac = get_awscontainer_resource(stage)
104
+ _confirm_delete_pocket_managed_secrets(ac)
105
+ if ac.context.secretsmanager:
106
+ ac.context.secretsmanager.resource.delete_pocket_secrets()
107
+
108
+
109
+ @awscontainer.command()
110
+ @click.option("--stage", prompt=True)
111
+ def create(stage):
112
+ ac = get_awscontainer_resource(stage)
113
+ if not ac.status == "NOEXIST":
114
+ echo.warning("AWS lambda container is already created.")
115
+ else:
116
+ mediator = Mediator(Context.from_toml(stage=stage))
117
+ ac.create(mediator)
118
+ echo.success("Created: lambda")
119
+
120
+
121
+ @awscontainer.command()
122
+ @click.option("--stage", prompt=True)
123
+ @click.option("--with-secrets", is_flag=True, default=False)
124
+ def destroy(stage, with_secrets):
125
+ ac = get_awscontainer_resource(stage)
126
+ if ac.stack.status == "NOEXIST":
127
+ echo.warning("No AWS lambda container found.")
128
+ else:
129
+ ac.stack.delete()
130
+ echo.success("Aws lambda container was destroyed.")
131
+ if with_secrets:
132
+ _confirm_delete_pocket_managed_secrets(ac)
133
+ if ac.context.secretsmanager:
134
+ ac.context.secretsmanager.resource.delete_pocket_secrets()
135
+ echo.success("Pocket managed secrets were deleted.")
136
+ else:
137
+ echo.warning("Pocket managed secrets still exists.")
138
+
139
+
140
+ @awscontainer.command()
141
+ @click.option("--stage", prompt=True)
142
+ def update(stage):
143
+ ac = get_awscontainer_resource(stage)
144
+ if ac.status == "NOEXIST":
145
+ echo.warning("AWS lambda has not created yet.")
146
+ return
147
+ if ac.status == "FAILED":
148
+ echo.danger("AWS lambda has failed. Please check console.")
149
+ return
150
+ if ac.status == "PROGRESS":
151
+ echo.warning("AWS lambda is updating. Please wait.")
152
+ return
153
+ mediator = Mediator(Context.from_toml(stage=stage))
154
+ ac.update(mediator)
155
+
156
+
157
+ @awscontainer.command()
158
+ @click.option("--stage", prompt=True)
159
+ def status(stage):
160
+ ac = get_awscontainer_resource(stage)
161
+ if ac.status == "COMPLETED":
162
+ echo.success("Container is working!!!")
163
+ elif ac.status == "NOEXIST":
164
+ echo.warning("Container has not created yet.")
165
+ elif ac.status == "FAILED":
166
+ echo.danger("Container has failed. Please check console.")
167
+ else:
168
+ echo.warning("Container stack status: %s" % ac.stack.status)
169
+
170
+
171
+ @awscontainer.command()
172
+ @click.option("--stage", prompt=True)
173
+ @click.option("--openpath")
174
+ def url(stage, openpath):
175
+ ac = get_awscontainer_resource(stage)
176
+ if ac.status == "COMPLETED":
177
+ if endpoint := ac.endpoints.get("wsgi"):
178
+ echo.success(f"wsgi url: {endpoint}")
179
+ if openpath:
180
+ webbrowser.open(endpoint + "/" + openpath)
181
+ else:
182
+ echo.warning("wsgi endpoint not found.")
183
+ else:
184
+ echo.warning("Container is not working.")
@@ -0,0 +1,72 @@
1
+ import inspect
2
+ import webbrowser
3
+
4
+ import click
5
+
6
+ from ..context import Context
7
+ from ..mediator import Mediator
8
+ from ..utils import echo
9
+
10
+
11
+ def get_resources(context: Context):
12
+ resources = []
13
+ if context.neon:
14
+ resources.append(context.neon.resource)
15
+ if context.s3:
16
+ resources.append(context.s3.resource)
17
+ if context.awscontainer:
18
+ if context.awscontainer.vpc:
19
+ resources.append(context.awscontainer.vpc.resource)
20
+ resources.append(context.awscontainer.resource)
21
+ if context.spa:
22
+ resources.append(context.spa.resource)
23
+ return resources
24
+
25
+
26
+ def deploy_init_resources(context: Context):
27
+ for resource in get_resources(context):
28
+ target_name = resource.__class__.__name__
29
+ echo.log("Deploy init %s..." % target_name)
30
+ resource.deploy_init()
31
+
32
+
33
+ def deploy_resources(context: Context):
34
+ mediator = Mediator(context)
35
+ for resource in get_resources(context):
36
+ target_name = resource.__class__.__name__
37
+ if resource.status == "NOEXIST":
38
+ echo.log("Creating %s..." % target_name)
39
+ if "mediator" in inspect.signature(resource.create).parameters:
40
+ resource.create(mediator)
41
+ else:
42
+ resource.create()
43
+ elif resource.status == "REQUIRE_UPDATE":
44
+ echo.log("Updating %s..." % target_name)
45
+ if "mediator" in inspect.signature(resource.update).parameters:
46
+ resource.update(mediator)
47
+ else:
48
+ resource.update()
49
+ else:
50
+ echo.log("%s is already the latest version." % target_name)
51
+
52
+
53
+ @click.command()
54
+ @click.option("--stage", prompt=True)
55
+ @click.option("--openpath")
56
+ def deploy(stage: str, openpath):
57
+ context = Context.from_toml(stage=stage)
58
+ deploy_init_resources(context)
59
+ deploy_resources(context)
60
+ if endpoint := context.awscontainer and context.awscontainer.resource.endpoints.get(
61
+ "wsgi"
62
+ ):
63
+ if endpoint is None:
64
+ echo.warning("wsgi endpoint is not created yet.")
65
+ echo.warning("You can check the endpoint later by resource command.")
66
+ echo.warning("$ pocket resource awscontainer url")
67
+ echo.warning("Or deploy again.")
68
+ echo.warning("$ pocket deploy")
69
+ else:
70
+ echo.success(f"wsgi url: {endpoint}")
71
+ if openpath:
72
+ webbrowser.open(endpoint + "/" + openpath)
pocket/cli/main_cli.py ADDED
@@ -0,0 +1,41 @@
1
+ import click
2
+
3
+ from .. import __version__
4
+ from ..django import django_cli
5
+ from . import (
6
+ awscontainer_cli,
7
+ deploy_cli,
8
+ neon_cli,
9
+ s3_cli,
10
+ spa_cli,
11
+ status_cli,
12
+ vpc_cli,
13
+ )
14
+
15
+
16
+ @click.group()
17
+ def main():
18
+ pass
19
+
20
+
21
+ @main.command()
22
+ def version():
23
+ """Print the version number."""
24
+ click.echo(__version__)
25
+
26
+
27
+ main.add_command(deploy_cli.deploy)
28
+ main.add_command(status_cli.status)
29
+ main.add_command(django_cli.django)
30
+
31
+
32
+ @main.group()
33
+ def resource():
34
+ pass
35
+
36
+
37
+ resource.add_command(vpc_cli.vpc)
38
+ resource.add_command(awscontainer_cli.awscontainer)
39
+ resource.add_command(neon_cli.neon)
40
+ resource.add_command(s3_cli.s3)
41
+ resource.add_command(spa_cli.spa)
pocket/cli/neon_cli.py ADDED
@@ -0,0 +1,97 @@
1
+ from pprint import pprint
2
+
3
+ import click
4
+
5
+ from ..context import Context
6
+ from ..resources.neon import Neon
7
+ from ..utils import echo
8
+
9
+
10
+ @click.group()
11
+ def neon():
12
+ pass
13
+
14
+
15
+ def get_neon_resource(stage):
16
+ context = Context.from_toml(stage=stage)
17
+ if not context.neon:
18
+ echo.danger("neon is not configured for this stage")
19
+ raise Exception("neon is not configured for this stage")
20
+ return Neon(context=context.neon)
21
+
22
+
23
+ @neon.command()
24
+ @click.option("--stage", prompt=True)
25
+ def context(stage):
26
+ neon = get_neon_resource(stage)
27
+ pprint(neon.context.model_dump())
28
+
29
+
30
+ @neon.command()
31
+ @click.option("--stage", prompt=True)
32
+ def create(stage):
33
+ neon = get_neon_resource(stage)
34
+ neon.create()
35
+ echo.success("New branch was created")
36
+
37
+
38
+ @neon.command()
39
+ @click.option("--stage", prompt=True)
40
+ def reset_database(stage):
41
+ neon = get_neon_resource(stage)
42
+ neon.reset_database()
43
+ echo.success("Reset database")
44
+
45
+
46
+ @neon.command()
47
+ @click.option("--stage", prompt=True)
48
+ @click.option("--base-stage", default=None)
49
+ def branch_out(stage, base_stage):
50
+ neon = get_neon_resource(stage)
51
+ if neon.branch:
52
+ raise Exception("Branch already exists")
53
+ base_neon = get_neon_resource(base_stage)
54
+ if not base_neon.working:
55
+ raise Exception("Base stage is not working")
56
+ assert base_neon.branch
57
+ neon.create_branch(base_neon.branch)
58
+ echo.success("New branch was created")
59
+
60
+
61
+ @neon.command()
62
+ @click.option("--stage", prompt=True)
63
+ def delete(stage):
64
+ neon = get_neon_resource(stage)
65
+ neon.delete_branch()
66
+ echo.success("Branch was deleted successfully.")
67
+
68
+
69
+ @neon.command()
70
+ @click.option("--stage", prompt=True)
71
+ def status(stage):
72
+ neon = get_neon_resource(stage)
73
+ if neon.project:
74
+ echo.success("Project found")
75
+ else:
76
+ echo.warning("Project not found")
77
+ return
78
+ if neon.branch:
79
+ echo.success("Branch found")
80
+ else:
81
+ echo.warning("Branch not found")
82
+ return
83
+ if neon.database:
84
+ echo.success("Database found")
85
+ else:
86
+ echo.warning("Database not found")
87
+ return
88
+ if neon.endpoint:
89
+ echo.success("Endpoint found: %s" % neon.endpoint.host)
90
+ else:
91
+ echo.warning("Endpoint not found")
92
+ if neon.role:
93
+ echo.success("Role found: %s" % neon.context.role_name)
94
+ else:
95
+ echo.warning("Role not found")
96
+ if neon.role and neon.endpoint:
97
+ echo.success("Database url: %s" % neon.database_url)
pocket/cli/s3_cli.py ADDED
@@ -0,0 +1,62 @@
1
+ from pprint import pprint
2
+
3
+ import click
4
+
5
+ from ..context import Context
6
+ from ..resources.s3 import S3
7
+ from ..utils import echo
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", 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", prompt=True)
32
+ def create(stage):
33
+ storage = get_s3_resource(stage)
34
+ if storage.exists():
35
+ echo.warning(
36
+ "S3 Bucket is already created by you.\n"
37
+ "Presume this bucket is properly configured.\n"
38
+ "Please check the bucket.",
39
+ )
40
+ return
41
+ storage.ensure_exists()
42
+ echo.success("Created: bucket %s" % storage.context.bucket_name)
43
+
44
+
45
+ @s3.command()
46
+ @click.option("--stage", prompt=True)
47
+ def update_public_dirs(stage):
48
+ storage = get_s3_resource(stage)
49
+ if not storage.exists():
50
+ echo.warning("No bucket found.")
51
+ storage.ensure_public_access_block()
52
+ storage.ensure_policy()
53
+
54
+
55
+ @s3.command()
56
+ @click.option("--stage", prompt=True)
57
+ def status(stage):
58
+ storage = get_s3_resource(stage)
59
+ if storage.exists():
60
+ echo.success("Storage found")
61
+ else:
62
+ echo.warning("Storage not found")
pocket/cli/spa_cli.py ADDED
@@ -0,0 +1,82 @@
1
+ import click
2
+
3
+ from ..context import Context
4
+ from ..utils import echo
5
+
6
+
7
+ @click.group()
8
+ def spa():
9
+ pass
10
+
11
+
12
+ def get_spa_resource(stage):
13
+ context = Context.from_toml(stage=stage)
14
+ if not context.spa:
15
+ echo.danger("spa is not configured for this stage")
16
+ raise Exception("spa is not configured for this stage")
17
+ return context.spa.resource
18
+
19
+
20
+ @spa.command()
21
+ @click.option("--stage", prompt=True)
22
+ def yaml(stage):
23
+ spa = get_spa_resource(stage)
24
+ print(spa.stack.yaml)
25
+
26
+
27
+ @spa.command()
28
+ @click.option("--stage", prompt=True)
29
+ def yaml_diff(stage):
30
+ spa = get_spa_resource(stage)
31
+ print(spa.stack.yaml_diff.to_json(indent=2))
32
+
33
+
34
+ @spa.command()
35
+ @click.option("--stage", prompt=True)
36
+ def context(stage):
37
+ spa = get_spa_resource(stage)
38
+ print(spa.context.model_dump_json(indent=2))
39
+
40
+
41
+ @spa.command()
42
+ @click.option("--stage", prompt=True)
43
+ def create(stage):
44
+ spa = get_spa_resource(stage)
45
+ spa.create()
46
+ echo.success("spa store was created")
47
+
48
+
49
+ @spa.command()
50
+ @click.option("--stage", prompt=True)
51
+ def destroy(stage):
52
+ spa = get_spa_resource(stage)
53
+ spa.delete()
54
+ echo.success("spa store was deleted successfully.")
55
+
56
+
57
+ @spa.command()
58
+ @click.option("--stage", prompt=True)
59
+ def update(stage):
60
+ spa = get_spa_resource(stage)
61
+ if spa.status == "NOEXIST":
62
+ echo.warning("Spa resource has not created yet.")
63
+ return
64
+ if spa.status == "FAILED":
65
+ echo.danger("Spa resource creation has failed. Please check console.")
66
+ return
67
+ if spa.status == "PROGRESS":
68
+ echo.warning("Spa is updating. Please wait.")
69
+ return
70
+ spa.update()
71
+
72
+
73
+ @spa.command()
74
+ @click.option("--stage", prompt=True)
75
+ def status(stage):
76
+ spa = get_spa_resource(stage)
77
+ if spa._origin_s3_exists():
78
+ echo.info("s3 for spa exists")
79
+ if spa.status == "COMPLETED":
80
+ echo.success("COMPLETED")
81
+ else:
82
+ print(spa.status)