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.
@@ -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!