bard 1.7.4 → 2.0.0.beta
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.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +957 -0
- data/CUSTOM_STRATEGIES.md +701 -0
- data/MIGRATION_GUIDE.md +498 -0
- data/README.md +489 -0
- data/lib/bard/cli/deploy.rb +12 -3
- data/lib/bard/command.rb +25 -9
- data/lib/bard/config.rb +118 -43
- data/lib/bard/copy.rb +57 -13
- data/lib/bard/default_config.rb +35 -0
- data/lib/bard/deploy_strategy/github_pages.rb +135 -0
- data/lib/bard/deploy_strategy/ssh.rb +19 -0
- data/lib/bard/deploy_strategy.rb +60 -0
- data/lib/bard/ssh_server.rb +100 -0
- data/lib/bard/target.rb +239 -0
- data/lib/bard/version.rb +1 -1
- data/spec/bard/capability_spec.rb +97 -0
- data/spec/bard/config_spec.rb +1 -1
- data/spec/bard/deploy_strategy/ssh_spec.rb +67 -0
- data/spec/bard/deploy_strategy_spec.rb +107 -0
- data/spec/bard/dynamic_dsl_spec.rb +126 -0
- data/spec/bard/ssh_server_spec.rb +169 -0
- data/spec/bard/target_spec.rb +239 -0
- metadata +24 -2
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
# Custom Deployment Strategies Guide
|
|
2
|
+
|
|
3
|
+
This guide shows you how to create custom deployment strategies for Bard v2.0.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Bard's deployment strategies are pluggable modules that define how code gets deployed. Built-in strategies include SSH and GitHub Pages, but you can create your own for any deployment target: Jets, Docker, Kubernetes, Heroku, AWS Lambda, etc.
|
|
8
|
+
|
|
9
|
+
## How Strategies Work
|
|
10
|
+
|
|
11
|
+
### Auto-Registration
|
|
12
|
+
|
|
13
|
+
Strategies automatically register themselves via Ruby's `inherited` hook. No manual registration needed!
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
module Bard
|
|
17
|
+
class DeployStrategy
|
|
18
|
+
class MyStrategy < DeployStrategy
|
|
19
|
+
# Automatically registered as :my_strategy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### DSL Integration
|
|
26
|
+
|
|
27
|
+
Once registered, your strategy becomes a DSL method:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# After defining DeployStrategy::Jets
|
|
31
|
+
target :production do
|
|
32
|
+
jets "https://api.example.com", run_tests: true
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Basic Strategy
|
|
37
|
+
|
|
38
|
+
### Minimal Example
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# lib/docker_deploy_strategy.rb
|
|
42
|
+
module Bard
|
|
43
|
+
class DeployStrategy
|
|
44
|
+
class Docker < DeployStrategy
|
|
45
|
+
def deploy
|
|
46
|
+
run! "docker build -t myapp ."
|
|
47
|
+
run! "docker push myapp:latest"
|
|
48
|
+
run! "docker stack deploy -c docker-compose.yml myapp"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use it:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# bard.rb
|
|
59
|
+
require_relative 'lib/docker_deploy_strategy'
|
|
60
|
+
|
|
61
|
+
target :production do
|
|
62
|
+
docker "https://app.example.com"
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### What You Get
|
|
67
|
+
|
|
68
|
+
Your strategy class provides:
|
|
69
|
+
|
|
70
|
+
- `target` - The target being deployed
|
|
71
|
+
- `run!(cmd)` - Run local command (raises on error)
|
|
72
|
+
- `run(cmd)` - Run local command (silent on error)
|
|
73
|
+
- `system!(cmd)` - Run command with live output
|
|
74
|
+
- Access to target's capabilities via `target.run!`, `target.ssh`, etc.
|
|
75
|
+
|
|
76
|
+
## Strategy with Options
|
|
77
|
+
|
|
78
|
+
### Passing Options
|
|
79
|
+
|
|
80
|
+
Options passed to the DSL method are available via `target.strategy_options(name)`:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
module Bard
|
|
84
|
+
class DeployStrategy
|
|
85
|
+
class Jets < DeployStrategy
|
|
86
|
+
def deploy
|
|
87
|
+
options = target.strategy_options(:jets)
|
|
88
|
+
|
|
89
|
+
run! "rake vips:build:#{target.key}" unless options[:skip_build]
|
|
90
|
+
run! "bundle exec rspec" if options[:run_tests]
|
|
91
|
+
run! "jets deploy #{options[:env] || target.key}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use it:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
target :production do
|
|
102
|
+
jets "https://api.example.com", run_tests: true, env: "prod"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
target :staging do
|
|
106
|
+
jets "https://staging-api.example.com", skip_build: true
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Strategy with Ping Configuration
|
|
111
|
+
|
|
112
|
+
### Auto-Configure Ping
|
|
113
|
+
|
|
114
|
+
Strategies can auto-configure ping URLs:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
module Bard
|
|
118
|
+
class DeployStrategy
|
|
119
|
+
class Jets < DeployStrategy
|
|
120
|
+
def initialize(target, url, **options)
|
|
121
|
+
super(target)
|
|
122
|
+
@url = url
|
|
123
|
+
@options = options
|
|
124
|
+
|
|
125
|
+
# Auto-configure ping
|
|
126
|
+
target.ping(url)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def deploy
|
|
130
|
+
run! "jets deploy #{@options[:env] || target.key}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Now `bard ping production` and `bard open production` work automatically.
|
|
138
|
+
|
|
139
|
+
## Real-World Examples
|
|
140
|
+
|
|
141
|
+
### Example 1: Jets (AWS Lambda)
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# lib/jets_deploy_strategy.rb
|
|
145
|
+
module Bard
|
|
146
|
+
class DeployStrategy
|
|
147
|
+
class Jets < DeployStrategy
|
|
148
|
+
def initialize(target, url, **options)
|
|
149
|
+
super(target)
|
|
150
|
+
@url = url
|
|
151
|
+
@options = options
|
|
152
|
+
target.ping(url)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def deploy
|
|
156
|
+
target_name = target.key.to_s
|
|
157
|
+
|
|
158
|
+
# Build static assets
|
|
159
|
+
run! "rake vips:build:#{target_name}" unless @options[:skip_build]
|
|
160
|
+
|
|
161
|
+
# Run tests
|
|
162
|
+
if should_run_tests?(target_name)
|
|
163
|
+
run! "bundle exec rspec"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Deploy to AWS
|
|
167
|
+
env = @options[:env] || target_name
|
|
168
|
+
run! "jets deploy #{env}"
|
|
169
|
+
|
|
170
|
+
# Smoke test
|
|
171
|
+
target.ping! if @options[:verify]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def should_run_tests?(target_name)
|
|
177
|
+
return @options[:run_tests] if @options.key?(:run_tests)
|
|
178
|
+
target_name == "production" # Default: test production only
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Usage:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# bard.rb
|
|
189
|
+
require_relative 'lib/jets_deploy_strategy'
|
|
190
|
+
|
|
191
|
+
target :staging do
|
|
192
|
+
jets "https://staging-api.example.com",
|
|
193
|
+
skip_build: true,
|
|
194
|
+
run_tests: false
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
target :production do
|
|
198
|
+
jets "https://api.example.com",
|
|
199
|
+
run_tests: true,
|
|
200
|
+
verify: true
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
backup false # Serverless doesn't need backups
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Example 2: Heroku
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
# lib/heroku_deploy_strategy.rb
|
|
210
|
+
module Bard
|
|
211
|
+
class DeployStrategy
|
|
212
|
+
class Heroku < DeployStrategy
|
|
213
|
+
def initialize(target, app_name, **options)
|
|
214
|
+
super(target)
|
|
215
|
+
@app_name = app_name
|
|
216
|
+
@options = options
|
|
217
|
+
|
|
218
|
+
# Auto-configure ping from Heroku app name
|
|
219
|
+
target.ping("https://#{app_name}.herokuapp.com")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def deploy
|
|
223
|
+
# Push to Heroku
|
|
224
|
+
remote = @options[:remote] || "heroku"
|
|
225
|
+
run! "git push #{remote} HEAD:master"
|
|
226
|
+
|
|
227
|
+
# Run migrations
|
|
228
|
+
run! "heroku run rake db:migrate -a #{@app_name}" if @options[:migrate]
|
|
229
|
+
|
|
230
|
+
# Restart
|
|
231
|
+
run! "heroku restart -a #{@app_name}"
|
|
232
|
+
|
|
233
|
+
# Smoke test
|
|
234
|
+
target.ping!
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Usage:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
# bard.rb
|
|
245
|
+
require_relative 'lib/heroku_deploy_strategy'
|
|
246
|
+
|
|
247
|
+
target :staging do
|
|
248
|
+
heroku "myapp-staging", migrate: true
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
target :production do
|
|
252
|
+
heroku "myapp-production", migrate: true, remote: "production"
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Example 3: Kubernetes
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# lib/kubernetes_deploy_strategy.rb
|
|
260
|
+
module Bard
|
|
261
|
+
class DeployStrategy
|
|
262
|
+
class Kubernetes < DeployStrategy
|
|
263
|
+
def initialize(target, url, **options)
|
|
264
|
+
super(target)
|
|
265
|
+
@url = url
|
|
266
|
+
@options = options
|
|
267
|
+
@namespace = options[:namespace] || target.key.to_s
|
|
268
|
+
@context = options[:context]
|
|
269
|
+
|
|
270
|
+
target.ping(url)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def deploy
|
|
274
|
+
# Build and push Docker image
|
|
275
|
+
tag = git_sha
|
|
276
|
+
run! "docker build -t #{image_name}:#{tag} ."
|
|
277
|
+
run! "docker push #{image_name}:#{tag}"
|
|
278
|
+
|
|
279
|
+
# Update Kubernetes deployment
|
|
280
|
+
kubectl "set image deployment/#{app_name} #{app_name}=#{image_name}:#{tag}"
|
|
281
|
+
|
|
282
|
+
# Wait for rollout
|
|
283
|
+
kubectl "rollout status deployment/#{app_name}"
|
|
284
|
+
|
|
285
|
+
# Smoke test
|
|
286
|
+
target.ping!
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private
|
|
290
|
+
|
|
291
|
+
def kubectl(cmd)
|
|
292
|
+
context_flag = @context ? "--context #{@context}" : ""
|
|
293
|
+
run! "kubectl #{context_flag} -n #{@namespace} #{cmd}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def image_name
|
|
297
|
+
@options[:image] || "myregistry/#{app_name}"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def app_name
|
|
301
|
+
@options[:app] || target.key.to_s
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def git_sha
|
|
305
|
+
`git rev-parse --short HEAD`.strip
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Usage:
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
# bard.rb
|
|
316
|
+
require_relative 'lib/kubernetes_deploy_strategy'
|
|
317
|
+
|
|
318
|
+
target :staging do
|
|
319
|
+
kubernetes "https://staging.example.com",
|
|
320
|
+
namespace: "staging",
|
|
321
|
+
context: "minikube",
|
|
322
|
+
image: "myregistry/myapp"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
target :production do
|
|
326
|
+
kubernetes "https://example.com",
|
|
327
|
+
namespace: "production",
|
|
328
|
+
context: "production-cluster",
|
|
329
|
+
image: "myregistry/myapp"
|
|
330
|
+
end
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Example 4: Docker Compose
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
# lib/docker_compose_deploy_strategy.rb
|
|
337
|
+
module Bard
|
|
338
|
+
class DeployStrategy
|
|
339
|
+
class DockerCompose < DeployStrategy
|
|
340
|
+
def initialize(target, **options)
|
|
341
|
+
super(target)
|
|
342
|
+
@options = options
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def deploy
|
|
346
|
+
# Requires SSH capability for remote deployment
|
|
347
|
+
target.require_capability!(:ssh)
|
|
348
|
+
|
|
349
|
+
# Push code to server
|
|
350
|
+
run! "git push origin #{branch}"
|
|
351
|
+
|
|
352
|
+
# Pull and restart on remote server
|
|
353
|
+
target.run! "cd #{target.path} && git pull origin #{branch}"
|
|
354
|
+
target.run! "cd #{target.path} && docker-compose pull"
|
|
355
|
+
target.run! "cd #{target.path} && docker-compose up -d"
|
|
356
|
+
|
|
357
|
+
# Clean up old images
|
|
358
|
+
target.run! "docker image prune -f" if @options[:prune]
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private
|
|
362
|
+
|
|
363
|
+
def branch
|
|
364
|
+
@options[:branch] || "master"
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Usage:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
# bard.rb
|
|
375
|
+
require_relative 'lib/docker_compose_deploy_strategy'
|
|
376
|
+
|
|
377
|
+
target :production do
|
|
378
|
+
ssh "deploy@example.com:22", path: "app"
|
|
379
|
+
docker_compose branch: "main", prune: true
|
|
380
|
+
ping "https://example.com"
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Advanced Patterns
|
|
385
|
+
|
|
386
|
+
### Strategy with CI Integration
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
module Bard
|
|
390
|
+
class DeployStrategy
|
|
391
|
+
class CustomDeploy < DeployStrategy
|
|
392
|
+
def deploy
|
|
393
|
+
# Run CI if not disabled
|
|
394
|
+
unless @options[:skip_ci]
|
|
395
|
+
run! "bard ci"
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Deploy
|
|
399
|
+
run! "my-deploy-command"
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Strategy with Backup Support
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
module Bard
|
|
410
|
+
class DeployStrategy
|
|
411
|
+
class CustomDeploy < DeployStrategy
|
|
412
|
+
def deploy
|
|
413
|
+
# Create backup if enabled
|
|
414
|
+
if target.config.backup_enabled?
|
|
415
|
+
target.run! "bin/rake db:backup"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Deploy
|
|
419
|
+
run! "my-deploy-command"
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Strategy with Rollback
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
module Bard
|
|
430
|
+
class DeployStrategy
|
|
431
|
+
class CustomDeploy < DeployStrategy
|
|
432
|
+
def deploy
|
|
433
|
+
begin
|
|
434
|
+
run! "my-deploy-command"
|
|
435
|
+
target.ping!
|
|
436
|
+
rescue => e
|
|
437
|
+
puts "Deployment failed, rolling back..."
|
|
438
|
+
run! "my-rollback-command"
|
|
439
|
+
raise
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Multi-Stage Strategy
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
module Bard
|
|
451
|
+
class DeployStrategy
|
|
452
|
+
class CustomDeploy < DeployStrategy
|
|
453
|
+
def deploy
|
|
454
|
+
stages.each do |stage|
|
|
455
|
+
puts "Running stage: #{stage}"
|
|
456
|
+
send(stage)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
private
|
|
461
|
+
|
|
462
|
+
def stages
|
|
463
|
+
@options[:stages] || [:build, :test, :deploy, :verify]
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def build
|
|
467
|
+
run! "rake assets:precompile"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def test
|
|
471
|
+
run! "rspec"
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def deploy
|
|
475
|
+
run! "my-deploy-command"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def verify
|
|
479
|
+
target.ping!
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Capability Requirements
|
|
487
|
+
|
|
488
|
+
### Requiring Capabilities
|
|
489
|
+
|
|
490
|
+
If your strategy requires certain capabilities, check for them:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
module Bard
|
|
494
|
+
class DeployStrategy
|
|
495
|
+
class CustomDeploy < DeployStrategy
|
|
496
|
+
def deploy
|
|
497
|
+
# Fail fast if SSH not configured
|
|
498
|
+
target.require_capability!(:ssh)
|
|
499
|
+
|
|
500
|
+
# Now safe to use SSH
|
|
501
|
+
target.run! "my-command"
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Optional Capabilities
|
|
509
|
+
|
|
510
|
+
```ruby
|
|
511
|
+
module Bard
|
|
512
|
+
class DeployStrategy
|
|
513
|
+
class CustomDeploy < DeployStrategy
|
|
514
|
+
def deploy
|
|
515
|
+
run! "my-deploy-command"
|
|
516
|
+
|
|
517
|
+
# Use SSH for debugging if available
|
|
518
|
+
if target.has_capability?(:ssh)
|
|
519
|
+
target.run! "tail -n 50 logs/production.log"
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Testing Strategies
|
|
528
|
+
|
|
529
|
+
### Unit Testing
|
|
530
|
+
|
|
531
|
+
```ruby
|
|
532
|
+
# spec/jets_deploy_strategy_spec.rb
|
|
533
|
+
require 'spec_helper'
|
|
534
|
+
require_relative '../lib/jets_deploy_strategy'
|
|
535
|
+
|
|
536
|
+
RSpec.describe Bard::DeployStrategy::Jets do
|
|
537
|
+
let(:target) { double('target', key: :production) }
|
|
538
|
+
let(:strategy) { described_class.new(target, "https://api.example.com", run_tests: true) }
|
|
539
|
+
|
|
540
|
+
describe '#deploy' do
|
|
541
|
+
it 'runs tests for production' do
|
|
542
|
+
expect(strategy).to receive(:run!).with("bundle exec rspec")
|
|
543
|
+
expect(strategy).to receive(:run!).with(/jets deploy/)
|
|
544
|
+
strategy.deploy
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Integration Testing
|
|
551
|
+
|
|
552
|
+
```ruby
|
|
553
|
+
# spec/integration/jets_deployment_spec.rb
|
|
554
|
+
require 'spec_helper'
|
|
555
|
+
|
|
556
|
+
RSpec.describe 'Jets deployment' do
|
|
557
|
+
it 'deploys to staging' do
|
|
558
|
+
# Load bard.rb
|
|
559
|
+
config = Bard::Config.new
|
|
560
|
+
|
|
561
|
+
# Get strategy
|
|
562
|
+
target = config[:staging]
|
|
563
|
+
strategy = target.deploy_strategy
|
|
564
|
+
|
|
565
|
+
# Verify strategy type
|
|
566
|
+
expect(strategy).to be_a(Bard::DeployStrategy::Jets)
|
|
567
|
+
|
|
568
|
+
# Test deployment (in dry-run mode)
|
|
569
|
+
expect { strategy.deploy }.not_to raise_error
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Distribution
|
|
575
|
+
|
|
576
|
+
### In Your Project
|
|
577
|
+
|
|
578
|
+
Place custom strategies in `lib/` and require them in `bard.rb`:
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
# lib/my_deploy_strategy.rb
|
|
582
|
+
module Bard
|
|
583
|
+
class DeployStrategy
|
|
584
|
+
class MyDeploy < DeployStrategy
|
|
585
|
+
# ...
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# bard.rb
|
|
591
|
+
require_relative 'lib/my_deploy_strategy'
|
|
592
|
+
|
|
593
|
+
target :production do
|
|
594
|
+
my_deploy "https://example.com"
|
|
595
|
+
end
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### As a Gem
|
|
599
|
+
|
|
600
|
+
Package your strategy as a gem for reuse across projects:
|
|
601
|
+
|
|
602
|
+
```ruby
|
|
603
|
+
# my_bard_strategy.gemspec
|
|
604
|
+
Gem::Specification.new do |spec|
|
|
605
|
+
spec.name = "bard-jets"
|
|
606
|
+
spec.version = "1.0.0"
|
|
607
|
+
spec.authors = ["Your Name"]
|
|
608
|
+
spec.summary = "Jets deployment strategy for Bard"
|
|
609
|
+
|
|
610
|
+
spec.files = Dir["lib/**/*"]
|
|
611
|
+
|
|
612
|
+
spec.add_dependency "bard", "~> 2.0"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# lib/bard/jets.rb
|
|
616
|
+
require 'bard'
|
|
617
|
+
|
|
618
|
+
module Bard
|
|
619
|
+
class DeployStrategy
|
|
620
|
+
class Jets < DeployStrategy
|
|
621
|
+
# ...
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Use it:
|
|
628
|
+
|
|
629
|
+
```ruby
|
|
630
|
+
# Gemfile
|
|
631
|
+
gem 'bard-jets'
|
|
632
|
+
|
|
633
|
+
# bard.rb
|
|
634
|
+
require 'bard/jets'
|
|
635
|
+
|
|
636
|
+
target :production do
|
|
637
|
+
jets "https://api.example.com"
|
|
638
|
+
end
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Best Practices
|
|
642
|
+
|
|
643
|
+
1. **Auto-configure ping URLs** - Makes `bard ping` and `bard open` work
|
|
644
|
+
2. **Fail fast** - Use `require_capability!` to check dependencies early
|
|
645
|
+
3. **Provide good defaults** - Make simple cases simple
|
|
646
|
+
4. **Accept options** - Allow customization via hash options
|
|
647
|
+
5. **Test thoroughly** - Unit test logic, integration test with real targets
|
|
648
|
+
6. **Document well** - Include usage examples in README
|
|
649
|
+
7. **Handle errors** - Provide clear error messages
|
|
650
|
+
8. **Support dry-run** - Add `--dry-run` option support if possible
|
|
651
|
+
|
|
652
|
+
## Debugging
|
|
653
|
+
|
|
654
|
+
### Enable Verbose Output
|
|
655
|
+
|
|
656
|
+
```ruby
|
|
657
|
+
def deploy
|
|
658
|
+
puts "Deploying to #{target.key}..."
|
|
659
|
+
puts "Options: #{@options.inspect}"
|
|
660
|
+
|
|
661
|
+
run! "my-command"
|
|
662
|
+
end
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Dry-Run Mode
|
|
666
|
+
|
|
667
|
+
```ruby
|
|
668
|
+
def deploy
|
|
669
|
+
if ENV['DRY_RUN']
|
|
670
|
+
puts "Would run: my-deploy-command"
|
|
671
|
+
else
|
|
672
|
+
run! "my-deploy-command"
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
Use it:
|
|
678
|
+
```bash
|
|
679
|
+
DRY_RUN=1 bard deploy production
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
## Getting Help
|
|
683
|
+
|
|
684
|
+
- Review [ARCHITECTURE.md](ARCHITECTURE.md) for v2 architecture details
|
|
685
|
+
- Review [README.md](README.md) for API documentation
|
|
686
|
+
- Check built-in strategies in `lib/bard/deploy_strategy/`
|
|
687
|
+
- Open an issue at https://github.com/botandrose/bard/issues
|
|
688
|
+
|
|
689
|
+
## Examples Repository
|
|
690
|
+
|
|
691
|
+
Find more examples at:
|
|
692
|
+
https://github.com/botandrose/bard-strategies
|
|
693
|
+
|
|
694
|
+
Includes:
|
|
695
|
+
- AWS Lambda (Jets)
|
|
696
|
+
- Heroku
|
|
697
|
+
- Docker Compose
|
|
698
|
+
- Kubernetes
|
|
699
|
+
- Google Cloud Run
|
|
700
|
+
- Azure App Service
|
|
701
|
+
- And more!
|