bard 2.0.0.beta → 2.0.0

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} +13 -6
  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
@@ -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
@@ -1,5 +1,9 @@
1
1
  require "spec_helper"
2
+ require "shellwords"
2
3
  require "bard/target"
4
+ require "bard/plugins/url/target_methods"
5
+ require "bard/plugins/ssh/target_methods"
6
+ require "bard/plugins/ping/target_methods"
3
7
 
4
8
  describe Bard::Target do
5
9
  let(:config) { double("config", project_name: "testapp") }
@@ -16,12 +20,9 @@ describe Bard::Target do
16
20
 
17
21
  it "initializes with no capabilities" do
18
22
  expect(target.has_capability?(:ssh)).to be false
19
- expect(target.has_capability?(:ping)).to be false
23
+ expect(target.has_capability?(:url)).to be false
20
24
  end
21
25
 
22
- it "initializes with no deploy strategy" do
23
- expect(target.deploy_strategy).to be_nil
24
- end
25
26
  end
26
27
 
27
28
  describe "#ssh" do
@@ -37,7 +38,11 @@ describe Bard::Target do
37
38
  end
38
39
 
39
40
  it "parses SSH URI" do
40
- expect(target.ssh_uri).to eq("deploy@example.com:22")
41
+ expect(target.ssh_uri).to be_a(URI::Generic)
42
+ expect(target.ssh_uri.scheme).to eq("ssh")
43
+ expect(target.ssh_uri.user).to eq("deploy")
44
+ expect(target.ssh_uri.host).to eq("example.com")
45
+ expect(target.ssh_uri.port).to eq(22)
41
46
  end
42
47
  end
43
48
 
@@ -71,50 +76,67 @@ describe Bard::Target do
71
76
  expect(target.env).to eq("RAILS_ENV=production")
72
77
  end
73
78
 
74
- it "auto-configures ping URL from hostname" do
75
- expect(target.ping_urls).to include("example.com")
79
+ it "auto-configures url from hostname" do
80
+ expect(target.url).to eq("https://example.com")
76
81
  end
77
82
  end
78
83
 
79
- context "with false value" do
80
- before { target.ssh(false) }
81
-
84
+ context "without ssh configured" do
82
85
  it "does not enable SSH capability" do
83
86
  expect(target.has_capability?(:ssh)).to be false
84
87
  end
85
88
 
86
- it "sets server to nil" do
87
- expect(target.server).to be_nil
89
+ it "returns nil from ssh getter" do
90
+ expect(target.ssh).to be_nil
88
91
  end
89
92
  end
90
93
  end
91
94
 
92
- describe "#ping" do
93
- it "enables ping capability with single URL" do
94
- target.ping("https://example.com")
95
- expect(target.has_capability?(:ping)).to be true
96
- expect(target.ping_urls).to include("https://example.com")
95
+ describe "#url" do
96
+ it "enables url capability" do
97
+ target.url("https://example.com")
98
+ expect(target.has_capability?(:url)).to be true
99
+ expect(target.url).to eq("https://example.com")
100
+ end
101
+
102
+ it "normalizes URLs without scheme" do
103
+ target.url("example.com")
104
+ expect(target.url).to eq("https://example.com")
97
105
  end
98
106
 
107
+ it "disables url with false" do
108
+ target.url("https://example.com")
109
+ target.url(false)
110
+ expect(target.has_capability?(:url)).to be false
111
+ expect(target.url).to be_nil
112
+ end
113
+ end
114
+
115
+ describe "#ping" do
99
116
  it "accepts multiple URLs" do
100
- target.ping("https://example.com", "/health", "/status")
101
- expect(target.ping_urls).to include("https://example.com")
102
- expect(target.ping_urls).to include("/health")
103
- expect(target.ping_urls).to include("/status")
117
+ target.ping("https://example.com", "https://example.com/health")
118
+ expect(target.ping).to eq(["https://example.com", "https://example.com/health"])
119
+ end
120
+
121
+ it "defaults to url when not explicitly set" do
122
+ target.url("https://example.com")
123
+ expect(target.ping).to eq(["https://example.com"])
124
+ end
125
+
126
+ it "returns empty array when no url or ping configured" do
127
+ expect(target.ping).to eq([])
104
128
  end
105
129
 
106
130
  it "disables ping with false" do
107
131
  target.ping("https://example.com")
108
132
  target.ping(false)
109
- expect(target.has_capability?(:ping)).to be false
110
- expect(target.ping_urls).to be_empty
133
+ expect(target.ping).to be_empty
111
134
  end
112
135
  end
113
136
 
114
137
  describe "#path" do
115
- it "stores and retrieves path" do
116
- target.path("/var/www/app")
117
- expect(target.path).to eq("/var/www/app")
138
+ it "defaults to project name" do
139
+ expect(target.path).to eq("testapp")
118
140
  end
119
141
 
120
142
  it "can be set via ssh options" do
@@ -123,108 +145,43 @@ describe Bard::Target do
123
145
  end
124
146
  end
125
147
 
