bard 1.9.6 → 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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/CLAUDE.md +1 -1
  4. data/PLUGINS.md +31 -46
  5. data/bard.gemspec +2 -1
  6. data/features/ci.feature +1 -0
  7. data/features/data.feature +1 -0
  8. data/features/deploy.feature +1 -0
  9. data/features/deploy_git_workflow.feature +1 -0
  10. data/features/run.feature +1 -0
  11. data/features/step_definitions/bard_steps.rb +1 -0
  12. data/features/support/test_server.rb +3 -3
  13. data/lib/bard/cli.rb +9 -28
  14. data/lib/bard/command.rb +9 -88
  15. data/lib/bard/config.rb +38 -177
  16. data/lib/bard/copy.rb +28 -82
  17. data/lib/bard/plugins/data.rb +56 -0
  18. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +2 -2
  19. data/lib/bard/{ci → plugins/deploy/ci}/jenkins.rb +1 -1
  20. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +1 -1
  21. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +3 -3
  22. data/lib/bard/{ci.rb → plugins/deploy/ci.rb} +1 -1
  23. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  24. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -4
  25. data/lib/bard/plugins/deploy.rb +240 -0
  26. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  27. data/lib/bard/{github.rb → plugins/github.rb} +2 -2
  28. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +3 -4
  29. data/lib/bard/plugins/github_pages.rb +11 -15
  30. data/lib/bard/plugins/hurt.rb +12 -4
  31. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  32. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  33. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  34. data/lib/bard/plugins/install.rb +7 -3
  35. data/lib/bard/plugins/open.rb +20 -0
  36. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  37. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  38. data/lib/bard/plugins/ping.rb +8 -4
  39. data/lib/bard/plugins/run.rb +19 -0
  40. data/lib/bard/plugins/setup.rb +54 -0
  41. data/lib/bard/plugins/ssh/connection.rb +75 -0
  42. data/lib/bard/plugins/ssh/copy.rb +95 -0
  43. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +10 -42
  44. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  45. data/lib/bard/plugins/ssh.rb +10 -0
  46. data/lib/bard/plugins/url/target_methods.rb +23 -0
  47. data/lib/bard/plugins/url.rb +1 -0
  48. data/lib/bard/plugins/vim.rb +5 -4
  49. data/lib/bard/retryable.rb +25 -0
  50. data/lib/bard/target.rb +21 -230
  51. data/lib/bard/version.rb +1 -1
  52. data/lib/bard.rb +1 -3
  53. data/spec/acceptance/docker/Dockerfile +1 -1
  54. data/spec/bard/capability_spec.rb +8 -50
  55. data/spec/bard/ci/github_actions_spec.rb +1 -1
  56. data/spec/bard/ci/jenkins_spec.rb +1 -1
  57. data/spec/bard/ci/runner_spec.rb +3 -3
  58. data/spec/bard/ci_spec.rb +1 -1
  59. data/spec/bard/cli/ci_spec.rb +4 -23
  60. data/spec/bard/cli/data_spec.rb +7 -26
  61. data/spec/bard/cli/deploy_spec.rb +43 -40
  62. data/spec/bard/cli/hurt_spec.rb +2 -8
  63. data/spec/bard/cli/install_spec.rb +4 -10
  64. data/spec/bard/cli/master_key_spec.rb +5 -19
  65. data/spec/bard/cli/open_spec.rb +17 -35
  66. data/spec/bard/cli/ping_spec.rb +10 -25
  67. data/spec/bard/cli/run_spec.rb +10 -23
  68. data/spec/bard/cli/setup_spec.rb +10 -27
  69. data/spec/bard/cli/ssh_spec.rb +10 -25
  70. data/spec/bard/cli/stage_spec.rb +17 -32
  71. data/spec/bard/cli/vim_spec.rb +5 -11
  72. data/spec/bard/command_spec.rb +1 -10
  73. data/spec/bard/config_spec.rb +68 -116
  74. data/spec/bard/copy_spec.rb +54 -18
  75. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  76. data/spec/bard/deploy_strategy_spec.rb +1 -1
  77. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  78. data/spec/bard/git_spec.rb +9 -5
  79. data/spec/bard/github_spec.rb +1 -1
  80. data/spec/bard/ping_spec.rb +5 -5
  81. data/spec/bard/ssh_copy_spec.rb +44 -0
  82. data/spec/bard/ssh_server_spec.rb +1 -98
  83. data/spec/bard/target_spec.rb +61 -108
  84. metadata +50 -124
  85. data/lib/bard/ci/retryable.rb +0 -27
  86. data/lib/bard/cli/ci.rb +0 -73
  87. data/lib/bard/cli/command.rb +0 -26
  88. data/lib/bard/cli/data.rb +0 -45
  89. data/lib/bard/cli/deploy.rb +0 -116
  90. data/lib/bard/cli/hurt.rb +0 -15
  91. data/lib/bard/cli/install.rb +0 -11
  92. data/lib/bard/cli/master_key.rb +0 -17
  93. data/lib/bard/cli/new.rb +0 -101
  94. data/lib/bard/cli/new_rails_template.rb +0 -197
  95. data/lib/bard/cli/open.rb +0 -18
  96. data/lib/bard/cli/ping.rb +0 -12
  97. data/lib/bard/cli/provision.rb +0 -34
  98. data/lib/bard/cli/run.rb +0 -26
  99. data/lib/bard/cli/setup.rb +0 -56
  100. data/lib/bard/cli/ssh.rb +0 -14
  101. data/lib/bard/cli/stage.rb +0 -35
  102. data/lib/bard/cli/vim.rb +0 -8
  103. data/lib/bard/default_config.rb +0 -35
  104. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  105. data/lib/bard/deprecation.rb +0 -19
  106. data/lib/bard/github_pages.rb +0 -134
  107. data/lib/bard/plugin.rb +0 -100
  108. data/lib/bard/plugins/backup.rb +0 -19
  109. data/lib/bard/plugins/jenkins.rb +0 -6
  110. data/lib/bard/plugins/new.rb +0 -5
  111. data/lib/bard/plugins/provision.rb +0 -5
  112. data/lib/bard/provision/app.rb +0 -10
  113. data/lib/bard/provision/apt.rb +0 -16
  114. data/lib/bard/provision/authorizedkeys.rb +0 -25
  115. data/lib/bard/provision/data.rb +0 -27
  116. data/lib/bard/provision/deploy.rb +0 -10
  117. data/lib/bard/provision/http.rb +0 -16
  118. data/lib/bard/provision/logrotation.rb +0 -30
  119. data/lib/bard/provision/masterkey.rb +0 -18
  120. data/lib/bard/provision/mysql.rb +0 -22
  121. data/lib/bard/provision/passenger.rb +0 -37
  122. data/lib/bard/provision/repo.rb +0 -72
  123. data/lib/bard/provision/rvm.rb +0 -22
  124. data/lib/bard/provision/ssh.rb +0 -79
  125. data/lib/bard/provision/swapfile.rb +0 -23
  126. data/lib/bard/provision/user.rb +0 -42
  127. data/lib/bard/provision.rb +0 -16
  128. data/lib/bard/server.rb +0 -160
  129. data/spec/bard/cli/command_spec.rb +0 -50
  130. data/spec/bard/cli/new_spec.rb +0 -73
  131. data/spec/bard/cli/provision_spec.rb +0 -42
  132. data/spec/bard/deprecation_spec.rb +0 -281
  133. data/spec/bard/github_pages_spec.rb +0 -143
  134. data/spec/bard/plugin_spec.rb +0 -79
  135. data/spec/bard/provision/app_spec.rb +0 -33
  136. data/spec/bard/provision/apt_spec.rb +0 -39
  137. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  138. data/spec/bard/provision/data_spec.rb +0 -54
  139. data/spec/bard/provision/deploy_spec.rb +0 -33
  140. data/spec/bard/provision/http_spec.rb +0 -57
  141. data/spec/bard/provision/logrotation_spec.rb +0 -34
  142. data/spec/bard/provision/masterkey_spec.rb +0 -63
  143. data/spec/bard/provision/mysql_spec.rb +0 -55
  144. data/spec/bard/provision/passenger_spec.rb +0 -81
  145. data/spec/bard/provision/repo_spec.rb +0 -208
  146. data/spec/bard/provision/rvm_spec.rb +0 -49
  147. data/spec/bard/provision/ssh_spec.rb +0 -242
  148. data/spec/bard/provision/swapfile_spec.rb +0 -33
  149. data/spec/bard/provision/user_spec.rb +0 -103
  150. data/spec/bard/provision_spec.rb +0 -28
  151. data/spec/bard/server_spec.rb +0 -127
  152. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  153. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  154. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  155. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  156. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  157. /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,5 +1,5 @@
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") }
@@ -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
@@ -73,101 +73,4 @@ describe Bard::SSHServer do
73
73
  expect(server.connection_string).to eq("deploy@example.com")
