dsd-upsun 0.1.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.

Potentially problematic release.


This version of dsd-upsun might be problematic. Click here for more details.

dsd_upsun/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .deploy import dsd_get_plugin_config
2
+ from .deploy import dsd_deploy
dsd_upsun/deploy.py ADDED
@@ -0,0 +1,20 @@
1
+ """Manages all Upsun-specific aspects of the deployment process."""
2
+
3
+ import django_simple_deploy
4
+
5
+ from dsd_upsun.platform_deployer import PlatformDeployer
6
+ from .plugin_config import PluginConfig
7
+
8
+
9
+ @django_simple_deploy.hookimpl
10
+ def dsd_get_plugin_config():
11
+ """Get platform-specific attributes needed by core."""
12
+ plugin_config = PluginConfig()
13
+ return plugin_config
14
+
15
+
16
+ @django_simple_deploy.hookimpl
17
+ def dsd_deploy():
18
+ """Carry out platform-specific deployment steps."""
19
+ platform_deployer = PlatformDeployer()
20
+ platform_deployer.deploy()
@@ -0,0 +1,208 @@
1
+ """A collection of messages used in platform_deployer.py."""
2
+
3
+ # For conventions, see documentation in deploy_messages.py
4
+
5
+ from textwrap import dedent
6
+
7
+ from django.conf import settings
8
+
9
+
10
+ confirm_automate_all = """
11
+ The --automate-all flag means the deploy command will:
12
+ - Run `upsun create` for you, to create an empty Upsun project.
13
+ - This will create a project in the us-3.platform.sh region. If you wish
14
+ to use a different region, cancel this operation and use the --region flag.
15
+ - You can see a list of all regions at:
16
+ https://docs.upsun.com/development/regions.html#region-location
17
+ - Commit all changes to your project that are necessary for deployment.
18
+ - Push these changes to Upsun.
19
+ - Open your deployed project in a new browser tab.
20
+ """
21
+
22
+ cancel_upsun = """
23
+ Okay, cancelling Upsun deployment.
24
+ """
25
+
26
+ cli_not_installed = """
27
+ In order to deploy to Upsun, you need to install the Upsun CLI.
28
+ See here: https://docs.upsun.com/administration/cli.html
29
+ After installing the CLI, you can run the deploy command again.
30
+ """
31
+
32
+ cli_logged_out = """
33
+ You are currently logged out of the Upsun CLI. Please log in,
34
+ and then run the deploy command again.
35
+ You can log in from the command line:
36
+ $ platform login
37
+ """
38
+
39
+ upsun_settings_found = """
40
+ There is already an Upsun-specific settings block in settings.py. Is it okay to
41
+ overwrite this block, and everything that follows in settings.py?
42
+ """
43
+
44
+ cant_overwrite_settings = """
45
+ In order to configure the project for deployment, we need to write an Upsun-specific
46
+ settings block. Please remove the current Upsun-specific settings, and then run
47
+ the deploy command again.
48
+ """
49
+
50
+ no_project_name = """
51
+ An Upsun project name could not be found.
52
+
53
+ The deploy command expects that you've already run `platform create`, or
54
+ associated the local project with an existing project on Upsun.
55
+
56
+ If you haven't done so, run the `platform create` command and then run
57
+ the deploy command again. You can override this warning by using
58
+ the `--deployed-project-name` flag to specify the name you want to use for the
59
+ project. This must match the name of your Upsun project.
60
+ """
61
+
62
+ org_not_found = """
63
+ An Upsun organization name could not be found.
64
+
65
+ You may have created an Upsun account, but not created an organization.
66
+ The Upsun CLI requires an organization name when creating a new project.
67
+
68
+ Please visit the Upsun console and make sure you have created an organization.
69
+ You can also do this through the CLI using the `platform organization:create` command.
70
+ For help, run `platform help organization:create`.
71
+ """
72
+
73
+ no_org_available = """
74
+ An Upsun org must be used to make a deployment. Please identify or create the org
75
+ you'd like to use, and then try again.
76
+ """
77
+
78
+ login_required = """
79
+ You appear to be logged out of the Upsun CLI. Please run the
80
+ command `platform login`, and then run the deploy command again.
81
+
82
+ You may be able to override this error by passing the `--deployed-project-name`
83
+ flag.
84
+ """
85
+
86
+ unknown_error = """
87
+ An unknown error has occurred. Do you have the Upsun CLI installed?
88
+ """
89
+
90
+ may_configure = """
91
+ You may want to re-run the deploy command without the --automate-all flag.
92
+
93
+ You will have to create the Upsun project yourself, but django-simple-deploy
94
+ will do all of the necessary configuration for deployment.
95
+ """
96
+
97
+
98
+ # --- Dynamic strings ---
99
+ # These need to be generated in functions, to display information that's
100
+ # determined as the script runs.
101
+
102
+
103
+ def confirm_use_org(org_name):
104
+ """Confirm use of this org to create a new project."""
105
+
106
+ msg = dedent(
107
+ f"""
108
+ --- The Upsun CLI requires an organization name when creating a new project. ---
109
+ When using --automate-all, a project will be created on your behalf. The following
110
+ organization was found: {org_name}
111
+
112
+ This organization will be used to create a new project. If this is not okay,
113
+ enter n to cancel this operation.
114
+ """
115
+ )
116
+
117
+ return msg
118
+
119
+
120
+ def unknown_create_error(e):
121
+ """Process a non-specific error when running `platform create`
122
+ while using automate_all. This is most likely an issue with the user
123
+ not having permission to create a new project, for example because they
124
+ are on a trial plan and have already created too many projects.
125
+ """
126
+
127
+ msg = dedent(
128
+ f"""
129
+ --- An error has occurred when trying to create a new Upsun project. ---
130
+
131
+ While running `platform create`, an error has occurred. You should check
132
+ the Upsun console to see if a project was partially created.
133
+
134
+ The error messages that Upsun provides, both through the CLI and
135
+ the console, are not always specific enough to be helpful. For example,
136
+ newer users are limited to two new projects in a 24-hour period, or something
137
+ like that. But if you try to create an additional project, you only get
138
+ a message that says: "You do not have access to create a new Subscriptions resource".
139
+ There is no information about specific limits, and how to address them.
140
+
141
+ The following output may help diagnose the error:
142
+ ***** output of `platform create` *****
143
+
144
+ {e.stderr.decode()}
145
+
146
+ ***** end output *****
147
+ """
148
+ )
149
+
150
+ return msg
151
+
152
+
153
+ def success_msg(log_output=""):
154
+ """Success message, for configuration-only run."""
155
+
156
+ msg = dedent(
157
+ f"""
158
+ --- Your project is now configured for deployment on Upsun. ---
159
+
160
+ To deploy your project, you will need to:
161
+ - Commit the changes made in the configuration process.
162
+ $ git status
163
+ $ git add .
164
+ $ git commit -am "Configured project for deployment."
165
+ - Push your project to Upsun' servers:
166
+ $ platform push
167
+ - Open your project:
168
+ $ platform url
169
+ - As you develop your project further:
170
+ - Make local changes
171
+ - Commit your local changes
172
+ - Run `platform push`
173
+ """
174
+ )
175
+
176
+ if log_output:
177
+ msg += dedent(
178
+ f"""
179
+ - You can find a full record of this configuration in the dsd_logs directory.
180
+ """
181
+ )
182
+
183
+ return msg
184
+
185
+
186
+ def success_msg_automate_all(deployed_url):
187
+ """Success message, when using --automate-all."""
188
+
189
+ msg = dedent(
190
+ f"""
191
+
192
+ --- Your project should now be deployed on Upsun. ---
193
+
194
+ It should have opened up in a new browser tab.
195
+ - You can also visit your project at {deployed_url}
196
+
197
+ If you make further changes and want to push them to Upsun,
198
+ commit your changes and then run `platform push`.
199
+
200
+ Also, if you haven't already done so you should review the
201
+ documentation for Python deployments on Upsun at:
202
+ - https://fixed.docs.upsun.com/languages/python.html
203
+ - This documentation will help you understand how to maintain
204
+ your deployment.
205
+
206
+ """
207
+ )
208
+ return msg
@@ -0,0 +1,411 @@
1
+ """Manages all Upsun-specific aspects of the deployment process."""
2
+
3
+ import sys, os, subprocess, time
4
+ from pathlib import Path
5
+
6
+ from django.conf import settings
7
+ from django.core.management.utils import get_random_secret_key
8
+ from django.utils.crypto import get_random_string
9
+ from django.utils.safestring import mark_safe
10
+
11
+ from django_simple_deploy.management.commands.utils import plugin_utils
12
+ from django_simple_deploy.management.commands.utils.plugin_utils import dsd_config
13
+ from django_simple_deploy.management.commands.utils.command_errors import (
14
+ DSDCommandError,
15
+ )
16
+
17
+ from . import deploy_messages as upsun_msgs
18
+ from . import utils as upsun_utils
19
+
20
+
21
+ class PlatformDeployer:
22
+ """Perform the initial deployment to Upsun.
23
+
24
+ If --automate-all is used, carry out an actual deployment.
25
+ If not, do all configuration work so the user only has to commit changes, and call
26
+ `upsun push`.
27
+ """
28
+
29
+ def __init__(self):
30
+ self.templates_path = Path(__file__).parent / "templates"
31
+
32
+ # --- Public methods ---
33
+
34
+ def deploy(self, *args, **options):
35
+ """Coordinate the overall configuration and deployment."""
36
+ plugin_utils.write_output(
37
+ "\nConfiguring project for deployment to Upsun..."
38
+ )
39
+
40
+ self._validate_platform()
41
+
42
+ self._prep_automate_all()
43
+ self._modify_settings()
44
+ self._add_requirements()
45
+ self._add_platform_app_yaml()
46
+ self._add_platform_dir()
47
+ self._add_services_yaml()
48
+ self._settings_env_var()
49
+
50
+ self._conclude_automate_all()
51
+ self._show_success_message()
52
+
53
+ # --- Helper methods for deploy() ---
54
+
55
+ def _validate_platform(self):
56
+ """Make sure the local environment and project supports deployment to
57
+ Upsun.
58
+
59
+ Make sure CLI is installed, and user is authenticated. Make sure necessary
60
+ resources have been created and identified, and that we have the user's
61
+ permission to use those resources.
62
+
63
+ Returns:
64
+ None
65
+
66
+ Raises:
67
+ DSDCommandError: If we find any reason deployment won't work.
68
+ """
69
+ if dsd_config.unit_testing:
70
+ # Unit tests don't use the CLI. Use the deployed project name that was
71
+ # passed to the simple_deploy CLI.
72
+ self.deployed_project_name = dsd_config.deployed_project_name
73
+ plugin_utils.log_info(
74
+ f"Deployed project name: {self.deployed_project_name}"
75
+ )
76
+ return
77
+
78
+ self._check_upsun_settings()
79
+ self._validate_cli()
80
+
81
+ self.deployed_project_name = self._get_upsun_project_name()
82
+ plugin_utils.log_info(f"Deployed project name: {self.deployed_project_name}")
83
+
84
+ self.org_name = self._get_org_name()
85
+ plugin_utils.log_info(f"\nOrg name: {self.org_name}")
86
+
87
+ def _prep_automate_all(self):
88
+ """Intial work for automating entire process.
89
+
90
+ Returns:
91
+ None: If creation of new project was successful.
92
+
93
+ Raises:
94
+ DSDCommandError: If create command fails.
95
+
96
+ Note: create command outputs project id to stdout if known, all other
97
+ output goes to stderr.
98
+ """
99
+ if not dsd_config.automate_all:
100
+ return
101
+
102
+ plugin_utils.write_output(" Running `upsun create`...")
103
+ plugin_utils.write_output(
104
+ " (Please be patient, this can take a few minutes."
105
+ )
106
+ cmd = f"upsun create --title { self.deployed_project_name } --org {self.org_name} --region {dsd_config.region} --yes"
107
+
108
+ try:
109
+ # Note: if user can't create a project the returncode will be 6, not 1.
110
+ # This may affect whether a CompletedProcess is returned, or an Exception
111
+ # is raised.
112
+ # Also, create command outputs project id to stdout if known, all other
113
+ # output goes to stderr.
114
+ plugin_utils.run_slow_command(cmd)
115
+ except subprocess.CalledProcessError as e:
116
+ error_msg = upsun_msgs.unknown_create_error(e)
117
+ raise DSDCommandError(error_msg)
118
+
119
+ def _modify_settings(self):
120
+ """Add upsun-specific settings.
121
+
122
+ This settings block is currently the same for all users. The ALLOWED_HOSTS
123
+ setting should be customized.
124
+ """
125
+ template_path = self.templates_path / "settings.py"
126
+ plugin_utils.modify_settings_file(template_path)
127
+
128
+ def _add_platform_app_yaml(self):
129
+ """Add a .platform.app.yaml file."""
130
+
131
+ # Build contents from template.
132
+ if dsd_config.pkg_manager == "poetry":
133
+ template_path = "poetry.platform.app.yaml"
134
+ elif dsd_config.pkg_manager == "pipenv":
135
+ template_path = "pipenv.platform.app.yaml"
136
+ else:
137
+ template_path = "platform.app.yaml"
138
+ template_path = self.templates_path / template_path
139
+
140
+ context = {
141
+ "project_name": dsd_config.local_project_name,
142
+ "deployed_project_name": self.deployed_project_name,
143
+ }
144
+
145
+ contents = plugin_utils.get_template_string(template_path, context)
146
+
147
+ # Write file to project.
148
+ path = dsd_config.project_root / ".platform.app.yaml"
149
+ plugin_utils.add_file(path, contents)
150
+
151
+ def _add_requirements(self):
152
+ """Add requirements for Upsun."""
153
+ requirements = ["platformshconfig", "gunicorn", "psycopg2"]
154
+ plugin_utils.add_packages(requirements)
155
+
156
+ def _add_platform_dir(self):
157
+ """Add a .platform directory, if it doesn't already exist."""
158
+ self.platform_dir_path = dsd_config.project_root / ".platform"
159
+ plugin_utils.add_dir(self.platform_dir_path)
160
+
161
+ def _add_services_yaml(self):
162
+ """Add the .platform/services.yaml file."""
163
+
164
+ template_path = self.templates_path / "services.yaml"
165
+ contents = plugin_utils.get_template_string(template_path, context=None)
166
+
167
+ path = self.platform_dir_path / "services.yaml"
168
+ plugin_utils.add_file(path, contents)
169
+
170
+ def _settings_env_var(self):
171
+ """Set the DJANGO_SETTINGS_MODULE env var, if needed."""
172
+ # This is primarily for Wagtail projects, as signified by a settings/production.py file.
173
+ if dsd_config.settings_path.parts[-2:] == ("settings", "production.py"):
174
+ plugin_utils.write_output(" Setting DJANGO_SETTINGS_MODULE environment variable...")
175
+
176
+ # Need form mysite.settings.production
177
+ dotted_settings_path = ".".join(dsd_config.settings_path.parts[-3:]).removesuffix(".py")
178
+
179
+ cmd = f"upsun variable:create --level environment --environment main --name DJANGO_SETTINGS_MODULE --value {dotted_settings_path} --no-interaction --visible-build true --prefix env"
180
+ output = plugin_utils.run_quick_command(cmd)
181
+ plugin_utils.write_output(output)
182
+
183
+ def _conclude_automate_all(self):
184
+ """Finish automating the push to Upsun.
185
+
186
+ - Commit all changes.
187
+ - Call `upsun push`.
188
+ - Open project.
189
+ """
190
+ # Making this check here lets deploy() be cleaner.
191
+ if not dsd_config.automate_all:
192
+ return
193
+
194
+ plugin_utils.commit_changes()
195
+
196
+ # Push project.
197
+ plugin_utils.write_output(" Pushing to Upsun...")
198
+
199
+ # Pause to make sure project that was just created can be used.
200
+ plugin_utils.write_output(
201
+ " Pausing 10s to make sure project is ready to use..."
202
+ )
203
+ time.sleep(10)
204
+
205
+ # Use run_slow_command(), to stream output as it runs.
206
+ cmd = "upsun push --yes"
207
+ plugin_utils.run_slow_command(cmd)
208
+
209
+ # Open project.
210
+ plugin_utils.write_output(" Opening deployed app in a new browser tab...")
211
+ cmd = "upsun url --yes"
212
+ output = plugin_utils.run_quick_command(cmd)
213
+ plugin_utils.write_output(output)
214
+
215
+ # Get url of deployed project.
216
+ # This can be done with an re, but there's one line of output with
217
+ # a url, so finding that line is simpler.
218
+ # DEV: Move this to a utility, and write a test against standard Upsun
219
+ # output.
220
+ self.deployed_url = ""
221
+ for line in output.stdout.decode().split("\n"):
222
+ if "https" in line:
223
+ self.deployed_url = line.strip()
224
+
225
+ def _show_success_message(self):
226
+ """After a successful run, show a message about what to do next."""
227
+
228
+ # DEV:
229
+ # - Mention that this script should not need to be run again unless creating
230
+ # a new deployment.
231
+ # - Describe ongoing approach of commit, push, migrate. Lots to consider
232
+ # when doing this on production app with users, make sure you learn.
233
+
234
+ if dsd_config.automate_all:
235
+ msg = upsun_msgs.success_msg_automate_all(self.deployed_url)
236
+ plugin_utils.write_output(msg)
237
+ else:
238
+ msg = upsun_msgs.success_msg(dsd_config.log_output)
239
+ plugin_utils.write_output(msg)
240
+
241
+ # --- Helper methods for methods called from deploy.py ---
242
+
243
+ def _check_upsun_settings(self):
244
+ """Check to see if an Upsun settings block already exists."""
245
+ start_line = "# Upsun settings."
246
+ plugin_utils.check_settings(
247
+ "Upsun",
248
+ start_line,
249
+ upsun_msgs.upsun_settings_found,
250
+ upsun_msgs.cant_overwrite_settings,
251
+ )
252
+
253
+ def _validate_cli(self):
254
+ """Make sure the Upsun CLI is installed, and user is authenticated."""
255
+ cmd = "upsun --version"
256
+
257
+ # This generates a FileNotFoundError on Ubuntu if the CLI is not installed.
258
+ try:
259
+ output_obj = plugin_utils.run_quick_command(cmd)
260
+ except FileNotFoundError:
261
+ raise DSDCommandError(upsun_msgs.cli_not_installed)
262
+
263
+ plugin_utils.log_info(output_obj)
264
+
265
+ # Check that the user is authenticated.
266
+ cmd = "upsun auth:info --no-interaction"
267
+ output_obj = plugin_utils.run_quick_command(cmd)
268
+
269
+ if "Authentication is required." in output_obj.stderr.decode():
270
+ raise DSDCommandError(upsun_msgs.cli_logged_out)
271
+
272
+ def _get_upsun_project_name(self):
273
+ """Get the deployed project name.
274
+
275
+ If using automate_all, we'll set this. Otherwise, we're looking for the name
276
+ that was given in the `upsun create` command.
277
+ - Try to get this from `project:info`.
278
+ - If can't get project name:
279
+ - Exit with warning, and inform user of --deployed-project-name
280
+ flag to override this error.
281
+
282
+ Retuns:
283
+ str: The deployed project name.
284
+ Raises:
285
+ DSDCommandError: If deployed project name can't be found.
286
+ """
287
+ # If we're creating the project, we'll just use the startproject name.
288
+ if dsd_config.automate_all:
289
+ return dsd_config.local_project_name
290
+
291
+ # Use the provided name if --deployed-project-name specified.
292
+ if dsd_config.deployed_project_name:
293
+ return dsd_config.deployed_project_name
294
+
295
+ # Use --yes flag to avoid interactive prompt hanging in background
296
+ # if the user is not currently logged in to the CLI.
297
+ cmd = "upsun project:info --yes --format csv"
298
+ output_obj = plugin_utils.run_quick_command(cmd)
299
+ output_str = output_obj.stdout.decode()
300
+
301
+ # Log cmd, but don't log the output of `project:info`. It contains identifying
302
+ # information about the user and project, including client_ssh_key.
303
+ plugin_utils.log_info(cmd)
304
+
305
+ # If there's no stdout, the user is probably logged out, hasn't called
306
+ # create, or doesn't have the CLI installed.
307
+ # Also, I've seen both ProjectNotFoundException and RootNotFoundException
308
+ # raised when no project has been created.
309
+ if not output_str:
310
+ output_str = output_obj.stderr.decode()
311
+ if "LoginRequiredException" in output_str:
312
+ raise DSDCommandError(upsun_msgs.login_required)
313
+ elif "ProjectNotFoundException" in output_str:
314
+ raise DSDCommandError(upsun_msgs.no_project_name)
315
+ elif "RootNotFoundException" in output_str:
316
+ raise DSDCommandError(upsun_msgs.no_project_name)
317
+ else:
318
+ error_msg = upsun_msgs.unknown_error
319
+ error_msg += upsun_msgs.cli_not_installed
320
+ raise DSDCommandError(error_msg)
321
+
322
+ # Pull deployed project name from output.
323
+ lines = output_str.splitlines()
324
+ title_line = [line for line in lines if "title," in line][0]
325
+ # Assume first project is one to use.
326
+ project_name = title_line.split(",")[1].strip()
327
+ project_name = upsun_utils.get_project_name(output_str)
328
+
329
+ # Project names can only have lowercase alphanumeric characters.
330
+ # See: https://github.com/ehmatthes/django-simple-deploy/issues/323
331
+ if " " in project_name:
332
+ project_name = project_name.replace(" ", "_").lower()
333
+ if project_name:
334
+ return project_name
335
+
336
+ # Couldn't find a project name. Warn user, and tell them about override flag.
337
+ raise DSDCommandError(upsun_msgs.no_project_name)
338
+
339
+ def _get_org_name(self):
340
+ """Get the organization name associated with the user's Upsun account.
341
+
342
+ This is needed for creating a project using automate_all.
343
+ Confirm that it's okay to use this org.
344
+
345
+ Returns:
346
+ str: org name
347
+ None: if not using automate-all
348
+ Raises:
349
+ DSDCommandError:
350
+ - if org name found, but not confirmed.
351
+ - if org name not found
352
+ """
353
+ if not dsd_config.automate_all:
354
+ return
355
+
356
+ cmd = "upsun organization:list --yes --format csv"
357
+ output_obj = plugin_utils.run_quick_command(cmd)
358
+ output_str = output_obj.stdout.decode()
359
+ plugin_utils.log_info(output_str)
360
+
361
+ org_names = upsun_utils.get_org_names(output_str)
362
+ if not org_names:
363
+ raise DSDCommandError(upsun_msgs.org_not_found)
364
+
365
+ if len(org_names) == 1:
366
+ # Get permission to use this org.
367
+ org_name = org_names[0]
368
+ if self._confirm_use_org(org_name):
369
+ return org_name
370
+
371
+ # Show all orgs, ask user to make selection.
372
+ prompt = "\n*** Found multiple orgs on Upsun. ***"
373
+ for index, name in enumerate(org_names):
374
+ prompt += f"\n {index}: {name}"
375
+ prompt += "\nWhich org would you like to use? "
376
+
377
+ valid_choices = [i for i in range(len(org_names))]
378
+
379
+ # Confirm selection, because we do *not* want to deploy using the wrong org.
380
+ confirmed = False
381
+ while not confirmed:
382
+ selection = plugin_utils.get_numbered_choice(
383
+ prompt, valid_choices, upsun_msgs.no_org_available
384
+ )
385
+ selected_org = org_names[selection]
386
+
387
+ confirm_prompt = f"You have selected {selected_org}."
388
+ confirm_prompt += " Is that correct?"
389
+ confirmed = plugin_utils.get_confirmation(confirm_prompt)
390
+
391
+ return selected_org
392
+
393
+ def _confirm_use_org(self, org_name):
394
+ """Confirm that it's okay to use the org that was found.
395
+
396
+ Returns:
397
+ True: if confirmed
398
+ DSDCommandError: if not confirmed
399
+ """
400
+
401
+ dsd_config.stdout.write(upsun_msgs.confirm_use_org(org_name))
402
+ confirmed = plugin_utils.get_confirmation(skip_logging=True)
403
+
404
+ if confirmed:
405
+ dsd_config.stdout.write(" Okay, continuing with deployment.")
406
+ return True
407
+ else:
408
+ # Exit, with a message that configuration is still an option.
409
+ msg = upsun_msgs.cancel_upsun
410
+ msg += upsun_msgs.may_configure
411
+ raise DSDCommandError(msg)
@@ -0,0 +1,27 @@
1
+ """Config class for plugin information shared with core."""
2
+
3
+ from . import deploy_messages as upsun_msgs
4
+
5
+
6
+ class PluginConfig:
7
+ """Class for managing attributes that need to be shared with core.
8
+
9
+ This is similar to the class DSDConfig in core's dsd_config.py.
10
+
11
+ This should future-proof plugins somewhat, in that if more information needs
12
+ to be shared back to core, it can be added here without breaking changes to the
13
+ core-plugin interface.
14
+
15
+ Get plugin-specific attributes required by core.
16
+
17
+ Required:
18
+ - automate_all_supported
19
+ - platform_name
20
+ Optional:
21
+ - confirm_automate_all_msg (required if automate_all_supported is True)
22
+ """
23
+
24
+ def __init__(self):
25
+ self.automate_all_supported = True
26
+ self.confirm_automate_all_msg = upsun_msgs.confirm_automate_all
27
+ self.platform_name = "Upsun"
@@ -0,0 +1,62 @@
1
+ # This file describes an application. You can have multiple applications
2
+ # in the same project.
3
+ #
4
+ # See https://fixed.docs.upsun.com/create-apps.html
5
+
6
+ # The name of this app. Must be unique within a project.
7
+ name: '{{ deployed_project_name }}'
8
+
9
+ # The runtime the application uses.
10
+ type: 'python:3.10'
11
+
12
+ # The relationships of the application with services or other applications.
13
+ #
14
+ # The left-hand side is the name of the relationship as it will be exposed
15
+ # to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand
16
+ # side is in the form `<service name>:<endpoint name>`.
17
+ relationships:
18
+ database: "db:postgresql"
19
+
20
+ # The configuration of app when it is exposed to the web.
21
+ web:
22
+ # Whether your app should speak to the webserver via TCP or Unix socket
23
+ # https://fixed.docs.upsun.com/guides/django/deploy/configure.html#how-to-start-your-app
24
+ upstream:
25
+ socket_family: unix
26
+ # Commands are run once after deployment to start the application process.
27
+ commands:
28
+ start: "pipenv run gunicorn -w 4 -b unix:$SOCKET {{ project_name }}.wsgi:application"
29
+ locations:
30
+ "/":
31
+ passthru: true
32
+ "/static":
33
+ root: "static"
34
+ expires: 1h
35
+ allow: true
36
+
37
+ # The size of the persistent disk of the application (in MB).
38
+ disk: 512
39
+
40
+ # Set a local R/W mount for logs
41
+ mounts:
42
+ 'logs':
43
+ source: local
44
+ source_path: logs
45
+
46
+ # The hooks executed at various points in the lifecycle of the application.
47
+ hooks:
48
+ # The build hook runs before the application is deployed, and is useful for
49
+ # assembling the codebase.
50
+ # DEV: Why remove logs right after making them?
51
+ # Also, may want to split requirements into requirements.txt and requirements_remote.txt.
52
+ build: |
53
+ pip install --upgrade pip
54
+ pip install pipenv
55
+ pipenv install
56
+
57
+ mkdir logs
58
+ pipenv run python manage.py collectstatic
59
+ rm -rf logs
60
+ deploy: |
61
+ pipenv run python manage.py migrate
62
+
@@ -0,0 +1,60 @@
1
+ # This file describes an application. You can have multiple applications
2
+ # in the same project.
3
+ #
4
+ # See https://fixed.docs.upsun.com/create-apps.html
5
+
6
+ # The name of this app. Must be unique within a project.
7
+ name: '{{ deployed_project_name }}'
8
+
9
+ # The runtime the application uses.
10
+ type: 'python:3.12'
11
+
12
+ # The relationships of the application with services or other applications.
13
+ #
14
+ # The left-hand side is the name of the relationship as it will be exposed
15
+ # to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand
16
+ # side is in the form `<service name>:<endpoint name>`.
17
+ relationships:
18
+ database: "db:postgresql"
19
+
20
+ # The configuration of app when it is exposed to the web.
21
+ web:
22
+ # Whether your app should speak to the webserver via TCP or Unix socket
23
+ # https://fixed.docs.upsun.com/guides/django/deploy/configure.html#how-to-start-your-app
24
+ upstream:
25
+ socket_family: unix
26
+ # Commands are run once after deployment to start the application process.
27
+ commands:
28
+ start: "gunicorn -w 4 -b unix:$SOCKET {{ project_name }}.wsgi:application"
29
+ locations:
30
+ "/":
31
+ passthru: true
32
+ "/static":
33
+ root: "static"
34
+ expires: 1h
35
+ allow: true
36
+
37
+ # The size of the persistent disk of the application (in MB).
38
+ disk: 512
39
+
40
+ # Set a local R/W mount for logs
41
+ mounts:
42
+ 'logs':
43
+ source: local
44
+ source_path: logs
45
+
46
+ # The hooks executed at various points in the lifecycle of the application.
47
+ hooks:
48
+ # The build hook runs before the application is deployed, and is useful for
49
+ # assembling the codebase.
50
+ # DEV: Why remove logs right after making them?
51
+ # Also, may want to split requirements into requirements.txt and requirements_remote.txt.
52
+ build: |
53
+ pip install --upgrade pip
54
+ pip install -r requirements.txt
55
+
56
+ mkdir logs
57
+ python manage.py collectstatic
58
+ rm -rf logs
59
+ deploy: |
60
+ python manage.py migrate
@@ -0,0 +1,74 @@
1
+ # This file describes an application. You can have multiple applications
2
+ # in the same project.
3
+ #
4
+ # See https://fixed.docs.upsun.com/create-apps.html
5
+
6
+ # The name of this app. Must be unique within a project.
7
+ name: '{{ deployed_project_name }}'
8
+
9
+ # The runtime the application uses.
10
+ type: 'python:3.10'
11
+
12
+ # Set properties for poetry.
13
+ variables:
14
+ env:
15
+ POETRY_VERSION: '1.3.1'
16
+ POETRY_VIRTUALENVS_IN_PROJECT: true
17
+ POETRY_VIRTUALENVS_CREATE: false
18
+
19
+ # The relationships of the application with services or other applications.
20
+ #
21
+ # The left-hand side is the name of the relationship as it will be exposed
22
+ # to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand
23
+ # side is in the form `<service name>:<endpoint name>`.
24
+ relationships:
25
+ database: "db:postgresql"
26
+
27
+ # The configuration of app when it is exposed to the web.
28
+ web:
29
+ # Whether your app should speak to the webserver via TCP or Unix socket
30
+ # https://fixed.docs.upsun.com/guides/django/deploy/configure.html#how-to-start-your-app
31
+ upstream:
32
+ socket_family: unix
33
+ # Commands are run once after deployment to start the application process.
34
+ commands:
35
+ start: "/app/.local/bin/poetry run gunicorn -w 4 -b unix:$SOCKET {{ project_name }}.wsgi:application"
36
+ locations:
37
+ "/":
38
+ passthru: true
39
+ "/static":
40
+ root: "static"
41
+ expires: 1h
42
+ allow: true
43
+
44
+ # The size of the persistent disk of the application (in MB).
45
+ disk: 512
46
+
47
+ # Set a local R/W mount for logs
48
+ mounts:
49
+ 'logs':
50
+ source: local
51
+ source_path: logs
52
+
53
+ # The hooks executed at various points in the lifecycle of the application.
54
+ hooks:
55
+ # The build hook runs before the application is deployed, and is useful for
56
+ # assembling the codebase.
57
+ # DEV: Why remove logs right after making them?
58
+ # Also, may want to split requirements into requirements.txt and requirements_remote.txt.
59
+ build: |
60
+ set -e
61
+ pip install --upgrade pip
62
+ export PIP_USER=false
63
+ curl -sSL https://install.python-poetry.org | python3 - --version $POETRY_VERSION
64
+ export PIP_USER=true
65
+ /app/.local/bin/poetry lock
66
+ /app/.local/bin/poetry export -f requirements.txt --output requirements.txt --without-hashes --with deploy
67
+ pip install -r requirements.txt
68
+
69
+ mkdir logs
70
+ python manage.py collectstatic
71
+ rm -rf logs
72
+ deploy: |
73
+ python manage.py migrate
74
+
@@ -0,0 +1,11 @@
1
+
2
+ # The services of the project.
3
+ #
4
+ # Each service listed will be deployed in its own container as part of your
5
+ # Platform.sh project.
6
+ #
7
+ # See https://fixed.docs.upsun.com/add-services.html
8
+
9
+ db:
10
+ type: postgresql:16
11
+ disk: 1024
@@ -0,0 +1,41 @@
1
+ {{current_settings}}
2
+
3
+ # Upsun settings.
4
+ import os
5
+
6
+ if os.environ.get("PLATFORM_APPLICATION_NAME"):
7
+ # Import some Upsun settings from the environment.
8
+ from platformshconfig import Config
9
+
10
+ config = Config()
11
+
12
+ try:
13
+ ALLOWED_HOSTS.append("*")
14
+ except NameError:
15
+ ALLOWED_HOSTS = ["*"]
16
+
17
+ DEBUG = False
18
+
19
+ STATIC_URL = "/static/"
20
+
21
+ if config.appDir:
22
+ STATIC_ROOT = os.path.join(config.appDir, "static")
23
+ if config.projectEntropy:
24
+ SECRET_KEY = config.projectEntropy
25
+
26
+ if not config.in_build():
27
+ db_settings = config.credentials("database")
28
+ DATABASES = {
29
+ "default": {
30
+ "ENGINE": "django.db.backends.postgresql",
31
+ "NAME": db_settings["path"],
32
+ "USER": db_settings["username"],
33
+ "PASSWORD": db_settings["password"],
34
+ "HOST": db_settings["host"],
35
+ "PORT": db_settings["port"],
36
+ },
37
+ "sqlite": {
38
+ "ENGINE": "django.db.backends.sqlite3",
39
+ "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
40
+ },
41
+ }
dsd_upsun/utils.py ADDED
@@ -0,0 +1,36 @@
1
+ """Utilities specific to Upsun deployments."""
2
+
3
+
4
+ def get_project_name(output_str):
5
+ """Get the project name from the output of `upsun project:info`.
6
+
7
+ Command is run with `--format csv` flag.
8
+
9
+ Returns:
10
+ str: project name
11
+ """
12
+ lines = output_str.splitlines()
13
+ title_line = [line for line in lines if "title," in line][0]
14
+ # Assume first project is one to use.
15
+ project_name = title_line.split(",")[1].strip()
16
+
17
+ return project_name
18
+
19
+
20
+ def get_org_names(output_str):
21
+ """Get org names from output of `upsun organization:list --yes --format csv`.
22
+
23
+ Sample input:
24
+ Name,Label,Owner email
25
+ <org-name>,<org-label>,<org-owner@example.com>
26
+ <org-name-2>,<org-label-2>,<org-owner-2@example.com>
27
+
28
+ Returns:
29
+ list: [str]
30
+ None: If user has no organizations.
31
+ """
32
+ if "No organizations found." in output_str:
33
+ return None
34
+
35
+ lines = output_str.split("\n")[1:]
36
+ return [line.split(",")[0] for line in lines if line]
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: dsd-upsun
3
+ Version: 0.1.0
4
+ Summary: A plugin for django-simple-deploy, supporting deployments to Upsun.
5
+ Author-email: Eric Matthes <ehmatthes@gmail.com>
6
+ Project-URL: Documentation, https://github.com/django-simple-deploy/dsd-upsun
7
+ Project-URL: GitHub, https://github.com/django-simple-deploy/dsd-upsun
8
+ Project-URL: Changelog, https://github.com/django-simple-deploy/dsd-upsun/blob/main/CHANGELOG.md
9
+ Keywords: django,deployment
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: Django :: 4.2
12
+ Classifier: Framework :: Django :: 5.1
13
+ Classifier: Framework :: Django :: 5.2
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: django>=4.2
28
+ Requires-Dist: pluggy>=1.5.0
29
+ Requires-Dist: toml>=0.10.2
30
+ Requires-Dist: requests>=2.32.2
31
+ Requires-Dist: django-simple-deploy>=1.3.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: black>=24.1.0; extra == "dev"
34
+ Requires-Dist: build>=1.2.1; extra == "dev"
35
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
36
+ Requires-Dist: twine>=5.1.1; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # dsd-upsun
40
+
41
+ A plugin for deploying Django projects to Upsun, using django-simple-deploy.
42
+
43
+ For full documentation, see the documentation for [django-simple-deploy](https://django-simple-deploy.readthedocs.io/en/latest/).
@@ -0,0 +1,16 @@
1
+ dsd_upsun/__init__.py,sha256=Hhn4V_hCKQV525mBTRr7oHtiSeaJOGrxuI00HABDffQ,73
2
+ dsd_upsun/deploy.py,sha256=nsKWnh8UQH6ddlqNTI7jYU1oLyMMAaqrwCJuVbUrTeQ,557
3
+ dsd_upsun/deploy_messages.py,sha256=Ht5-mgEM_rVmBaS6gQm6VBkXklI-Y4r4uK1lq-09xys,6839
4
+ dsd_upsun/platform_deployer.py,sha256=aWrAuw8IFRkGCAVdvw59jmOgwtEdLuweloSI1XqWqLs,15763
5
+ dsd_upsun/plugin_config.py,sha256=QAhymAPun38GSm7_yTKXVUf_kPJD8nt2psfMFJQuLBE,853
6
+ dsd_upsun/utils.py,sha256=YmmjDCJ23nOI1pqqnOnm6sSJ2ejUrMgdR6o75VaDfk8,999
7
+ dsd_upsun/templates/pipenv.platform.app.yaml,sha256=jeIR5PhPka5Y8kW3hBmJ1yp1PqNyKCBBW4mH0T8pc_k,1973
8
+ dsd_upsun/templates/platform.app.yaml,sha256=N2mq6nlJRb7HCPO9_I4F9jcmNJNgpg5tO_5m3pS5QG4,1930
9
+ dsd_upsun/templates/poetry.platform.app.yaml,sha256=OrU6FJa-kzwTK-VT5414X09ionkARWjIQpD0w3KKqO0,2419
10
+ dsd_upsun/templates/services.yaml,sha256=eLN8qe9VZqMt9o9DKlA11vRGYiO1Qg1qBw-yWqaKXLA,231
11
+ dsd_upsun/templates/settings.py,sha256=EJVGvX4wNdyhw10--vf4Z75pySLIJqHWp6lN0RQoKBA,1134
12
+ dsd_upsun-0.1.0.dist-info/licenses/LICENSE,sha256=eE0PSpGK-C_T72lmuE6kQ_Am4fOlJympaeS_aItmiws,1484
13
+ dsd_upsun-0.1.0.dist-info/METADATA,sha256=JWuwPCHNJaiM4JLatk461J7mWUsnpNSiZyvkxWxwg0w,1802
14
+ dsd_upsun-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ dsd_upsun-0.1.0.dist-info/top_level.txt,sha256=3WChrd4xa_Bh0gQG6SKGhSuKEfYgWRxWZrqAcdUh4W0,10
16
+ dsd_upsun-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,11 @@
1
+ Copyright (c) Eric Matthes and individual contributors.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ dsd_upsun