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,169 @@
1
+ require "spec_helper"
2
+ require "bard/ssh_server"
3
+
4
+ describe Bard::SSHServer do
5
+ describe "#initialize" do
6
+ it "parses SSH URI with user, host, and port" do
7
+ server = described_class.new("deploy@example.com:22")
8
+ expect(server.user).to eq("deploy")
9
+ expect(server.host).to eq("example.com")
10
+ expect(server.port).to eq("22")
11
+ end
12
+
13
+ it "handles SSH URI without port (defaults to 22)" do
14
+ server = described_class.new("deploy@example.com")
15
+ expect(server.user).to eq("deploy")
16
+ expect(server.host).to eq("example.com")
17
+ expect(server.port).to eq("22")
18
+ end
19
+
20
+ it "handles SSH URI without user (uses current user)" do
21
+ server = described_class.new("example.com:22")
22
+ expect(server.host).to eq("example.com")
23
+ expect(server.port).to eq("22")
24
+ expect(server.user).to eq(ENV['USER'])
25
+ end
26
+
27
+ it "accepts options hash" do
28
+ server = described_class.new("deploy@example.com:22",
29
+ path: "/app",
30
+ gateway: "bastion@example.com:22",
31
+ ssh_key: "/path/to/key",
32
+ env: "RAILS_ENV=production"
33
+ )
34
+
35
+ expect(server.path).to eq("/app")
36
+ expect(server.gateway).to eq("bastion@example.com:22")
37
+ expect(server.ssh_key).to eq("/path/to/key")
38
+ expect(server.env).to eq("RAILS_ENV=production")
39
+ end
40
+ end
41
+
42
+ describe "#ssh_uri" do
43
+ it "returns the SSH connection string" do
44
+ server = described_class.new("deploy@example.com:22")
45
+ expect(server.ssh_uri).to eq("deploy@example.com:22")
46
+ end
47
+
48
+ it "includes port if non-standard" do
49
+ server = described_class.new("deploy@example.com:2222")
50
+ expect(server.ssh_uri).to eq("deploy@example.com:2222")
51
+ end
52
+ end
53
+
54
+ describe "#hostname" do
55
+ it "extracts hostname from SSH URI" do
56
+ server = described_class.new("deploy@example.com:22")
57
+ expect(server.hostname).to eq("example.com")
58
+ end
59
+
60
+ it "handles IP addresses" do
61
+ server = described_class.new("deploy@192.168.1.1:22")
62
+ expect(server.hostname).to eq("192.168.1.1")
63
+ end
64
+ end
65
+
66
+ describe "#connection_string" do
67
+ it "builds SSH connection string" do
68
+ server = described_class.new("deploy@example.com:22")
69
+ expect(server.connection_string).to eq("deploy@example.com")
70
+ end
71
+ 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
+ end
@@ -0,0 +1,239 @@
1
+ require "spec_helper"
2
+ require "bard/target"
3
+
4
+ describe Bard::Target do
5
+ let(:config) { double("config", project_name: "testapp") }
6
+ let(:target) { described_class.new(:production, config) }
7
+
8
+ describe "#initialize" do
9
+ it "stores the target key" do
10
+ expect(target.key).to eq(:production)
11
+ end
12
+
13
+ it "stores the config" do
14
+ expect(target.config).to eq(config)
15
+ end
16
+
17
+ it "initializes with no capabilities" do
18
+ expect(target.has_capability?(:ssh)).to be false
19
+ expect(target.has_capability?(:ping)).to be false
20
+ end
21
+
22
+ it "initializes with no deploy strategy" do
23
+ expect(target.deploy_strategy).to be_nil
24
+ end
25
+ end
26
+
27
+ describe "#ssh" do
28
+ context "with simple SSH configuration" do
29
+ before { target.ssh("deploy@example.com:22") }
30
+
31
+ it "enables SSH capability" do
32
+ expect(target.has_capability?(:ssh)).to be true
33
+ end
34
+
35
+ it "creates SSHServer instance" do
36
+ expect(target.server).to be_a(Bard::SSHServer)
37
+ end
38
+
39
+ it "parses SSH URI" do
40
+ expect(target.ssh_uri).to eq("deploy@example.com:22")
41
+ end
42
+ end
43
+
44
+ context "with hash options" do
45
+ before do
46
+ target.ssh("deploy@example.com:22",
47
+ path: "/var/www/app",
48
+ gateway: "bastion@example.com:22",
49
+ ssh_key: "/path/to/key",
50
+ env: "RAILS_ENV=production"
51
+ )
52
+ end
53
+
54
+ it "enables SSH capability" do
55
+ expect(target.has_capability?(:ssh)).to be true
56
+ end
57
+
58
+ it "stores path" do
59
+ expect(target.path).to eq("/var/www/app")
60
+ end
61
+
62
+ it "stores gateway" do
63
+ expect(target.gateway).to eq("bastion@example.com:22")
64
+ end
65
+
66
+ it "stores SSH key" do
67
+ expect(target.ssh_key).to eq("/path/to/key")
68
+ end
69
+
70
+ it "stores environment variables" do
71
+ expect(target.env).to eq("RAILS_ENV=production")
72
+ end
73
+
74
+ it "auto-configures ping URL from hostname" do
75
+ expect(target.ping_urls).to include("example.com")
76
+ end
77
+ end
78
+
79
+ context "with false value" do
80
+ before { target.ssh(false) }
81
+
82
+ it "does not enable SSH capability" do
83
+ expect(target.has_capability?(:ssh)).to be false
84
+ end
85
+
86
+ it "sets server to nil" do
87
+ expect(target.server).to be_nil
88
+ end
89
+ end
90
+ end
91
+
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")
97
+ end
98
+
99
+ 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")
104
+ end
105
+
106
+ it "disables ping with false" do
107
+ target.ping("https://example.com")
108
+ target.ping(false)
109
+ expect(target.has_capability?(:ping)).to be false
110
+ expect(target.ping_urls).to be_empty
111
+ end
112
+ end
113
+
114
+ 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")
118
+ end
119
+
120
+ it "can be set via ssh options" do
121
+ target.ssh("deploy@example.com:22", path: "/app")
122
+ expect(target.path).to eq("/app")
123
+ end
124
+ end
125
+
126
+ describe "remote command execution" do
127
+ before do
128
+ target.ssh("deploy@example.com:22", path: "/app")
129
+ end
130
+
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/)
136
+ end
137
+
138
+ it "executes command on remote server" do
139
+ expect(Bard::Command).to receive(:run!)
140
+ .with("ls", on: target.server, home: false, verbose: false, quiet: false)
141
+ target.run!("ls")
142
+ 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
+
152
+ it "executes command on remote server without raising" do
153
+ expect(Bard::Command).to receive(:run)
154
+ .with("ls", on: target.server, home: false, verbose: false, quiet: false)
155
+ target.run("ls")
156
+ 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
+
166
+ it "replaces process with remote command" do
167
+ expect(Bard::Command).to receive(:exec!)
168
+ .with("ls", on: target.server, home: false)
169
+ target.exec!("ls")
170
+ end
171
+ end
172
+ end
173
+
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
+ describe "#to_s" do
229
+ it "returns the target key as string" do
230
+ expect(target.to_s).to eq("production")
231
+ end
232
+ end
233
+
234
+ describe "#to_sym" do
235
+ it "returns the target key as symbol" do
236
+ expect(target.to_sym).to eq(:production)
237
+ end
238
+ end
239
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.4
4
+ version: 2.0.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-12-16 00:00:00.000000000 Z
10
+ date: 2025-12-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -160,8 +160,12 @@ files:
160
160
  - ".gitignore"
