bard 2.0.0.beta → 2.0.1

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.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -1
  3. data/CLAUDE.md +76 -0
  4. data/MIGRATION_GUIDE.md +24 -9
  5. data/PLUGINS.md +99 -0
  6. data/README.md +14 -6
  7. data/Rakefile +3 -1
  8. data/bard.gemspec +2 -1
  9. data/cucumber.yml +1 -0
  10. data/features/ci.feature +63 -0
  11. data/features/data.feature +13 -0
  12. data/features/deploy.feature +14 -0
  13. data/features/deploy_git_workflow.feature +89 -0
  14. data/features/run.feature +14 -0
  15. data/features/step_definitions/bard_steps.rb +136 -0
  16. data/features/support/bard-coverage +16 -0
  17. data/features/support/env.rb +14 -39
  18. data/features/support/test_server.rb +216 -0
  19. data/lib/bard/cli.rb +14 -31
  20. data/lib/bard/command.rb +10 -69
  21. data/lib/bard/config.rb +40 -183
  22. data/lib/bard/copy.rb +28 -103
  23. data/lib/bard/plugins/data.rb +56 -0
  24. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
  25. data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
  26. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
  27. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
  28. data/lib/bard/plugins/deploy/ci.rb +38 -0
  29. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  30. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
  31. data/lib/bard/plugins/deploy.rb +240 -0
  32. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  33. data/lib/bard/{github.rb → plugins/github.rb} +4 -6
  34. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +41 -13
  35. data/lib/bard/plugins/github_pages.rb +30 -0
  36. data/lib/bard/plugins/hurt.rb +13 -0
  37. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  38. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  39. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  40. data/lib/bard/plugins/install.rb +9 -0
  41. data/lib/bard/plugins/open.rb +20 -0
  42. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  43. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  44. data/lib/bard/plugins/ping.rb +10 -0
  45. data/lib/bard/plugins/run.rb +19 -0
  46. data/lib/bard/plugins/setup.rb +54 -0
  47. data/lib/bard/plugins/ssh/connection.rb +75 -0
  48. data/lib/bard/plugins/ssh/copy.rb +95 -0
  49. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
  50. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  51. data/lib/bard/plugins/ssh.rb +10 -0
  52. data/lib/bard/plugins/url/target_methods.rb +23 -0
  53. data/lib/bard/plugins/url.rb +1 -0
  54. data/lib/bard/plugins/vim.rb +6 -0
  55. data/lib/bard/retryable.rb +25 -0
  56. data/lib/bard/secrets.rb +10 -0
  57. data/lib/bard/target.rb +27 -185
  58. data/lib/bard/version.rb +1 -1
  59. data/lib/bard.rb +1 -3
  60. data/spec/acceptance/docker/Dockerfile +3 -2
  61. data/spec/bard/capability_spec.rb +8 -50
  62. data/spec/bard/ci/github_actions_spec.rb +117 -14
  63. data/spec/bard/ci/jenkins_spec.rb +139 -0
  64. data/spec/bard/ci/runner_spec.rb +61 -0
  65. data/spec/bard/ci_spec.rb +1 -1
  66. data/spec/bard/cli/ci_spec.rb +34 -27
  67. data/spec/bard/cli/data_spec.rb +7 -26
  68. data/spec/bard/cli/deploy_spec.rb +87 -46
  69. data/spec/bard/cli/hurt_spec.rb +3 -9
  70. data/spec/bard/cli/install_spec.rb +5 -11
  71. data/spec/bard/cli/master_key_spec.rb +5 -19
  72. data/spec/bard/cli/open_spec.rb +14 -30
  73. data/spec/bard/cli/ping_spec.rb +8 -23
  74. data/spec/bard/cli/run_spec.rb +27 -21
  75. data/spec/bard/cli/setup_spec.rb +10 -27
  76. data/spec/bard/cli/ssh_spec.rb +10 -25
  77. data/spec/bard/cli/stage_spec.rb +28 -23
  78. data/spec/bard/cli/vim_spec.rb +3 -9
  79. data/spec/bard/command_spec.rb +1 -8
  80. data/spec/bard/config_spec.rb +78 -98
  81. data/spec/bard/copy_spec.rb +54 -18
  82. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  83. data/spec/bard/deploy_strategy_spec.rb +1 -1
  84. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  85. data/spec/bard/git_spec.rb +9 -5
  86. data/spec/bard/github_spec.rb +2 -2
  87. data/spec/bard/ping_spec.rb +5 -5
  88. data/spec/bard/ssh_copy_spec.rb +44 -0
  89. data/spec/bard/ssh_server_spec.rb +8 -101
  90. data/spec/bard/target_spec.rb +66 -109
  91. data/spec/spec_helper.rb +6 -1
  92. metadata +79 -143
  93. data/README.rdoc +0 -15
  94. data/features/bard_check.feature +0 -94
  95. data/features/bard_deploy.feature +0 -18
  96. data/features/bard_pull.feature +0 -112
  97. data/features/bard_push.feature +0 -112
  98. data/features/podman_testcontainers.feature +0 -16
  99. data/features/step_definitions/check_steps.rb +0 -47
  100. data/features/step_definitions/git_steps.rb +0 -73
  101. data/features/step_definitions/global_steps.rb +0 -56
  102. data/features/step_definitions/podman_steps.rb +0 -23
  103. data/features/step_definitions/rails_steps.rb +0 -44
  104. data/features/step_definitions/submodule_steps.rb +0 -110
  105. data/features/support/grit_ext.rb +0 -13
  106. data/features/support/io.rb +0 -32
  107. data/features/support/podman.rb +0 -153
  108. data/lib/bard/ci/jenkins.rb +0 -105
  109. data/lib/bard/ci/retryable.rb +0 -27
  110. data/lib/bard/ci.rb +0 -50
  111. data/lib/bard/cli/ci.rb +0 -66
  112. data/lib/bard/cli/command.rb +0 -26
  113. data/lib/bard/cli/data.rb +0 -45
  114. data/lib/bard/cli/deploy.rb +0 -85
  115. data/lib/bard/cli/hurt.rb +0 -20
  116. data/lib/bard/cli/install.rb +0 -16
  117. data/lib/bard/cli/master_key.rb +0 -17
  118. data/lib/bard/cli/new.rb +0 -101
  119. data/lib/bard/cli/new_rails_template.rb +0 -197
  120. data/lib/bard/cli/open.rb +0 -22
  121. data/lib/bard/cli/ping.rb +0 -18
  122. data/lib/bard/cli/provision.rb +0 -34
  123. data/lib/bard/cli/run.rb +0 -24
  124. data/lib/bard/cli/setup.rb +0 -56
  125. data/lib/bard/cli/ssh.rb +0 -14
  126. data/lib/bard/cli/stage.rb +0 -27
  127. data/lib/bard/cli/vim.rb +0 -13
  128. data/lib/bard/default_config.rb +0 -35
  129. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  130. data/lib/bard/github_pages.rb +0 -134
  131. data/lib/bard/provision/app.rb +0 -10
  132. data/lib/bard/provision/apt.rb +0 -16
  133. data/lib/bard/provision/authorizedkeys.rb +0 -25
  134. data/lib/bard/provision/data.rb +0 -27
  135. data/lib/bard/provision/deploy.rb +0 -10
  136. data/lib/bard/provision/http.rb +0 -16
  137. data/lib/bard/provision/logrotation.rb +0 -30
  138. data/lib/bard/provision/masterkey.rb +0 -18
  139. data/lib/bard/provision/mysql.rb +0 -22
  140. data/lib/bard/provision/passenger.rb +0 -37
  141. data/lib/bard/provision/repo.rb +0 -72
  142. data/lib/bard/provision/rvm.rb +0 -22
  143. data/lib/bard/provision/ssh.rb +0 -72
  144. data/lib/bard/provision/swapfile.rb +0 -21
  145. data/lib/bard/provision/user.rb +0 -42
  146. data/lib/bard/provision.rb +0 -16
  147. data/lib/bard/server.rb +0 -117
  148. data/spec/bard/cli/command_spec.rb +0 -50
  149. data/spec/bard/cli/new_spec.rb +0 -73
  150. data/spec/bard/cli/provision_spec.rb +0 -42
  151. data/spec/bard/github_pages_spec.rb +0 -143
  152. data/spec/bard/provision/app_spec.rb +0 -33
  153. data/spec/bard/provision/apt_spec.rb +0 -39
  154. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  155. data/spec/bard/provision/data_spec.rb +0 -54
  156. data/spec/bard/provision/deploy_spec.rb +0 -33
  157. data/spec/bard/provision/http_spec.rb +0 -57
  158. data/spec/bard/provision/logrotation_spec.rb +0 -34
  159. data/spec/bard/provision/masterkey_spec.rb +0 -63
  160. data/spec/bard/provision/mysql_spec.rb +0 -55
  161. data/spec/bard/provision/passenger_spec.rb +0 -81
  162. data/spec/bard/provision/repo_spec.rb +0 -208
  163. data/spec/bard/provision/rvm_spec.rb +0 -49
  164. data/spec/bard/provision/ssh_spec.rb +0 -229
  165. data/spec/bard/provision/swapfile_spec.rb +0 -32
  166. data/spec/bard/provision/user_spec.rb +0 -103
  167. data/spec/bard/provision_spec.rb +0 -28
  168. data/spec/bard/server_spec.rb +0 -127
  169. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  170. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  171. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  172. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  173. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  174. /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
