or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-line-interface.mdenvironment-configuration.mdexport-system.mdindex.mdoutput-formatting.mdprocess-management.md

export-system.mddocs/

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

```