161
161
  - ".gitmodules"
162
162
  - ".rspec"
163
+ - ARCHITECTURE.md
164
+ - CUSTOM_STRATEGIES.md
163
165
  - Gemfile
164
166
  - LICENSE
167
+ - MIGRATION_GUIDE.md
168
+ - README.md
165
169
  - README.rdoc
166
170
  - Rakefile
167
171
  - bard.gemspec
@@ -219,6 +223,10 @@ files:
219
223
  - lib/bard/command.rb
220
224
  - lib/bard/config.rb
221
225
  - lib/bard/copy.rb
226
+ - lib/bard/default_config.rb
227
+ - lib/bard/deploy_strategy.rb
228
+ - lib/bard/deploy_strategy/github_pages.rb
229
+ - lib/bard/deploy_strategy/ssh.rb
222
230
  - lib/bard/git.rb
223
231
  - lib/bard/github.rb
224
232
  - lib/bard/github_pages.rb
@@ -240,11 +248,14 @@ files:
240
248
  - lib/bard/provision/swapfile.rb
241
249
  - lib/bard/provision/user.rb
242
250
  - lib/bard/server.rb
251
+ - lib/bard/ssh_server.rb
252
+ - lib/bard/target.rb
243
253
  - lib/bard/version.rb
244
254
  - spec/acceptance/.gitignore