@@ -2,32 +2,68 @@ require "spec_helper"
2
2
  require "bard/copy"
3
3
 
4
4
  describe Bard::Copy do
5
- let(:production) { double("production", key: :production, scp_uri: "user@example.com:/path/to/file", rsync_uri: "user@example.com:/path/to/", gateway: nil, ssh_key: nil, path: "/path/to") }
6
- let(:local) { double("local", key: :local) }
5
+ let(:local) { double("local", key: :local, has_capability?: false) }
6
+ let(:production) { double("production", key: :production, has_capability?: true) }
7
7
 
8
- context ".file" do
9
- it "should copy a file from a remote server to the local machine" do
10
- expect(Bard::Command).to receive(:run!).with("scp user@example.com:/path/to/file path/to/file", verbose: false)
11
- Bard::Copy.file "path/to/file", from: production, to: local
8
+ around do |example|
9
+ original_handlers = Bard::Copy.instance_variable_get(:@handlers).dup
10
+ example.run
11
+ Bard::Copy.instance_variable_set(:@handlers, original_handlers)
12
+ end
13
+
14
+ describe "auto-registration" do
15
+ it "registers handlers via inherited hook" do
16
+ handler = Class.new(Bard::Copy) do
17
+ def self.can_handle?(from, to) = true
18
+ def file = "copied"
19
+ end
20
+
21
+ copy = handler.new("path", local, production, false)
22
+ expect(copy.file).to eq("copied")
12
23
  end