74
74
  end
75
75
  end
76
-
77
- describe "#run" do
78
- let(:server) do
79
- described_class.new("deploy@example.com:22", path: "/app")
80
- end
81
-
82
- it "executes command via SSH" do
83
- expect(Open3).to receive(:capture3)
84
- .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
85
- .and_return(["output", "", 0])
86
-
87
- server.run("ls")
88
- end
89
-
90
- it "includes environment variables if configured" do
91
- server_with_env = described_class.new("deploy@example.com:22",
92
- path: "/app",
93
- env: "RAILS_ENV=production"
94
- )
95
-
96
- expect(Open3).to receive(:capture3)
97
- .with(/RAILS_ENV.*production/)
98
- .and_return(["output", "", 0])
99
-
100
- server_with_env.run("ls")
101
- end
102
- end
103
-
104
- describe "#run!" do
105
- let(:server) do
106
- described_class.new("deploy@example.com:22", path: "/app")
107
- end
108
-
109
- it "executes command via SSH" do
110
- expect(Open3).to receive(:capture3)
111
- .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
112
- .and_return(["output", "", 0])
113
-
114
- server.run!("ls")
115
- end
116
-
117
- it "raises error if command fails" do
118
- expect(Open3).to receive(:capture3)
119
- .and_return(["", "error", 1])
120
-
121
- expect { server.run!("false") }.to raise_error(Bard::Command::Error)
122
- end
123
- end
124
-
125
- describe "#exec!" do
126
- let(:server) do
127
- described_class.new("deploy@example.com:22", path: "/app")
128
- end
129
-
130
- it "replaces current process with SSH command" do
131
- expect(server).to receive(:exec)
132
- .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
133
-
134
- server.exec!("ls")
135
- end
136
- end
137
-
138
- describe "path handling" do
139
- it "uses path in commands if configured" do
140
- server = described_class.new("deploy@example.com:22", path: "/var/www/app")
141
-
142
- expect(Open3).to receive(:capture3)
143
- .with(/cd.+\/var\/www\/app.+ls/)
144
- .and_return(["output", "", 0])
145
-
146
- server.run("ls")
147
- end
148
-
149
- it "works without path" do
150
- server = described_class.new("deploy@example.com:22")
151
-
152
- expect(Open3).to receive(:capture3)
153
- .with(/ssh.*ls/)
154
- .and_return(["output", "", 0])
155
-
156
- server.run("ls")
157
- end
158
- end
159
-
160
- describe "gateway/bastion support" do
161
- it "uses ProxyJump for gateway" do
162
- server = described_class.new("deploy@private.example.com:22",
163
- gateway: "bastion@public.example.com:22"
164
- )
165
-
166
- expect(Open3).to receive(:capture3)
167
- .with(/-o ProxyJump=bastion@public.example.com:22/)
168
- .and_return(["output", "", 0])
169
-
170
- server.run("ls")
171
- end
172
- end
173
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
@@ -75,50 +76,67 @@ describe Bard::Target do
75
76
  expect(target.env).to eq("RAILS_ENV=production")
