pystrano 1.0.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.
pystrano-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 lexpank
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.1
2
+ Name: pystrano
3
+ Version: 1.0.0
4
+ Summary: A Python package for managing deploying Django applications (like Capistrano for Ruby)
5
+ Home-page: https://github.com/lexpank/pystrano
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 lexpank
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: homepage, https://github.com/lexpank/pystrano
29
+ Project-URL: issues, https://github.com/lexpank/pystrano/issues
30
+ Keywords: pystrano
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Requires-Python: >=3.10
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: fabric<4.0,>=3.2.2
41
+ Requires-Dist: click<9.0,>=8.1.7
42
+ Requires-Dist: pyyaml<7.0,>=6.0.2
43
+ Requires-Dist: python-dotenv<2.0,>=1.0.1
44
+
45
+ # Pystrano
46
+
47
+ Pystrano is a simple deployment tool for Python projects.
48
+ It is inspired by Capistrano, a popular deployment tool for
49
+ Ruby projects.
50
+
51
+ ## Disclaimer
52
+
53
+ This is a work in progress. It is not ready for production use
54
+ just yet. Proceed with caution. Currently used with Ubuntu
55
+ by yours truly. If someone finds it useful and wants to use it
56
+ in any capacity, don't hesitate.
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ pip install pystrano
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ Pystrano is a command line tool. To deploy a project, you need
67
+ to create a config for the environment you want to deploy to.
68
+
69
+ ### Configuration
70
+
71
+ Pystrano uses a YAML file to configure the deployment. It contains two sections: `common` and `servers`. Variables in `common` section are shared across all servers, while in `servers` section you define a list of servers to deploy to. It is also possible to define server-specific variables, which will override the common ones (if defined).
72
+
73
+ Here is a description of variables you can set in the config file:
74
+
75
+ - `source_code_url`: The URL of the git repository;
76
+ - `project_root`: The directory where the project is located;
77
+ - `project_user`: The user that will be used to deploy the project;
78
+ - `venv_dir`: The directory where the virtual environment is located (in the `project_user` home);
79
+ - `keep_releases`: The number of releases to keep on the server. If set to 0 or less, all releases will be kept;
80
+ - `system_packages`: A list of system packages to install on the server (during setup);
81
+ - `env_file`: The path to the environment file to use for the deployment;
82
+ - `ssh_known_hosts`: The path to the known hosts file to use for the deployment (during setup; separated by semicolons);
83
+ - `service_file`: The path to the service file to set up/use in deployment (optional);
84
+ - `secrets`: List of secrets to set up on the server (during setup only for now; separated by semicolons);
85
+ - `branch`: The name of the branch to deploy.
86
+
87
+ Server-specific variables:
88
+
89
+ - `host`: The hostname of the server;
90
+ - `port`: The port to use for SSH connection (optional, default is 22);
91
+ - `run_migrations`: Whether to run migrations on deployment (optional, default is false);
92
+ - `run_collectstatic`: Whether to run collectstatic on deployment (optional, default is false);
93
+
94
+ ### Deployment
95
+
96
+ To set up deployment for a project, run the following command:
97
+
98
+ ```bash
99
+ pystrano-setup --config-file=path/to/config.yml
100
+ ```
101
+
102
+ This will set up your server to be ready for deployment.
103
+
104
+ To deploy your project, run the following command:
105
+
106
+ ```bash
107
+ pystrano --config-file=path/to/config.yml
108
+ ```
@@ -0,0 +1,64 @@
1
+ # Pystrano
2
+
3
+ Pystrano is a simple deployment tool for Python projects.
4
+ It is inspired by Capistrano, a popular deployment tool for
5
+ Ruby projects.
6
+
7
+ ## Disclaimer
8
+
9
+ This is a work in progress. It is not ready for production use
10
+ just yet. Proceed with caution. Currently used with Ubuntu
11
+ by yours truly. If someone finds it useful and wants to use it
12
+ in any capacity, don't hesitate.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install pystrano
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Pystrano is a command line tool. To deploy a project, you need
23
+ to create a config for the environment you want to deploy to.
24
+
25
+ ### Configuration
26
+
27
+ Pystrano uses a YAML file to configure the deployment. It contains two sections: `common` and `servers`. Variables in `common` section are shared across all servers, while in `servers` section you define a list of servers to deploy to. It is also possible to define server-specific variables, which will override the common ones (if defined).
28
+
29
+ Here is a description of variables you can set in the config file:
30
+
31
+ - `source_code_url`: The URL of the git repository;
32
+ - `project_root`: The directory where the project is located;
33
+ - `project_user`: The user that will be used to deploy the project;
34
+ - `venv_dir`: The directory where the virtual environment is located (in the `project_user` home);
35
+ - `keep_releases`: The number of releases to keep on the server. If set to 0 or less, all releases will be kept;
36
+ - `system_packages`: A list of system packages to install on the server (during setup);
37
+ - `env_file`: The path to the environment file to use for the deployment;
38
+ - `ssh_known_hosts`: The path to the known hosts file to use for the deployment (during setup; separated by semicolons);
39
+ - `service_file`: The path to the service file to set up/use in deployment (optional);
40
+ - `secrets`: List of secrets to set up on the server (during setup only for now; separated by semicolons);
41
+ - `branch`: The name of the branch to deploy.
42
+
43
+ Server-specific variables:
44
+
45
+ - `host`: The hostname of the server;
46
+ - `port`: The port to use for SSH connection (optional, default is 22);
47
+ - `run_migrations`: Whether to run migrations on deployment (optional, default is false);
48
+ - `run_collectstatic`: Whether to run collectstatic on deployment (optional, default is false);
49
+
50
+ ### Deployment
51
+
52
+ To set up deployment for a project, run the following command:
53
+
54
+ ```bash
55
+ pystrano-setup --config-file=path/to/config.yml
56
+ ```
57
+
58
+ This will set up your server to be ready for deployment.
59
+
60
+ To deploy your project, run the following command:
61
+
62
+ ```bash
63
+ pystrano --config-file=path/to/config.yml
64
+ ```
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pystrano"
7
+ version = "1.0.0"
8
+ description = "A Python package for managing deploying Django applications (like Capistrano for Ruby)"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ classifiers = [
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11 ",
17
+ "Programming Language :: Python :: 3.12",
18
+ ]
19
+ keywords = ["pystrano"]
20
+ dependencies = [
21
+ "fabric>=3.2.2,<4.0",
22
+ "click>=8.1.7,<9.0",
23
+ "pyyaml>=6.0.2,<7.0",
24
+ "python-dotenv>=1.0.1,<2.0",
25
+ ]
26
+ requires-python = ">=3.12"
27
+
28
+ [project.urls]
29
+ homepage = "https://github.com/lexpank/pystrano"
30
+ issues = "https://github.com/lexpank/pystrano/issues"
31
+
32
+ [project.scripts]
33
+ pystrano = "pystrano.deploy:main"
34
+ pystrano-setup = "pystrano.deploy:setup"
35
+
36
+ [tool.bumpver]
37
+ current_version = "1.0.0"
38
+ version_pattern = "MAJOR.MINOR.PATCH"
39
+ commit_message = "Bump version {old_version} -> {new_version}"
40
+ commit = true
41
+ tag = true
42
+ push = false
43
+
44
+ [tool.bumpver.file_patterns]
45
+ "pyproject.toml" = [
46
+ 'current_version = "{version}"',
47
+ 'version = "{version}"'
48
+ ]
49
+ "setup.py" = [
50
+ "{version}",
51
+ ]
52
+ "README.md" = [
53
+ "{version}",
54
+ ]
55
+
File without changes
@@ -0,0 +1,98 @@
1
+ from dotenv import dotenv_values
2
+ from yaml import safe_load
3
+ from os import path
4
+
5
+
6
+ class PystranoConfig(object):
7
+ """A class to represent a Pystrano server configuration."""
8
+ def __init__(self):
9
+ self._config_finalized = False
10
+
11
+ def update_dict(self, data):
12
+ """Update the configuration with the given key-value pairs overwriting previous values."""
13
+ self.__dict__.update(**data)
14
+ self._clean()
15
+
16
+ def _clean(self):
17
+ """Clean up the configuration values for later use."""
18
+
19
+ # Convert ssh_known_hosts to a list
20
+ if hasattr(self, "ssh_known_hosts") and isinstance(getattr(self, "ssh_known_hosts"), str):
21
+ setattr(self, "ssh_known_hosts", getattr(self, "ssh_known_hosts", "").split(";"))
22
+
23
+ # Convert secrets to a list
24
+ if hasattr(self, "secrets") and isinstance(getattr(self, "secrets"), str):
25
+ setattr(self, "secrets", getattr(self, "secrets", "").split(";"))
26
+
27
+ def _load_env_file(self):
28
+ """Load the environment file and return the values as a dictionary."""
29
+ return dotenv_values(self.env_file)
30
+
31
+ def finalize_config(self):
32
+ """Finalize the configuration by cleaning up the values."""
33
+ if self._config_finalized:
34
+ return
35
+
36
+ # Mark the configuration as finalized so this method is not called again
37
+ self._config_finalized = True
38
+
39
+ self._clean()
40
+
41
+ if hasattr(self, "project_user") and hasattr(self, "project_root"):
42
+ setattr(self, "project_root", path.join("/home", self.project_user, self.project_root))
43
+ setattr(self, "releases_dir", path.join(self.project_root, "releases"))
44
+ setattr(self, "current_dir", path.join(self.project_root, "current"))
45
+ setattr(self, "shared_dir", path.join(self.project_root, "shared"))
46
+
47
+ if hasattr(self, "venv_dir"):
48
+ setattr(self, "venv_dir", path.join("/home", self.project_user, self.venv_dir))
49
+ setattr(self, "python_path", path.join(self.venv_dir, "bin", "python"))
50
+
51
+ if hasattr(self, "env_file"):
52
+ setattr(self, "env_vars", self._load_env_file())
53
+
54
+ if hasattr(self, "service_file"):
55
+ setattr(self, "service_file_name", path.basename(self.service_file))
56
+
57
+ if hasattr(self, "run_migrations"):
58
+ setattr(self, "run_migrations", self.run_migrations.lower() == "true")
59
+ else:
60
+ setattr(self, "run_migrations", False)
61
+
62
+ if hasattr(self, "collect_static_files"):
63
+ setattr(self, "collect_static_files", self.collect_static_files.lower() == "true")
64
+ else:
65
+ setattr(self, "collect_static_files", False)
66
+
67
+ if hasattr(self, "port"):
68
+ setattr(self, "port", int(self.port))
69
+ else:
70
+ setattr(self, "port", 22)
71
+
72
+
73
+
74
+ def create_server_config(server_description: dict, common_config: dict) -> PystranoConfig:
75
+ """Create a Pystrano server configuration from the given server description and common configuration."""
76
+ config = PystranoConfig()
77
+
78
+ # Load common variables first
79
+ config.update_dict(common_config)
80
+
81
+ # Load server specific variables (potentially overwriting common variables)
82
+ config.update_dict(server_description)
83
+
84
+ # Finalize the configuration
85
+ config.finalize_config()
86
+
87
+ return config
88
+
89
+ def load_config(config_path: str) -> list[PystranoConfig]:
90
+ """Load Pystrano server configurations from the given YAML config file."""
91
+ with open(config_path) as f:
92
+ config = safe_load(f)
93
+
94
+ # Get common configuration
95
+ common_config = config.pop("common")
96
+
97
+ # Create a cofinguration for each server
98
+ return [create_server_config(server, common_config) for server in config["servers"]]
@@ -0,0 +1,151 @@
1
+ from fabric import Connection
2
+ from pystrano.config import PystranoConfig
3
+ from os import path
4
+
5
+
6
+ def setup_release_dir(connection: Connection, new_release_dir: str):
7
+ """Set up a new release directory on the server."""
8
+ connection.run(f'mkdir -p {new_release_dir}')
9
+
10
+
11
+ def update_source_code(connection: Connection, new_release_dir: str, conf: PystranoConfig):
12
+ """Update the source code in the new release directory."""
13
+ connection.run(f'git clone {conf.source_code_url} {new_release_dir}')
14
+ connection.run(f'cd {new_release_dir} && git checkout {conf.branch}')
15
+
16
+
17
+ def setup_symlinks(connection: Connection, new_release_dir: str, conf: PystranoConfig):
18
+ """Set up symlinks to shared directories."""
19
+ connection.run(f'ln -sfn {conf.shared_dir}/media {new_release_dir}/media')
20
+ connection.run(f'ln -sfn {conf.shared_dir}/.env.{new_release_dir.split("/")[-1]} {conf.shared_dir}/.env')
21
+
22
+
23
+ def install_requirements(connection: Connection, new_release_dir: str, conf: PystranoConfig):
24
+ """Install dependencies in the virtual environment."""
25
+ with connection.cd(new_release_dir):
26
+ connection.run(f'{conf.venv_dir}/bin/pip install -r requirements.txt')
27
+
28
+
29
+ def collect_static_files(connection: Connection, new_release_dir: str, conf: PystranoConfig):
30
+ """Collect static files to the shared directory."""
31
+ with connection.cd(new_release_dir):
32
+ connection.run(f'{conf.python_path} manage.py collectstatic --noinput', env=conf.env_vars)
33
+
34
+
35
+ def migrate_database(connection: Connection, new_release_dir: str, conf: PystranoConfig):
36
+ """Apply migrations to the database."""
37
+ with connection.cd(new_release_dir):
38
+ connection.run(f'{conf.python_path} manage.py migrate', env=conf.env_vars)
39
+
40
+
41
+ def update_symlink(connection: Connection, new_release_dir: str, conf: PystranoConfig):
42
+ """Update the `current` symlink to point to the new release."""
43
+ connection.run(f'ln -sfn {new_release_dir} {conf.current_dir}')
44
+
45
+
46
+ def restart_service(connection: Connection, conf: PystranoConfig):
47
+ """Restart the Gunicorn service."""
48
+ connection.sudo(f'systemctl restart {conf.service_file_name}')
49
+
50
+
51
+ def cleanup_old_releases(connection: Connection, conf: PystranoConfig):
52
+ """Remove old releases, keeping only the last `KEEP_RELEASES`."""
53
+
54
+ # Allow people to choose to keep all releases
55
+ if conf.keep_releases <= 0:
56
+ return
57
+
58
+ releases = connection.run(f'ls -1 {conf.releases_dir}', hide=True).stdout.split()
59
+
60
+ if len(releases) > conf.keep_releases:
61
+ old_releases = releases[:-conf.keep_releases]
62
+ for release in old_releases:
63
+ connection.run(f'rm -rf {conf.releases_dir}/{release}')
64
+
65
+
66
+ def create_project_user(connection: Connection, conf: PystranoConfig):
67
+ """Set up a user if it does not exist."""
68
+
69
+ # Check if user exists
70
+ result = connection.run(f'id -u {conf.project_user}', warn=True)
71
+
72
+ if result.failed:
73
+ # Create user if it doesn't exist
74
+ connection.sudo(f'useradd -m -s /bin/bash {conf.project_user}')
75
+ # Allow sudo without password
76
+ connection.sudo(f'echo "{conf.project_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/{conf.project_user}')
77
+
78
+
79
+ def create_directory_structure(connection: Connection, conf: PystranoConfig):
80
+ """Create the directory structure on the server."""
81
+ connection.sudo(f'mkdir -p {conf.shared_dir}')
82
+ connection.sudo(f'mkdir -p {conf.releases_dir}')
83
+ connection.sudo(f'mkdir -p {conf.venv_dir}')
84
+ connection.sudo(f'chown -R {conf.project_user}:{conf.project_user} {conf.project_root}')
85
+
86
+
87
+ def setup_packages(connection: Connection, conf: PystranoConfig):
88
+ """Install required packages on the server."""
89
+ connection.run('apt update')
90
+ connection.run('apt install -y python3 python3-venv python3-pip git')
91
+
92
+ # Install additional system packages if specified
93
+ if conf.system_packages:
94
+ connection.run(f'apt install -y {conf.system_packages}')
95
+
96
+
97
+ def setup_venv(connection: Connection, conf: PystranoConfig):
98
+ """Set up a virtual environment."""
99
+ connection.run(f'python3 -m venv {conf.venv_dir}')
100
+ connection.run(f'chown -R {conf.project_user}:{conf.project_user} {conf.venv_dir}')
101
+
102
+
103
+ def copy_authorized_keys(connection: Connection, conf: PystranoConfig):
104
+ """Copy the authorized keys to the project user's directory."""
105
+ connection.run(f'mkdir -p /home/{conf.project_user}/.ssh')
106
+ connection.run(f'cp ~/.ssh/authorized_keys /home/{conf.project_user}/.ssh/')
107
+ connection.run(f'chown -R {conf.project_user}:{conf.project_user} /home/{conf.project_user}/.ssh')
108
+
109
+
110
+ def setup_known_hosts(connection: Connection, conf: PystranoConfig):
111
+ """Add known hosts to the project user's SSH configuration."""
112
+ connection.run(f'ssh-keyscan github.com >> /home/{conf.project_user}/.ssh/known_hosts')
113
+
114
+ for host in conf.ssh_known_hosts:
115
+ connection.run(f'ssh-keyscan {host} >> /home/{conf.project_user}/.ssh/known_hosts')
116
+
117
+ connection.run(f'chown {conf.project_user}:{conf.project_user} /home/{conf.project_user}/.ssh/known_hosts')
118
+
119
+
120
+ def setup_service(connection: Connection, conf: PystranoConfig):
121
+ """Set up the service."""
122
+ connection.put(conf.service_file, f'/etc/systemd/system/{conf.service_file_name}')
123
+ connection.sudo('systemctl daemon-reload')
124
+ connection.sudo(f'systemctl enable {conf.service_file_name}')
125
+
126
+
127
+ def try_to_remove_release_dir(connection: Connection, release_dir: str):
128
+ """Try to remove a release directory."""
129
+ try:
130
+ connection.run(f'rm -rf {release_dir}')
131
+ except Exception as e:
132
+ print(f"Error removing release directory: {e}")
133
+
134
+
135
+ def copy_env_file(connection: Connection, new_release_dir: str, conf: PystranoConfig):
136
+ """Copy the environment file to the shared directory."""
137
+ connection.put(conf.env_file, f'{conf.shared_dir}/.env.{new_release_dir.split("/")[-1]}')
138
+
139
+
140
+ def copy_secrets(connection: Connection, conf: PystranoConfig):
141
+ """Copy secrets to the shared directory."""
142
+ for secret in conf.secrets:
143
+ secret_file_name = path.basename(secret)
144
+ connection.put(secret, f'{conf.shared_dir}/{secret_file_name}')
145
+
146
+
147
+ def link_secrets_to_release(connection: Connection, new_release_dir: str, conf: PystranoConfig):
148
+ """Link secrets to the release directory."""
149
+ for secret in conf.secrets:
150
+ secret_file_name = path.basename(secret)
151
+ connection.run(f'ln -sfn {conf.shared_dir}/{secret_file_name} {new_release_dir}/{secret_file_name}')
@@ -0,0 +1,136 @@
1
+ from fabric import Connection
2
+ from datetime import datetime
3
+ from click import command, option
4
+
5
+ from .config import load_config, PystranoConfig
6
+ from .core import (
7
+ setup_release_dir,
8
+ update_source_code,
9
+ setup_symlinks,
10
+ install_requirements,
11
+ collect_static_files,
12
+ migrate_database,
13
+ update_symlink,
14
+ restart_service,
15
+ cleanup_old_releases,
16
+ try_to_remove_release_dir,
17
+ create_project_user,
18
+ copy_authorized_keys,
19
+ create_directory_structure,
20
+ setup_packages,
21
+ setup_venv,
22
+ setup_known_hosts,
23
+ setup_service,
24
+ copy_env_file,
25
+ copy_secrets,
26
+ link_secrets_to_release,
27
+ )
28
+
29
+
30
+ def set_up(server_configurations: list[PystranoConfig]):
31
+ try:
32
+ for server_config in server_configurations:
33
+ print(f"Setting up {server_config.host}")
34
+ c = Connection(f"root@{server_config.host}", forward_agent=True, port=server_config.port)
35
+
36
+ print("Creating project user")
37
+ create_project_user(c, server_config)
38
+
39
+ print("Copy SSH keys from root")
40
+ copy_authorized_keys(c, server_config)
41
+
42
+ print("Creating directory structure")
43
+ create_directory_structure(c, server_config)
44
+
45
+ print("Install necessary packages")
46
+ setup_packages(c, server_config)
47
+
48
+ print("Setting up venv")
49
+ setup_venv(c, server_config)
50
+
51
+ print("Setting up known hosts")
52
+ setup_known_hosts(c, server_config)
53
+
54
+ if hasattr(server_config, "service_file"):
55
+ print("Setting up service that should be executed")
56
+ setup_service(c, server_config)
57
+
58
+ if hasattr(server_config, "secrets"):
59
+ print("Copying secrets to shared directory")
60
+ copy_secrets(c, server_config)
61
+ except Exception as e:
62
+ print(f"Error setting up: {e}")
63
+ exit(1)
64
+
65
+
66
+ def deploy(server_configurations: list[PystranoConfig]):
67
+ timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
68
+
69
+ try:
70
+ for server_config in server_configurations:
71
+ new_release_dir = f"{server_config.releases_dir}/{timestamp}"
72
+
73
+ print(f"Deploying to {server_config.host}")
74
+ c = Connection(f"{server_config.project_user}@{server_config.host}", forward_agent=True, port=server_config.port)
75
+
76
+ print("Creating release directory")
77
+ setup_release_dir(c, new_release_dir)
78
+
79
+ print("Updating source code")
80
+ update_source_code(c, new_release_dir, server_config)
81
+
82
+ print("Copying environment file to shared directory")
83
+ copy_env_file(c, new_release_dir, server_config)
84
+
85
+ print("Setting up symlinks")
86
+ setup_symlinks(c, new_release_dir, server_config)
87
+
88
+ print("Installing requirements")
89
+ install_requirements(c, new_release_dir, server_config)
90
+
91
+ if hasattr(server_config, "secrets"):
92
+ print("Linking secrets to the release directory")
93
+ link_secrets_to_release(c, new_release_dir, server_config)
94
+
95
+ if hasattr(server_config, "collect_static_files") and server_config.collect_static_files:
96
+ print("Collecting static files")
97
+ collect_static_files(c, new_release_dir, server_config)
98
+
99
+ if hasattr(server_config, "run_migrations") and server_config.run_migrations:
100
+ print("Migrating database")
101
+ migrate_database(c, new_release_dir, server_config)
102
+
103
+ print("Updating symlinks")
104
+ update_symlink(c, new_release_dir, server_config)
105
+
106
+ if hasattr(server_config, "service_file"):
107
+ print("Restarting service")
108
+ restart_service(c, server_config)
109
+
110
+ print("Cleaning up old releases")
111
+ cleanup_old_releases(c, server_config)
112
+ except Exception as e:
113
+ print(f"Error deploying: {e}")
114
+ exit(1)
115
+
116
+
117
+ @command()
118
+ @option('--config-file', required=True, help='Path to the configuration file')
119
+ def main(config_file):
120
+ try:
121
+ server_configurations = load_config(config_file)
122
+ deploy(server_configurations)
123
+ except Exception as e:
124
+ print(f"Error deploying servers: {e}")
125
+ exit(1)
126
+
127
+
128
+ @command()
129
+ @option('--config-file', required=True, help='Path to the configuration file')
130
+ def setup(config_file):
131
+ try:
132
+ server_configurations = load_config(config_file)
133
+ set_up(server_configurations)
134
+ except Exception as e:
135
+ print(f"Error setting up servers: {e}")
136
+ exit(1)
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.1
2
+ Name: pystrano
3
+ Version: 1.0.0
4
+ Summary: A Python package for managing deploying Django applications (like Capistrano for Ruby)
5
+ Home-page: https://github.com/lexpank/pystrano
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 lexpank
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: homepage, https://github.com/lexpank/pystrano
29
+ Project-URL: issues, https://github.com/lexpank/pystrano/issues
30
+ Keywords: pystrano
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Requires-Python: >=3.10
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: fabric<4.0,>=3.2.2
41
+ Requires-Dist: click<9.0,>=8.1.7
42
+ Requires-Dist: pyyaml<7.0,>=6.0.2
43
+ Requires-Dist: python-dotenv<2.0,>=1.0.1
44
+
45
+ # Pystrano
46
+
47
+ Pystrano is a simple deployment tool for Python projects.
48
+ It is inspired by Capistrano, a popular deployment tool for
49
+ Ruby projects.
50
+
51
+ ## Disclaimer
52
+
53
+ This is a work in progress. It is not ready for production use
54
+ just yet. Proceed with caution. Currently used with Ubuntu
55
+ by yours truly. If someone finds it useful and wants to use it
56
+ in any capacity, don't hesitate.
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ pip install pystrano
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ Pystrano is a command line tool. To deploy a project, you need
67
+ to create a config for the environment you want to deploy to.
68
+
69
+ ### Configuration
70
+
71
+ Pystrano uses a YAML file to configure the deployment. It contains two sections: `common` and `servers`. Variables in `common` section are shared across all servers, while in `servers` section you define a list of servers to deploy to. It is also possible to define server-specific variables, which will override the common ones (if defined).
72
+
73
+ Here is a description of variables you can set in the config file:
74
+
75
+ - `source_code_url`: The URL of the git repository;
76
+ - `project_root`: The directory where the project is located;
77
+ - `project_user`: The user that will be used to deploy the project;
78
+ - `venv_dir`: The directory where the virtual environment is located (in the `project_user` home);
79
+ - `keep_releases`: The number of releases to keep on the server. If set to 0 or less, all releases will be kept;
80
+ - `system_packages`: A list of system packages to install on the server (during setup);
81
+ - `env_file`: The path to the environment file to use for the deployment;
82
+ - `ssh_known_hosts`: The path to the known hosts file to use for the deployment (during setup; separated by semicolons);
83
+ - `service_file`: The path to the service file to set up/use in deployment (optional);
84
+ - `secrets`: List of secrets to set up on the server (during setup only for now; separated by semicolons);
85
+ - `branch`: The name of the branch to deploy.
86
+
87
+ Server-specific variables:
88
+
89
+ - `host`: The hostname of the server;
90
+ - `port`: The port to use for SSH connection (optional, default is 22);
91
+ - `run_migrations`: Whether to run migrations on deployment (optional, default is false);
92
+ - `run_collectstatic`: Whether to run collectstatic on deployment (optional, default is false);
93
+
94
+ ### Deployment
95
+
96
+ To set up deployment for a project, run the following command:
97
+
98
+ ```bash
99
+ pystrano-setup --config-file=path/to/config.yml
100
+ ```
101
+
102
+ This will set up your server to be ready for deployment.
103
+
104
+ To deploy your project, run the following command:
105
+
106
+ ```bash
107
+ pystrano --config-file=path/to/config.yml
108
+ ```
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ pystrano/__init__.py
6
+ pystrano/config.py
7
+ pystrano/core.py
8
+ pystrano/deploy.py
9
+ pystrano.egg-info/PKG-INFO
10
+ pystrano.egg-info/SOURCES.txt
11
+ pystrano.egg-info/dependency_links.txt
12
+ pystrano.egg-info/entry_points.txt
13
+ pystrano.egg-info/requires.txt
14
+ pystrano.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pystrano = pystrano.deploy:main
3
+ pystrano-setup = pystrano.deploy:setup
@@ -0,0 +1,4 @@
1
+ fabric<4.0,>=3.2.2
2
+ click<9.0,>=8.1.7
3
+ pyyaml<7.0,>=6.0.2
4
+ python-dotenv<2.0,>=1.0.1
@@ -0,0 +1 @@
1
+ pystrano
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,42 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="pystrano",
8
+ version="1.0.0",
9
+ description="A Python package for managing deploying Django applications (like Capistrano for Ruby)",
10
+ long_description=long_description,
11
+ long_description_content_type="text/markdown",
12
+ license="MIT",
13
+ classifiers=[
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ],
22
+ keywords=["pystrano"],
23
+ packages=find_packages(),
24
+ python_requires=">=3.10",
25
+ install_requires=[
26
+ "fabric>=3.2.2,<4.0",
27
+ "click>=8.1.7,<9.0",
28
+ "pyyaml>=6.0.2,<7.0",
29
+ "python-dotenv>=1.0.1,<2.0",
30
+ ],
31
+ entry_points={
32
+ "console_scripts": [
33
+ "pystrano=pystrano.deploy:main",
34
+ "pystrano-setup=pystrano.deploy:setup",
35
+ ],
36
+ },
37
+ url="https://github.com/lexpank/pystrano",
38
+ project_urls={
39
+ "Homepage": "https://github.com/lexpank/pystrano",
40
+ "Issues": "https://github.com/lexpank/pystrano/issues",
41
+ },
42
+ )