-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfabfile.py
More file actions
346 lines (274 loc) · 10.3 KB
/
fabfile.py
File metadata and controls
346 lines (274 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#!/usr/bin/env python3
"""
Fabric file for deploying Python projects.
This file defines a set of tasks that can be used to deploy Python projects
using Fabric. It includes functionality for deploying individual projects,
"""
import os
from fabric2 import Connection, task
from invoke.collection import Collection
from invoke.context import Context
from fabricator.deploy import deploy_site
from fabricator.logger import get_logger
from fabricator.recipes import (
release_lock,
restart_services,
rollback_to_previous_release,
)
from fabricator.runners import DockerRunner
from fabricator.utils import load_sites, print_site_list
def get_connection(
fallback_connection: Connection | DockerRunner | Context,
config: dict | None = None
) -> Connection | DockerRunner | Context:
"""
Resolve the appropriate connection object for the site.
Selects the correct runner based on environment variables,
Docker settings, or host definitions. Priority order:
1. Environment variables: ``DEPLOYER_HOST``, ``DEPLOYER_USER``,
``DEPLOYER_PORT``
2. Docker runner (if ``runner: docker`` is set in config)
3. SSH host/port defined in config
4. Fallback connection
5. Default to local context
:param fallback_connection: Default local or remote runner.
:type fallback_connection: Union[Connection, DockerRunner, Context]
:param config: Optional site config loaded from YAML.
:type config: dict or None
:return: A connection object suitable for deployment.
:rtype: Union[Connection, DockerRunner, Context]
"""
# 1. If environment variable is set, override all
host = os.getenv("DEPLOYER_HOST")
user = os.getenv("DEPLOYER_USER")
port = os.getenv("DEPLOYER_PORT")
if host:
return Connection(
host=host,
user=user or None,
port=int(port) if port else 22
)
# 2. If Docker runner is configured
if config and config.get("runner") == "docker":
container = config["docker_container"]
docker_user = config.get("docker_user", "root")
return DockerRunner(
container_name=container,
inner_runner=Context(),
user=docker_user
)
# 3. If host is defined in config and not already remote
if (
config
and "host" in config
and getattr(fallback_connection, "host", "localhost") == "localhost"
):
return Connection(
host=config["host"],
port=config.get("port", 22)
)
# 4. Fallback to local context if no remote host is detected
if getattr(fallback_connection, "host", "localhost") == "localhost":
return Context()
# 5. Default fallback
return fallback_connection
@task(help={"site": "Name of the site to deploy"})
def deploy(c: Connection | DockerRunner | Context, site: str) -> None:
"""
Deploy a single site defined in the configuration file.
Loads site-specific configuration, resolves the proper connection
(local, Docker, or SSH), and runs the full deployment process.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
:param site: Name of the site as defined in ``sites.yml``.
:type site: str
"""
# Load all site definitions from the YAML config
sites = load_sites()
# Initialize logger for the given site
logger = get_logger(site)
# Abort if the requested site does not exist in config
if site not in sites:
logger.error(f"Site '{site}' not found in sites.yml")
return
# Load config and set the site name explicitly
config = sites[site]
config['name'] = site
# Resolve connection using environment vars if present
c = get_connection(c, config = config)
deploy_site(c, config)
@task
def deploy_all(c: Connection | DockerRunner | Context) -> None:
"""
Deploy all sites listed in the configuration file.
Iterates through all entries in ``sites.yml`` and runs the
deployment pipeline for each one sequentially.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
"""
# Load all site definitions
sites = load_sites()
# Deploy each site iteratively
for site, config in sites.items():
config['name'] = site
# Use remote connection if available from environment
c = get_connection(c, config = config)
logger = get_logger(site)
logger.info(f"Deploying site: {site}")
deploy_site(c, config)
@task
def list_sites(c: Connection | DockerRunner | Context) -> None:
"""
Display all configured sites and their repository URLs.
Loads site data from ``sites.yml`` and prints the site name
alongside its associated Git repository.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
"""
# Print table of available site names
print_site_list()
@task(help={"site": "Name of the site to rollback"})
def rollback(c: Connection | DockerRunner | Context, site: str) -> None:
"""
Rollback a site to the previous release.
Identifies the most recent backup release and reverts to it.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
:param site: Name of the site to rollback.
:type site: str
"""
# Initialize logger
logger = get_logger(site)
# Load site definitions
sites = load_sites()
# Exit if the site doesn't exist
if site not in sites:
logger.error(f"Site '{site}' not found in sites.yml.")
return
# Extract config and set site name
config = sites[site]
config['name'] = site
# Resolve connection using environment vars if present
c = get_connection(c, config = config)
# Log and execute rollback
logger.info(f"Rolling back site: {site}")
rollback_to_previous_release(c, config)
@task
def rollback_all(c: Connection | DockerRunner | Context) -> None:
"""
Rollback all sites to their previous releases.
Iterates through all configured sites and performs a rollback
to their last known good deployment.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
"""
# Load all site configs
sites = load_sites()
# Apply rollback to each one
for site, config in sites.items():
config['name'] = site
# Resolve connection using environment vars if present
c = get_connection(c, config = config)
logger = get_logger(site)
logger.info(f"Rolling back site: {site}")
rollback_to_previous_release(c, config)
@task(help={"site": "Name of the site to unlock"})
def unlock(c: Connection | DockerRunner | Context, site: str) -> None:
"""
Force-remove the deployment lock for a specific site.
Useful when a previous deployment was interrupted and the
lock wasn't released.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
:param site: Name of the site to unlock.
:type site: str
"""
# Initialize logger
logger = get_logger(site)
# Load available sites
sites = load_sites()
# Validate site presence
if site not in sites:
logger.error(f"Site '{site}' not found in sites.yml.")
return
# Set site name and perform unlock
config = sites[site]
config['name'] = site
# Resolve connection using environment vars if present
c = get_connection(c, config = config)
logger.info(f"Unlocking site: {site}")
release_lock(c, config, force=True)
@task
def unlock_all(c: Connection | DockerRunner | Context) -> None:
"""
Remove lock files for all sites defined in ``sites.yml``.
Useful when recovering from interrupted deploys or rollbacks.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
"""
# Load site configurations
sites = load_sites()
# Unlock each one using force
for site, config in sites.items():
config['name'] = site
# Resolve connection if needed
c = get_connection(c, config = config)
logger = get_logger(site)
logger.info(f"Unlocking site: {site}")
release_lock(c, config, force=True)
@task(help={"site": "Name of the site to restart"})
def restart_site(c: Connection | DockerRunner | Context, site: str) -> None:
"""
Restart a single site defined in the configuration file.
Loads site-specific configuration, resolves the proper connection
(local, Docker, or SSH), and runs the full deployment process.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
:param site: Name of the site as defined in ``sites.yml``.
:type site: str
"""
# Load all site definitions from the YAML config
sites = load_sites()
# Initialize logger for the given site
logger = get_logger(site)
# Abort if the requested site does not exist in config
if site not in sites:
logger.error(f"Site '{site}' not found in sites.yml")
return
# Load config and set the site name explicitly
config = sites[site]
config['name'] = site
# Resolve connection using environment vars if present
c = get_connection(c, config = config)
restart_services(c, config)
@task
def restart_all(c: Connection | DockerRunner | Context) -> None:
"""
Restart all sites listed in the configuration file.
Iterates through all entries in ``sites.yml`` and runs the
deployment pipeline for each one sequentially.
:param c: Fabric connection or context object.
:type c: Union[Connection, DockerRunner, Context]
"""
# Load all site definitions
sites = load_sites()
# Deploy each site iteratively
for site, config in sites.items():
config['name'] = site
# Use remote connection if available from environment
c = get_connection(c, config = config)
logger = get_logger(site)
logger.info(f"Restarting site: {site}")
restart_services(c, config)
# This line exposes the tasks to the Fabric CLI (`fab2 ...`)
ns = Collection(
deploy,
deploy_all,
list_sites,
rollback,
rollback_all,
unlock,
unlock_all,
restart_site,
restart_all
)