76
77
  end
77
78
 
78
- it "auto-configures ping URL from hostname" do
79
- expect(target.ping_urls).to include("https://example.com")
79
+ it "auto-configures url from hostname" do
80
+ expect(target.url).to eq("https://example.com")
80
81
  end
81
82
  end
82
83
 
83
- context "with false value" do
84
- before { target.ssh(false) }
85
-
84
+ context "without ssh configured" do
86
85
  it "does not enable SSH capability" do
87
86
  expect(target.has_capability?(:ssh)).to be false
88
87
  end
89
88
 
90
- it "sets server to nil" do
91
- expect(target.server).to be_nil
89
+ it "returns nil from ssh getter" do
90
+ expect(target.ssh).to be_nil
92
91
  end
93
92
  end
94
93
  end
95
94
 
96
- describe "#ping" do
97
- it "enables ping capability with single URL" do
98
- target.ping("https://example.com")
99
- expect(target.has_capability?(:ping)).to be true
100
- 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")
101
105
  end
102
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
103
116
  it "accepts multiple URLs" do
104
- target.ping("https://example.com", "/health", "/status")
105
- expect(target.ping_urls).to include("https://example.com")
106
- expect(target.ping_urls).to include("/health")
107
- 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([])
108
128
  end
109
129
 
110
130
  it "disables ping with false" do
111
131
  target.ping("https://example.com")
112
132
  target.ping(false)
113
- expect(target.has_capability?(:ping)).to be false
114
- expect(target.ping_urls).to be_empty
133
+ expect(target.ping).to be_empty
115
134
  end