24
+ end
25
+
26
+ describe ".file" do
27
+ it "dispatches to the handler that can handle the pair" do
28
+ Class.new(Bard::Copy) do
29
+ def self.can_handle?(from, to) = true
30
+ def file = "file_copied"
31
+ end
13
32
 
14
- it "should copy a file from the local machine to a remote server" do
15
- expect(Bard::Command).to receive(:run!).with("scp path/to/file user@example.com:/path/to/file", verbose: false)
16
- Bard::Copy.file "path/to/file", from: local, to: production
33
+ expect(Bard::Copy.file("db/data.sql.gz", from: local, to: production)).to eq("file_copied")
17
34
  end
18
35
  end
19
36
 
20
- context ".dir" do
21
- it "should copy a directory from a remote server to the local machine" do
22
- allow(production).to receive_message_chain("ssh_uri.port").and_return(22)
23
- expect(Bard::Command).to receive(:run!).with("rsync -e'ssh -p22' --delete --info=progress2 -az user@example.com:/path/to/ ./path/", verbose: false)
24
- Bard::Copy.dir "path/to", from: production, to: local
37
+ describe ".dir" do
38
+ it "dispatches to the handler that can handle the pair" do
39
+ Class.new(Bard::Copy) do
40
+ def self.can_handle?(from, to) = true
41
+ def dir = "dir_copied"
42
+ end
43
+
44
+ expect(Bard::Copy.dir("storage/", from: local, to: production)).to eq("dir_copied")
25
45
  end
46
+ end
47
+
48
+ describe ".handler_for!" do
49
+ it "raises when no handler matches" do
50
+ expect {
51
+ Bard::Copy.file("file", from: local, to: local)
52
+ }.to raise_error(/No copy handler for local -> local/)
53
+ end
54
+ end
55
+
56
+ describe "#initialize" do
57
+ it "stores path, from, to, verbose" do
58
+ handler = Class.new(Bard::Copy) do
59
+ def self.can_handle?(from, to) = true
60
+ end
26
61
 
