pystrano 1.0.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.
- pystrano/__init__.py +0 -0
- pystrano/config.py +98 -0
- pystrano/core.py +151 -0
- pystrano/deploy.py +136 -0
- pystrano-1.0.0.dist-info/LICENSE +21 -0
- pystrano-1.0.0.dist-info/METADATA +108 -0
- pystrano-1.0.0.dist-info/RECORD +10 -0
- pystrano-1.0.0.dist-info/WHEEL +5 -0
- pystrano-1.0.0.dist-info/entry_points.txt +3 -0
- pystrano-1.0.0.dist-info/top_level.txt +1 -0
pystrano/__init__.py
ADDED
|
File without changes
|
pystrano/config.py
ADDED
|
@@ -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"]]
|
pystrano/core.py
ADDED
|
@@ -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}')
|
pystrano/deploy.py
ADDED
|
@@ -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,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,10 @@
|
|
|
1
|
+
pystrano/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pystrano/config.py,sha256=Kh33h_c9f-5AO_TILCPTfF5SaekmkUWqeOB98Kfz39g,3735
|
|
3
|
+
pystrano/core.py,sha256=wZG008eethJ03yLoZ3jFTNyuBXqVBZRPP4R9k5PaUqA,6442
|
|
4
|
+
pystrano/deploy.py,sha256=GElWW1EZcqnZVClrWBdx0mIUH7O0FpV_AwS4UQZF8JA,4545
|
|
5
|
+
pystrano-1.0.0.dist-info/LICENSE,sha256=l6v4VVqOnAz93I_4Bb9HiAV7JXqtvvCTWPuGlLy2pE0,1064
|
|
6
|
+
pystrano-1.0.0.dist-info/METADATA,sha256=CfsYtxjWuf1FnBj7OoPDbeC1iX_sfe6QfYnp46cRVx8,4647
|
|
7
|
+
pystrano-1.0.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
8
|
+
pystrano-1.0.0.dist-info/entry_points.txt,sha256=5ajtKw_Tra0lLniOJPusScU-snuASzHShg17XVGLcnc,89
|
|
9
|
+
pystrano-1.0.0.dist-info/top_level.txt,sha256=ib6v34d04Rjf5KOe0anUVg0ZhAvbcCMV8hrYWKm5jiE,9
|
|
10
|
+
pystrano-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pystrano
|