126
- describe "remote command execution" do
127
- before do
128
- target.ssh("deploy@example.com:22", path: "/app")
148
+ describe "command execution" do
149
+ describe "local (base)" do
150
+ it "runs commands locally" do
151
+ expect(Bard::Command).to receive(:run!)
152
+ .with("ls", verbose: false, quiet: false)
153
+ target.run!("ls")
154
+ end
129
155
  end
130
156
 
131
- describe "#run!" do
132
- it "requires SSH capability" do
133
- target_without_ssh = described_class.new(:local, config)
134
- expect { target_without_ssh.run!("ls") }
135
- .to raise_error(/SSH not configured/)
157
+ describe "remote (SSH)" do
158
+ before do
159
+ target.ssh("deploy@example.com:22", path: "/app")
136
160
  end
137
161
 
138
- it "executes command on remote server" do
162
+ it "runs commands on remote server" do
163
+ expected_cmd = "ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR deploy@example.com #{Shellwords.shellescape("cd /app && ls")}"
139
164
  expect(Bard::Command).to receive(:run!)
140
- .with("ls", on: target.server, home: false, verbose: false, quiet: false)
165
+ .with(expected_cmd, verbose: false, quiet: false)
141
166
  target.run!("ls")
142
167
  end
143
- end
144
-
145
- describe "#run" do
146
- it "requires SSH capability" do
147
- target_without_ssh = described_class.new(:local, config)
148
- expect { target_without_ssh.run("ls") }
149
- .to raise_error(/SSH not configured/)
150
- end
151
168
 
152
- it "executes command on remote server without raising" do
169
+ it "runs commands without raising on remote server" do
170
+ expected_cmd = "ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR deploy@example.com #{Shellwords.shellescape("cd /app && ls")}"
153
171
  expect(Bard::Command).to receive(:run)
154
- .with("ls", on: target.server, home: false, verbose: false, quiet: false)
172
+ .with(expected_cmd, verbose: false, quiet: false)
155
173
  target.run("ls")
156
174
  end
157
- end
158
-
159
- describe "#exec!" do
160
- it "requires SSH capability" do
161
- target_without_ssh = described_class.new(:local, config)
162
- expect { target_without_ssh.exec!("ls") }
163
- .to raise_error(/SSH not configured/)
164
- end
165
175
 
166
176
  it "replaces process with remote command" do
177
+ expected_cmd = "ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR deploy@example.com #{Shellwords.shellescape("cd /app && ls")}"
167
178
  expect(Bard::Command).to receive(:exec!)
168
- .with("ls", on: target.server, home: false)
179
+ .with(expected_cmd)
169
180
  target.exec!("ls")
170
181
  end
171
182
  end
172
183
  end
173
184
 
174
- describe "file transfer" do
175
- let(:source_target) do
176
- t = described_class.new(:source, config)
177
- t.ssh("source@example.com:22", path: "/source")
178
- t
179
- end
180
-
181
- let(:dest_target) do
182
- t = described_class.new(:dest, config)
183
- t.ssh("dest@example.com:22", path: "/dest")
184
- t
185
- end
186
-
187
- describe "#copy_file" do
188
- it "requires SSH capability on source" do
189
- target_without_ssh = described_class.new(:local, config)
190
- expect { target_without_ssh.copy_file("test.txt", to: dest_target) }
191
- .to raise_error(/SSH not configured/)
192
- end
193
-
194
- it "requires SSH capability on destination" do
195
- target_without_ssh = described_class.new(:local, config)
196
- expect { source_target.copy_file("test.txt", to: target_without_ssh) }
197
- .to raise_error(/SSH not configured/)
198
- end
199
-
200
- it "copies file via SCP" do
201
- expect(Bard::Copy).to receive(:file)
202
- .with("test.txt", from: source_target, to: dest_target, verbose: false)
203
- source_target.copy_file("test.txt", to: dest_target)
204
- end
205
- end
206
-
207
- describe "#copy_dir" do
208
- it "requires SSH capability on source" do
209
- target_without_ssh = described_class.new(:local, config)
210
- expect { target_without_ssh.copy_dir("test/", to: dest_target) }
211
- .to raise_error(/SSH not configured/)
212
- end
213
-
214
- it "requires SSH capability on destination" do
215
- target_without_ssh = described_class.new(:local, config)
216
- expect { source_target.copy_dir("test/", to: target_without_ssh) }
217
- .to raise_error(/SSH not configured/)
218
- end
219
-
220
- it "syncs directory via rsync" do
221
- expect(Bard::Copy).to receive(:dir)
222
- .with("test/", from: source_target, to: dest_target, verbose: false)
223
- source_target.copy_dir("test/", to: dest_target)
224
- end
225
- end
226
- end
227
-
228
185
  describe "#to_s" do
229
186
  it "returns the target key as string" do
230
187
  expect(target.to_s).to eq("production")
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require "simplecov"
2
- SimpleCov.start
2
+ SimpleCov.start do
3
+ command_name "RSpec"
4
+ track_files "lib/**/*.rb"
5
+ add_filter "spec/"
6
+ add_filter "features/"
7
+ end
3
8
 
4
9
  require "webmock/rspec"
5
10