27
- it "should copy a directory from the local machine to a remote server" do
28
- allow(production).to receive_message_chain("ssh_uri.port").and_return(22)
29
- expect(Bard::Command).to receive(:run!).with("rsync -e'ssh -p22' --delete --info=progress2 -az ./path/to user@example.com:/path/to/", verbose: false)
30
- Bard::Copy.dir "path/to", from: local, to: production
62
+ copy = handler.new("db/data.sql.gz", local, production, true)
63
+ expect(copy.path).to eq("db/data.sql.gz")
64
+ expect(copy.from).to eq(local)
65
+ expect(copy.to).to eq(production)
66
+ expect(copy.verbose).to eq(true)
31
67
  end
32
68
  end
33
69
  end
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
- require "bard/deploy_strategy"
3
- require "bard/deploy_strategy/ssh"
2
+ require "bard/plugins/deploy/strategy"
3
+ require "bard/plugins/deploy/ssh_strategy"
4
4
 
5
5
  describe Bard::DeployStrategy::SSH do
6
6
  let(:config) { double("config", project_name: "testapp") }
@@ -17,12 +17,12 @@ describe Bard::DeployStrategy::SSH do
17
17
  strategy_without_ssh = described_class.new(target_without_ssh)
18
18
 
19
19
  expect { strategy_without_ssh.deploy }
20
- .to raise_error(/SSH not configured/)
20
+ .to raise_error(/ssh capability not configured/)
21
21
  end
22
22
 
23
23
  it "runs git pull on remote server" do
24
24
  expect(target).to receive(:run!)
25
- .with(/git pull origin master/)
25
+ .with("git pull --ff-only origin master")
26
26
 
27
27
  allow(target).to receive(:run!).with(/bin\/setup/)
28
28
 
@@ -42,12 +42,69 @@ describe Bard::DeployStrategy::SSH do
42
42
  target.instance_variable_set(:@branch, "main")
43
43
 
44
44
  expect(target).to receive(:run!)
45
- .with(/git pull origin main/)
45
+ .with("git pull --ff-only origin main")
46
46
 
47
47
  allow(target).to receive(:run!).with(/bin\/setup/)
48
48
 
49
49
  strategy.deploy
50
50
  end
51
+
52
+ context "with force: true" do
53
+ it "force-checks-out the given branch on the remote server" do
54
+ expect(target).to receive(:run!).with("git fetch origin feature-x").ordered
55
+ expect(target).to receive(:run!).with("git checkout -f origin/feature-x").ordered
56
+
57
+ allow(target).to receive(:run!).with(/bin\/setup/)
58
+
59
+ strategy.deploy(branch: "feature-x", force: true)
60
+ end
61
+ end
62
+
63
+ context "with clone" do
64
+ let(:local_target) { double("local") }
65
+
66
+ before do
67
+ allow(config).to receive(:[]).with(:local).and_return(local_target)
68
+ allow(Bard::Copy).to receive(:file)
69
+ end
70
+
71
+ it "clones the repository" do
72
+ expect(target).to receive(:run!)
73
+ .with("git clone git@github.com:botandrosedesign/testapp /app", home: true)
74
+ allow(target).to receive(:run!).with("bin/setup")
75
+ allow(target).to receive(:run!).with("bard setup")
76
+
77
+ strategy.deploy(clone: "testapp")
78
+ end
79
+
80
+ it "copies master key from local" do
81
+ allow(target).to receive(:run!).with(/git clone/, home: true)
82
+ expect(Bard::Copy).to receive(:file)
83
+ .with("config/master.key", from: local_target, to: target)
84
+ allow(target).to receive(:run!).with("bin/setup")
85
+ allow(target).to receive(:run!).with("bard setup")
86
+
87
+ strategy.deploy(clone: "testapp")
88
+ end
89
+
90
+ it "runs bin/setup and bard setup" do
91
+ allow(target).to receive(:run!).with(/git clone/, home: true)
92
+ expect(target).to receive(:run!).with("bin/setup")
93
+ expect(target).to receive(:run!).with("bard setup")
94
+
95
+ strategy.deploy(clone: "testapp")
96
+ end
97
+
98
+ it "does not run git pull" do
99
+ allow(target).to receive(:run!).with(/git clone/, home: true)
100
+ allow(target).to receive(:run!).with("bin/setup")
101
+ allow(target).to receive(:run!).with("bard setup")
102
+
103
+ expect(target).not_to receive(:run!).with(/git pull/)
104
+
105
+ strategy.deploy(clone: "testapp")
106
+ end
107
+ end
51
108
  end