116
135
  end
117
136
 
118
137
  describe "#path" do
119
- it "stores and retrieves path" do
120
- target.path("/var/www/app")
121
- expect(target.path).to eq("/var/www/app")
138
+ it "defaults to project name" do
139
+ expect(target.path).to eq("testapp")
122
140
  end
123
141
 
124
142
  it "can be set via ssh options" do
@@ -127,108 +145,43 @@ describe Bard::Target do
127
145
  end
128
146
  end
129
147
 
130
- describe "remote command execution" do
131
- before do
132
- 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
133
155
  end
134
156
 
135
- describe "#run!" do
136
- it "requires SSH capability" do
137
- target_without_ssh = described_class.new(:local, config)
138
- expect { target_without_ssh.run!("ls") }
139
- .to raise_error(/SSH not configured/)
157
+ describe "remote (SSH)" do
158
+ before do
159
+ target.ssh("deploy@example.com:22", path: "/app")
140
160
  end
141
161
 
142
- 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")}"
143
164
  expect(Bard::Command).to receive(:run!)
144
- .with("ls", on: target, home: false, verbose: false, quiet: false)
165
+ .with(expected_cmd, verbose: false, quiet: false)
145
166
  target.run!("ls")
146
167
  end
147
- end
148
-
149
- describe "#run" do
150
- it "requires SSH capability" do
151
- target_without_ssh = described_class.new(:local, config)
152
- expect { target_without_ssh.run("ls") }
153
- .to raise_error(/SSH not configured/)
154
- end
155
168
 
156
- 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")}"
157
171
  expect(Bard::Command).to receive(:run)
158
- .with("ls", on: target, home: false, verbose: false, quiet: false)
172
+ .with(expected_cmd, verbose: false, quiet: false)
159
173
  target.run("ls")
160
174
  end
161
- end
162
-
163
- describe "#exec!" do
164
- it "requires SSH capability" do
165
- target_without_ssh = described_class.new(:local, config)
166
- expect { target_without_ssh.exec!("ls") }
167
- .to raise_error(/SSH not configured/)
168
- end
169
175
 
170
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")}"
171
178
  expect(Bard::Command).to receive(:exec!)
172
- .with("ls", on: target, home: false)
179
+ .with(expected_cmd)
173
180
  target.exec!("ls")
174
181
  end
175
182
  end
176
183
  end
177
184
 
178
- describe "file transfer" do
179
- let(:source_target) do
180
- t = described_class.new(:source, config)
181
- t.ssh("source@example.com:22", path: "/source")
182
- t
183
- end
184
-
185
- let(:dest_target) do
186
- t = described_class.new(:dest, config)
187
- t.ssh("dest@example.com:22", path: "/dest")
188
- t
189
- end
190
-
191
- describe "#copy_file" do
192
- it "requires SSH capability on source" do
193
- target_without_ssh = described_class.new(:local, config)
194
- expect { target_without_ssh.copy_file("test.txt", to: dest_target) }
195
- .to raise_error(/SSH not configured/)
196
- end
197
-
198
- it "requires SSH capability on destination" do
199
- target_without_ssh = described_class.new(:local, config)
200
- expect { source_target.copy_file("test.txt", to: target_without_ssh) }
201
- .to raise_error(/SSH not configured/)
202
- end
203
-
204
- it "copies file via SCP" do
205
- expect(Bard::Copy).to receive(:file)
206
- .with("test.txt", from: source_target, to: dest_target, verbose: false)
207
- source_target.copy_file("test.txt", to: dest_target)
208
- end
209
- end
210
-
211
- describe "#copy_dir" do
212
- it "requires SSH capability on source" do
213
- target_without_ssh = described_class.new(:local, config)
214
- expect { target_without_ssh.copy_dir("test/", to: dest_target) }
215
- .to raise_error(/SSH not configured/)
216
- end
217
-
218
- it "requires SSH capability on destination" do
219
- target_without_ssh = described_class.new(:local, config)
220
- expect { source_target.copy_dir("test/", to: target_without_ssh) }
221
- .to raise_error(/SSH not configured/)
222
- end
223
-
224
- it "syncs directory via rsync" do
225
- expect(Bard::Copy).to receive(:dir)
226
- .with("test/", from: source_target, to: dest_target, verbose: false)
227
- source_target.copy_dir("test/", to: dest_target)
228
- end
229
- end
230
- end
231
-
232
185
  describe "#to_s" do
233
186
  it "returns the target key as string" do
234
187
  expect(target.to_s).to eq("production")