0
# Export System
1
2
Pluggable architecture for exporting Procfile-based applications to various process management systems including systemd, supervisord, upstart, and runit. The export system transforms Procfile configurations into native system service definitions.
3
4
## Capabilities
5
6
### Base Export Framework
7
8
The BaseExport class provides the foundation for all export plugins with template management and rendering capabilities.
9
10
```python { .api }
11
class BaseExport:
12
"""
13
Base class for all export plugins. Provides template management
14
and rendering infrastructure using Jinja2 templates.
15
"""
16
17
def __init__(self, template_dir=None, template_env=None):
18
"""
19
Initialize export plugin with template configuration.
20
21
Parameters:
22
- template_dir: str, optional custom template directory path
23
- template_env: jinja2.Environment, optional pre-configured template environment
24
"""
25
26
def get_template(self, path):
27
"""
28
Retrieve template at the specified path.
29
30
Parameters:
31
- path: str, template path relative to template directory
32
33
Returns:
34
jinja2.Template: loaded template object
35
"""
36
37
def get_template_loader(self):
38
"""
39
Get the template loader for this export plugin.
40
Must be implemented by subclasses.
41
42
Returns:
43
jinja2.BaseLoader: template loader instance
44
45
Raises:
46
NotImplementedError: if not implemented by subclass
47
"""
48
49
def render(self, processes, context):
50
"""
51
Render processes to export format files.
52
Must be implemented by subclasses.
53
54
Parameters:
55
- processes: list, expanded process definitions
56
- context: dict, export context with app info and configuration
57
58
Returns:
59
Generator[File]: generator yielding File objects
60
61
Raises:
62
NotImplementedError: if not implemented by subclass
63
"""
64
```
65
66
### File Representation
67
68
Data structure representing exported files with metadata.
69
70
```python { .api }
71
class File:
72
"""
73
Represents an exported file with name, content, and execution permissions.
74
"""
75
76
def __init__(self, name, content, executable=False):
77
"""
78
Initialize file representation.
79
80
Parameters:
81
- name: str, file name or path
82
- content: str, file content
83
- executable: bool, whether file should be executable (default: False)
84
"""
85
86
# Properties
87
name: str
88
content: str
89
executable: bool
90
```
91
92
### Systemd Exporter
93
94
Export plugin for systemd service files and targets.
95
96
```python { .api }
97
class SystemdExport(BaseExport):
98
"""
99
Exporter for systemd service files and targets.
100
Generates .service files for individual processes and .target files for grouping.
101
"""
102
103
def get_template_loader(self):
104
"""Get systemd template loader."""
105
106
def render(self, processes, context):
107
"""
108
Render systemd service files and targets.
109
110
Parameters:
111
- processes: list, expanded process definitions
112
- context: dict, export context with app, user, log directory, etc.
113
114
Returns:
115
Generator[File]: yields .service and .target files
116
"""
117
```
118
119
### Supervisord Exporter
120
121
Export plugin for supervisord configuration files.
122
123
```python { .api }
124
class SupervisordExport(BaseExport):
125
"""
126
Exporter for supervisord configuration files.
127
Generates a single .conf file with all process definitions.
128
"""
129
130
def get_template_loader(self):
131
"""Get supervisord template loader."""
132
133
def render(self, processes, context):
134
"""
135
Render supervisord configuration file.
136
137
Parameters:
138
- processes: list, expanded process definitions
139
- context: dict, export context
140
141
Returns:
142
Generator[File]: yields single .conf file
143
"""
144
```
145
146
### Upstart Exporter
147
148
Export plugin for Ubuntu Upstart job definitions.
149
150
```python { .api }
151
class UpstartExport(BaseExport):
152
"""
153
Exporter for Ubuntu Upstart job definitions.
154
Generates .conf files for individual processes and master job.
155
"""
156
157
def get_template_loader(self):
158
"""Get upstart template loader."""
159
160
def render(self, processes, context):
161
"""
162
Render upstart job configuration files.
163
164
Parameters:
165
- processes: list, expanded process definitions
166
- context: dict, export context
167
168
Returns:
169
Generator[File]: yields .conf files for jobs
170
"""
171
```
172
173
### Runit Exporter
174
175
Export plugin for runit service directories.
176
177
```python { .api }
178
class RunitExport(BaseExport):
179
"""
180
Exporter for runit service directories.
181
Generates run scripts and service directory structure.
182
"""
183
184
def get_template_loader(self):
185
"""Get runit template loader."""
186
187
def render(self, processes, context):
188
"""
189
Render runit service directories and scripts.
190
191
Parameters:
192
- processes: list, expanded process definitions
193
- context: dict, export context
194
195
Returns:
196
Generator[File]: yields run scripts and configuration files
197
"""
198
```
199
200
### Template Utilities
201
202
Utility functions for template processing and string formatting.
203
204
```python { .api }
205
def dashrepl(value):
206
"""
207
Replace any non-word characters with dashes.
208
Used for generating safe file and service names.
209
210
Parameters:
211
- value: str, input string
212
213
Returns:
214
str: string with non-word characters replaced by dashes
215
"""
216
217
def percentescape(value):
218
"""
219
Double any percent signs for systemd compatibility.
220
221
Parameters:
222
- value: str, input string
223
224
Returns:
225
str: string with percent signs escaped
226
"""
227
```
228
229
## Usage Examples
230
231
### Basic Export Usage
232
233
```python
234
from honcho.export.systemd import Export as SystemdExport
235
from honcho.environ import expand_processes, parse_procfile
236
237
# Parse Procfile and expand processes
238
procfile_content = """
239
web: python app.py
240
worker: python worker.py
241
"""
242
procfile = parse_procfile(procfile_content)
243
244
# Expand with concurrency and environment
245
processes = expand_processes(
246
procfile.processes,
247
concurrency={'web': 2, 'worker': 1},
248
env={'DATABASE_URL': 'postgresql://localhost/myapp'},
249
port=5000
250
)
251
252
# Create export context
253
context = {
254
'app': 'myapp',
255
'app_root': '/srv/myapp',
256
'log': '/var/log/myapp',
257
'shell': '/bin/bash',
258
'user': 'myapp'
259
}
260
261
# Create exporter and render files
262
exporter = SystemdExport()
263
files = list(exporter.render(processes, context))
264
265
# Write files to filesystem
266
import os
267
for file in files:
268
file_path = os.path.join('/etc/systemd/system', file.name)
269
with open(file_path, 'w') as f:
270
f.write(file.content)
271
272
if file.executable:
273
os.chmod(file_path, 0o755)
274
275
print(f"Wrote {file_path}")
276
```
277
278
### Custom Template Directory
279
280
```python
281
from honcho.export.systemd import Export as SystemdExport
282
283
# Use custom template directory
284
exporter = SystemdExport(template_dir='/path/to/custom/templates')
285
286
# Or provide custom Jinja2 environment
287
import jinja2
288
template_env = jinja2.Environment(
289
loader=jinja2.FileSystemLoader('/custom/templates'),
290
trim_blocks=True,
291
lstrip_blocks=True
292
)
293
exporter = SystemdExport(template_env=template_env)
294
```
295
296
### Multiple Export Formats
297
298
```python
299
from honcho.export.systemd import Export as SystemdExport
300
from honcho.export.supervisord import Export as SupervisordExport
301
from honcho.export.upstart import Export as UpstartExport
302
303
# Export to multiple formats
304
exporters = {
305
'systemd': SystemdExport(),
306
'supervisord': SupervisordExport(),
307
'upstart': UpstartExport()
308
}
309
310
for format_name, exporter in exporters.items():
311
output_dir = f'/tmp/export-{format_name}'
312
os.makedirs(output_dir, exist_ok=True)
313
314
files = list(exporter.render(processes, context))
315
316
for file in files:
317
file_path = os.path.join(output_dir, file.name)
318
with open(file_path, 'w') as f:
319
f.write(file.content)
320
321
if file.executable:
322
os.chmod(file_path, 0o755)
323
324
print(f"Exported {len(files)} files to {output_dir}")
325
```
326
327
### Plugin Discovery
328
329
The export system uses entry points for plugin discovery:
330
331
```python
332
import sys
333
if sys.version_info < (3, 10):
334
from backports.entry_points_selectable import entry_points
335
else:
336
from importlib.metadata import entry_points
337
338
# Discover available export plugins
339
export_choices = dict(
340
(_export.name, _export) for _export in entry_points(group="honcho_exporters")
341
)
342
343
print("Available exporters:")
344
for name in export_choices:
345
print(f" {name}")
346
347
# Load and use specific exporter
348
systemd_export_class = export_choices['systemd'].load()
349
exporter = systemd_export_class()
350
```
351
352
### Context Configuration
353
354
```python
355
# Typical export context structure
356
context = {
357
'app': 'myapp', # Application name
358
'app_root': '/srv/myapp', # Application root directory
359
'log': '/var/log/myapp', # Log directory
360
'shell': '/bin/bash', # Shell to use for commands
361
'user': 'myapp', # User to run processes as
362
'template_dir': None, # Custom template directory
363
}
364
365
# Context is passed to templates and can include custom variables
366
context.update({
367
'environment': 'production',
368
'restart_policy': 'always',
369
'memory_limit': '512M'
370
})
371
```
372
373
### Custom Export Plugin
374
375
```python
376
import jinja2
377
from honcho.export.base import BaseExport, File
378
379
class CustomExport(BaseExport):
380
"""Custom export plugin example."""
381
382
def get_template_loader(self):
383
# Use package templates or custom directory
384
return jinja2.PackageLoader('mypackage.templates', 'custom')
385
386
def render(self, processes, context):
387
template = self.get_template('service.conf')
388
389
# Generate one file per process
390
for process in processes:
391
process_context = context.copy()
392
process_context['process'] = process
393
394
content = template.render(process_context)
395
filename = f"{process.name}.conf"
396
397
yield File(filename, content, executable=False)
398
399
# Register plugin via entry points in setup.py or pyproject.toml
400
# [project.entry-points.honcho_exporters]
401
# custom = "mypackage.export:CustomExport"
402
```
403
404
## Template System
405
406
The export system uses Jinja2 templates with custom filters:
407
408
```python { .api }
409
# Built-in template filters provided by BaseExport
410
env.filters['dashrepl'] = dashrepl # Replace non-word chars with dashes
411
env.filters['percentescape'] = percentescape # Escape percent signs
412
413
# Additional filters can be added by exporters:
414
# env.filters['shellquote'] = shlex.quote # Shell-safe quoting (if needed)
415
```
416
417
Example template usage:
418
419
```jinja2
420
[Unit]
421
Description={{ app }} ({{ process.name }})
422
After=network.target
423
424
[Service]
425
Type=simple
426
User={{ user }}
427
WorkingDirectory={{ app_root }}
428
Environment={{ process.env }}
429
ExecStart={{ process.cmd }}
430
Restart=always
431
RestartSec=10
432
433
[Install]
434
WantedBy=multi-user.target
435
```