52
109
 
53
110
  describe "auto-registration" do
@@ -57,11 +114,12 @@ describe Bard::DeployStrategy::SSH do
57
114
  end
58
115
 
59
116
  describe "integration with target" do
60
- it "is enabled by ssh DSL method" do
117
+ it "is the default strategy when SSH is configured" do
118
+ require "bard/plugins/deploy"
61
119
  new_target = Bard::Target.new(:staging, config)
62
120
  new_target.ssh("deploy@staging.example.com:22")
63
121
 
64
- expect(new_target.deploy_strategy).to eq(:ssh)
122
+ expect(new_target.deploy_strategy_instance).to be_a(described_class)
65
123
  end
66
124
  end
67
125
  end
@@ -1,5 +1,5 @@
1
1
  require "spec_helper"
2
- require "bard/deploy_strategy"
2
+ require "bard/plugins/deploy/strategy"
3
3
 
4
4
  describe Bard::DeployStrategy do
5
5
  describe "auto-registration" do
@@ -1,118 +1,31 @@
1
1
  require "spec_helper"
2
2
  require "bard/target"
3
- require "bard/deploy_strategy"
3
+ require "bard/plugins/deploy"
4
4
 
5
- describe "Dynamic DSL Methods" do
5
+ describe "Deploy strategy target methods" do
6
6
  let(:config) { double("config", project_name: "testapp") }
7
7
  let(:target) { Bard::Target.new(:production, config) }
8
8
 
9
- before do
10
- # Register test strategies
11
- class Bard::DeployStrategy::Jets < Bard::DeployStrategy
12
- def deploy
13
- # test implementation
14
- end
15
- end
16
-
17
- class Bard::DeployStrategy::Docker < Bard::DeployStrategy
18
- def deploy
19
- # test implementation
20
- end
21
- end
22
- end
23
-
24
- describe "method_missing for strategies" do
25
- it "enables strategy when method name matches registered strategy" do
26
- target.jets("https://api.example.com")
27
- expect(target.deploy_strategy).to eq(:jets)
28
- end
29
-
30
- it "stores strategy options" do
31
- target.jets("https://api.example.com", run_tests: true, env: "production")
32
- options = target.strategy_options(:jets)
33
- expect(options[:run_tests]).to be true
34
- expect(options[:env]).to eq("production")
35
- end
36
-
37
- it "auto-configures ping URL from first argument if it's a URL" do
38
- target.jets("https://api.example.com")
39
- expect(target.ping_urls).to include("https://api.example.com")
40
- end
41
-
42
- it "works with multiple strategies" do
43
- target1 = Bard::Target.new(:production, config)
44
- target2 = Bard::Target.new(:staging, config)
45
-
46
- target1.jets("https://api.example.com")
47
- target2.docker("https://app.example.com")
48
-
49
- expect(target1.deploy_strategy).to eq(:jets)
50
- expect(target2.deploy_strategy).to eq(:docker)
51
- end
52
-
53
- it "raises NoMethodError for unknown methods" do
54
- expect { target.unknown_method("arg") }
55
- .to raise_error(NoMethodError)
56
- end
57
- end
58
-
59
- describe "strategy DSL integration" do
60
- it "allows chaining with other configuration methods" do
61
- target.jets("https://api.example.com", run_tests: true)
62
- target.ssh("deploy@example.com:22", path: "app")
63
-
64
- expect(target.deploy_strategy).to eq(:jets)
65
- expect(target.has_capability?(:ssh)).to be true
66
- end
67
-
68
- it "allows strategy configuration without ping URL" do
69
- target.docker(skip_build: true)
70
- options = target.strategy_options(:docker)
71
- expect(options[:skip_build]).to be true
72
- end
73
- end
74
-
75
- describe "#strategy_options" do
76
- it "returns options for the specified strategy" do
77
- target.jets("https://api.example.com", run_tests: true, env: "prod")
78
- options = target.strategy_options(:jets)
79
- expect(options[:run_tests]).to be true
80
- expect(options[:env]).to eq("prod")
81
- end
82
-
83
- it "returns empty hash if strategy not configured" do
84
- options = target.strategy_options(:unknown)
85
- expect(options).to eq({})
86
- end
87
-
88
- it "filters out URL from options" do
89
- target.jets("https://api.example.com", run_tests: true)
90
- options = target.strategy_options(:jets)
91
- expect(options[:run_tests]).to be true
92
- expect(options).not_to have_key(:url)
93
- end
94
- end
95
-
96
9
  describe "#deploy_strategy" do