245
255
  - spec/acceptance/docker/Dockerfile
246
256
  - spec/acceptance/docker/test_key
247
257
  - spec/acceptance/docker/test_key.pub
258
+ - spec/bard/capability_spec.rb
248
259
  - spec/bard/ci/github_actions_spec.rb
249
260
  - spec/bard/ci_spec.rb
250
261
  - spec/bard/cli/ci_spec.rb
@@ -266,6 +277,9 @@ files:
266
277
  - spec/bard/command_spec.rb
267
278
  - spec/bard/config_spec.rb
268
279
  - spec/bard/copy_spec.rb
280
+ - spec/bard/deploy_strategy/ssh_spec.rb
281
+ - spec/bard/deploy_strategy_spec.rb
282
+ - spec/bard/dynamic_dsl_spec.rb
269
283
  - spec/bard/git_spec.rb
270
284
  - spec/bard/github_pages_spec.rb
271
285
  - spec/bard/github_spec.rb
@@ -287,6 +301,8 @@ files:
287
301
  - spec/bard/provision/user_spec.rb
288
302
  - spec/bard/provision_spec.rb
289
303
  - spec/bard/server_spec.rb
304
+ - spec/bard/ssh_server_spec.rb
305
+ - spec/bard/target_spec.rb
290
306
  - spec/bard_spec.rb
291
307
  - spec/spec_helper.rb
292
308
  - spec/support/fixtures/job.json
@@ -332,6 +348,7 @@ test_files:
332
348
  - spec/acceptance/docker/Dockerfile
333
349
  - spec/acceptance/docker/test_key
334
350
  - spec/acceptance/docker/test_key.pub
351
+ - spec/bard/capability_spec.rb
335
352
  - spec/bard/ci/github_actions_spec.rb
336
353
  - spec/bard/ci_spec.rb
337
354
  - spec/bard/cli/ci_spec.rb
@@ -353,6 +370,9 @@ test_files:
353
370
  - spec/bard/command_spec.rb
354
371
  - spec/bard/config_spec.rb
355
372
  - spec/bard/copy_spec.rb
373
+ - spec/bard/deploy_strategy/ssh_spec.rb
374
+ - spec/bard/deploy_strategy_spec.rb
375
+ - spec/bard/dynamic_dsl_spec.rb
356
376
  - spec/bard/git_spec.rb
357
377
  - spec/bard/github_pages_spec.rb
358
378
  - spec/bard/github_spec.rb
@@ -374,6 +394,8 @@ test_files:
374
394
  - spec/bard/provision/user_spec.rb
375
395
  - spec/bard/provision_spec.rb
376
396
  - spec/bard/server_spec.rb
397
+ - spec/bard/ssh_server_spec.rb
398
+ - spec/bard/target_spec.rb
377
399
  - spec/bard_spec.rb
378
400
  - spec/spec_helper.rb
379
401
  - spec/support/fixtures/job.json