97
- it "returns the configured strategy symbol" do
98
- target.jets("https://api.example.com")
99
- expect(target.deploy_strategy).to eq(:jets)
100
- end
101
-
102
10
  it "returns nil if no strategy configured" do
103
11
  expect(target.deploy_strategy).to be_nil
104
12
  end
13
+
14
+ it "returns :ssh after ssh is configured via deploy_strategy_instance" do
15
+ target.ssh("deploy@example.com:22")
16
+ expect(target.deploy_strategy_instance).to be_a(Bard::DeployStrategy::SSH)
17
+ end
105
18
  end
106
19
 
107
20
  describe "#deploy_strategy_instance" do
108
- it "creates an instance of the strategy class" do
109
- target.jets("https://api.example.com")
21
+ it "defaults to SSH strategy when SSH capability is present" do
22
+ target.ssh("deploy@example.com:22")
110
23
  instance = target.deploy_strategy_instance
111
- expect(instance).to be_a(Bard::DeployStrategy::Jets)
24
+ expect(instance).to be_a(Bard::DeployStrategy::SSH)
112
25
  expect(instance.target).to eq(target)
113
26
  end
114
27
 
115
- it "raises error if no strategy configured" do
28
+ it "raises error if no strategy configured and no SSH capability" do
116
29
  expect { target.deploy_strategy_instance }
117
30
  .to raise_error(/No deployment strategy configured/)
118
31
  end
@@ -123,4 +36,11 @@ describe "Dynamic DSL Methods" do
123
36
  .to raise_error(/Unknown deployment strategy: unknown/)
124
37
  end
125
38
  end
39
+
40
+ describe "#strategy_options" do
41
+ it "returns empty hash if strategy not configured" do
42
+ options = target.strategy_options(:unknown)
43
+ expect(options).to eq({})
44
+ end
45
+ end
126
46
  end
@@ -1,5 +1,5 @@
1
1
  require "spec_helper"
2
- require "bard/git"
2
+ require "bard/plugins/git"
3
3
 
4
4
  describe Bard::Git do
5
5
  describe ".current_branch" do
@@ -46,14 +46,18 @@ describe Bard::Git do
46
46
 
47
47
  describe ".sha_of" do
48
48
  it "should return the sha of a ref" do
49
- allow(Bard::Git).to receive(:`).with("git rev-parse ref 2>/dev/null").and_return("sha\n")
50
- allow(Bard::Git).to receive(:command_succeeded?).and_return(true)
49
+ allow(Bard::Git).to receive(:`).with("git rev-parse ref 2>/dev/null") {
50
+ `true` # sets $? to success
51
+ "sha\n"
52
+ }
51
53
  expect(Bard::Git.sha_of("ref")).to eq("sha")
52
54
  end
53
55
 
54
56
  it "should return nil if the ref does not exist" do
55
- allow(Bard::Git).to receive(:`).with("git rev-parse ref 2>/dev/null").and_return("ref: fatal: ambiguous argument 'ref': unknown revision or path not in the working tree.\n")
56
- allow(Bard::Git).to receive(:command_succeeded?).and_return(false)
57
+ allow(Bard::Git).to receive(:`).with("git rev-parse ref 2>/dev/null") {
58
+ `false` # sets $? to failure
59
+ "ref: fatal: ambiguous argument 'ref': unknown revision or path not in the working tree.\n"
60
+ }
57
61
  expect(Bard::Git.sha_of("ref")).to be_nil
58
62
  end
59
63
  end
@@ -1,11 +1,11 @@
1
1
  require "spec_helper"
2
- require "bard/github"
2
+ require "bard/plugins/github"
3
3
 
4
4
  describe Bard::Github do
5
5
  let(:github) { Bard::Github.new("test-project") }
6
6
 
7
7
  before do
8
- allow(github).to receive(:`).with("git ls-remote -t git@github.com:botandrosedesign/secrets").and_return("github-apikey|12345")
8
+ allow(Bard::Secrets).to receive(:fetch).with("github-apikey").and_return("12345")
9
9
  end
10
10
 
11
11
  describe "#get" do
@@ -1,9 +1,9 @@
1
1
  require "spec_helper"
2
- require "bard/ping"
2
+ require "bard/plugins/ping/check"
3
3
 
4
4
  describe Bard::Ping do
5
- let(:server) { double("server", ping: ["http://example.com"]) }
6
- let(:ping) { described_class.new(server) }
5
+ let(:target) { double("target", ping: ["http://example.com"]) }
6
+ let(:ping) { described_class.new(target) }
7
7
 
8
8
  def success_response
9
9
  Net::HTTPSuccess.new(1.0, "200", "OK")
@@ -13,14 +13,14 @@ describe Bard::Ping do
13
13
  Net::HTTPNotFound.new(1.0, "404", "Not Found")
14
14
  end
15
15
 
16
- context "when the server is reachable" do
16
+ context "when the target is reachable" do
17
17
  it "returns an empty array" do
18
18
  allow(ping).to receive(:http_get).and_return(success_response)
19
19
  expect(ping.call).to be_empty
20
20
  end
21
21
  end
22
22
 
23
- context "when the server is not reachable" do
23
+ context "when the target is not reachable" do
24
24
  it "returns the url" do
25
25
  allow(ping).to receive(:http_get).and_return(not_found_response)
26
26
  expect(ping.call).to eq(["http://example.com"])
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+ require "bard/plugins/ssh/copy"
3
+
4
+ describe Bard::SSH::Copy do
5
+ let(:ssh_server) { double("ssh_server", gateway: nil, ssh_key: nil, port: "22", ssh_uri: double(port: 22, user: "user", host: "example.com")) }
6
+ let(:production) { double("production", key: :production, server: ssh_server, scp_uri: "user@example.com:/path/to/file", rsync_uri: "user@example.com:/path/to/", path: "/path/to", has_capability?: true) }
7
+ let(:local) { double("local", key: :local, has_capability?: false) }
8
+
9
+ describe ".can_handle?" do
10
+ it "handles pairs where at least one target has ssh capability" do
11
+ expect(Bard::SSH::Copy.can_handle?(local, production)).to be true
12
+ expect(Bard::SSH::Copy.can_handle?(production, local)).to be true
13
+ expect(Bard::SSH::Copy.can_handle?(production, production)).to be true
14
+ end
15
+
16
+ it "does not handle pairs where neither target has ssh capability" do
17
+ expect(Bard::SSH::Copy.can_handle?(local, local)).to be false
18
+ end
19
+ end
20
+
21
+ context ".file via Bard::Copy dispatch" do
22
+ it "should copy a file from a remote server to the local machine" do
23
+ expect(Bard::Command).to receive(:run!).with("scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR user@example.com:/path/to/file path/to/file", verbose: false)
24
+ Bard::Copy.file "path/to/file", from: production, to: local
25
+ end
26
+
27
+ it "should copy a file from the local machine to a remote server" do
28
+ expect(Bard::Command).to receive(:run!).with("scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR path/to/file user@example.com:/path/to/file", verbose: false)
29
+ Bard::Copy.file "path/to/file", from: local, to: production
30
+ end
31
+ end
32
+
33
+ context ".dir via Bard::Copy dispatch" do
34
+ it "should copy a directory from a remote server to the local machine" do
35
+ expect(Bard::Command).to receive(:run!).with("rsync -e'ssh -p22' --delete --info=progress2 -az user@example.com:/path/to/ ./path/", verbose: false)
36
+ Bard::Copy.dir "path/to", from: production, to: local
37
+ end
38
+
39
+ it "should copy a directory from the local machine to a remote server" do
40
+ expect(Bard::Command).to receive(:run!).with("rsync -e'ssh -p22' --delete --info=progress2 -az ./path/to user@example.com:/path/to/", verbose: false)
41
+ Bard::Copy.dir "path/to", from: local, to: production
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  require "spec_helper"
2
- require "bard/ssh_server"
2
+ require "bard/plugins/ssh/server"
3
3
 
4
4
  describe Bard::SSHServer do
5
5
  describe "#initialize" do
@@ -40,14 +40,18 @@ describe Bard::SSHServer do
40
40
  end
41
41
 
42
42
  describe "#ssh_uri" do
43
- it "returns the SSH connection string" do
43
+ it "returns a URI object" do
44
44
  server = described_class.new("deploy@example.com:22")
45
- expect(server.ssh_uri).to eq("deploy@example.com:22")
45
+ expect(server.ssh_uri).to be_a(URI::Generic)
46
+ expect(server.ssh_uri.scheme).to eq("ssh")
47
+ expect(server.ssh_uri.user).to eq("deploy")
48
+ expect(server.ssh_uri.host).to eq("example.com")
49
+ expect(server.ssh_uri.port).to eq(22)
46
50
  end
47
51
 
48
52
  it "includes port if non-standard" do
49
53
  server = described_class.new("deploy@example.com:2222")
50
- expect(server.ssh_uri).to eq("deploy@example.com:2222")
54
+ expect(server.ssh_uri.port).to eq(2222)
51
55
  end
52
56
  end
53
57
 
@@ -69,101 +73,4 @@ describe Bard::SSHServer do
69
73
  expect(server.connection_string).to eq("deploy@example.com")
70
74
  end
71
75
  end
72
-
73
- describe "#run" do
74
- let(:server) do
75
- described_class.new("deploy@example.com:22", path: "/app")
76
- end
77
-
78
- it "executes command via SSH" do
79
- expect(Open3).to receive(:capture3)
80
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
81
- .and_return(["output", "", 0])
82
-
83
- server.run("ls")
84
- end
85
-
86
- it "includes environment variables if configured" do
87
- server_with_env = described_class.new("deploy@example.com:22",
88
- path: "/app",
89
- env: "RAILS_ENV=production"
90
- )
91
-
92
- expect(Open3).to receive(:capture3)
93
- .with(/RAILS_ENV=production/)
94
- .and_return(["output", "", 0])
95
-
96
- server_with_env.run("ls")
97
- end
98
- end
99
-
100
- describe "#run!" do
101
- let(:server) do
102
- described_class.new("deploy@example.com:22", path: "/app")
103
- end
104
-
105
- it "executes command via SSH" do
106
- expect(Open3).to receive(:capture3)
107
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
108
- .and_return(["output", "", 0])
109
-
110
- server.run!("ls")
111
- end
112
-
113
- it "raises error if command fails" do
114
- expect(Open3).to receive(:capture3)
115
- .and_return(["", "error", 1])
116
-
117
- expect { server.run!("false") }.to raise_error(Bard::Command::Error)
118
- end
119
- end
120
-
121
- describe "#exec!" do
122
- let(:server) do
123
- described_class.new("deploy@example.com:22", path: "/app")
124
- end
125
-
126
- it "replaces current process with SSH command" do
127
- expect(server).to receive(:exec)
128
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
129
-
130
- server.exec!("ls")
131
- end
132
- end
133
-
134
- describe "path handling" do
135
- it "uses path in commands if configured" do
136
- server = described_class.new("deploy@example.com:22", path: "/var/www/app")
137
-
138
- expect(Open3).to receive(:capture3)
139
- .with(/cd \/var\/www\/app && ls/)
140
- .and_return(["output", "", 0])
141
-
142
- server.run("ls")
143
- end
144
-
145
- it "works without path" do
146
- server = described_class.new("deploy@example.com:22")
147
-
148
- expect(Open3).to receive(:capture3)
149
- .with(/ssh.*ls/)
150
- .and_return(["output", "", 0])
151
-
152
- server.run("ls")
153
- end
154
- end
155
-
156
- describe "gateway/bastion support" do
157
- it "uses ProxyJump for gateway" do
158
- server = described_class.new("deploy@private.example.com:22",
159
- gateway: "bastion@public.example.com:22"
160
- )
161
-
162
- expect(Open3).to receive(:capture3)
163
- .with(/-o ProxyJump=bastion@public.example.com:22/)
164
- .and_return(["output", "", 0])
165
-
166
- server.run("ls")
167
- end
168
- end
169
76
  end