bard 1.8.0 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +0 -5
- data/MIGRATION_GUIDE.md +9 -24
- data/Rakefile +1 -3
- data/features/bard_check.feature +94 -0
- data/features/bard_deploy.feature +18 -0
- data/features/bard_pull.feature +112 -0
- data/features/bard_push.feature +112 -0
- data/features/podman_testcontainers.feature +16 -0
- data/features/step_definitions/check_steps.rb +47 -0
- data/features/step_definitions/git_steps.rb +73 -0
- data/features/step_definitions/global_steps.rb +56 -0
- data/features/step_definitions/podman_steps.rb +23 -0
- data/features/step_definitions/rails_steps.rb +44 -0
- data/features/step_definitions/submodule_steps.rb +110 -0
- data/features/support/env.rb +39 -5
- data/features/support/grit_ext.rb +13 -0
- data/features/support/io.rb +32 -0
- data/features/support/podman.rb +153 -0
- data/lib/bard/command.rb +10 -29
- data/lib/bard/config.rb +0 -2
- data/lib/bard/copy.rb +33 -12
- data/lib/bard/server.rb +1 -43
- data/lib/bard/ssh_server.rb +1 -1
- data/lib/bard/target.rb +15 -65
- data/lib/bard/version.rb +1 -1
- data/spec/acceptance/docker/Dockerfile +1 -2
- data/spec/bard/command_spec.rb +1 -1
- data/spec/bard/copy_spec.rb +3 -3
- data/spec/bard/ssh_server_spec.rb +3 -7
- data/spec/bard/target_spec.rb +5 -9
- metadata +30 -16
- data/cucumber.yml +0 -1
- data/features/data.feature +0 -12
- data/features/deploy.feature +0 -13
- data/features/run.feature +0 -13
- data/features/step_definitions/bard_steps.rb +0 -39
- data/features/support/test_server.rb +0 -215
- data/lib/bard/deprecation.rb +0 -19
- data/spec/bard/deprecation_spec.rb +0 -281
data/lib/bard/target.rb
CHANGED
|
@@ -2,12 +2,11 @@ require "uri"
|
|
|
2
2
|
require "bard/command"
|
|
3
3
|
require "bard/copy"
|
|
4
4
|
require "bard/deploy_strategy"
|
|
5
|
-
require "bard/deprecation"
|
|
6
5
|
|
|
7
6
|
module Bard
|
|
8
7
|
class Target
|
|
9
|
-
attr_reader :key, :config
|
|
10
|
-
attr_accessor :server
|
|
8
|
+
attr_reader :key, :config, :path
|
|
9
|
+
attr_accessor :server, :gateway, :ssh_key, :env
|
|
11
10
|
|
|
12
11
|
def initialize(key, config)
|
|
13
12
|
@key = key
|
|
@@ -68,7 +67,7 @@ module Bard
|
|
|
68
67
|
|
|
69
68
|
# Auto-configure ping from hostname
|
|
70
69
|
hostname = @server.hostname
|
|
71
|
-
ping(
|
|
70
|
+
ping(hostname) if hostname
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
|
@@ -79,46 +78,17 @@ module Bard
|
|
|
79
78
|
# Path configuration
|
|
80
79
|
def path(new_path = nil)
|
|
81
80
|
if new_path
|
|
82
|
-
Deprecation.warn "Separate `path` call is deprecated; pass as keyword argument to `ssh` instead, e.g., `ssh \"user@host\", path: \"#{new_path}\"` (will be removed in v2.0)"
|
|
83
81
|
@path = new_path
|
|
84
82
|
else
|
|
85
83
|
@path || config.project_name
|
|
86
84
|
end
|
|
87
85
|
end
|
|
88
86
|
|
|
89
|
-
# Deprecated separate setter methods - use ssh(..., option: value) instead
|
|
90
|
-
def gateway(value = nil)
|
|
91
|
-
if value
|
|
92
|
-
Deprecation.warn "Separate `gateway` call is deprecated; pass as keyword argument to `ssh` instead, e.g., `ssh \"user@host\", gateway: \"#{value}\"` (will be removed in v2.0)"
|
|
93
|
-
@gateway = value
|
|
94
|
-
else
|
|
95
|
-
@gateway
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def ssh_key(value = nil)
|
|
100
|
-
if value
|
|
101
|
-
Deprecation.warn "Separate `ssh_key` call is deprecated; pass as keyword argument to `ssh` instead, e.g., `ssh \"user@host\", ssh_key: \"#{value}\"` (will be removed in v2.0)"
|
|
102
|
-
@ssh_key = value
|
|
103
|
-
else
|
|
104
|
-
@ssh_key
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def env(value = nil)
|
|
109
|
-
if value
|
|
110
|
-
Deprecation.warn "Separate `env` call is deprecated; pass as keyword argument to `ssh` instead, e.g., `ssh \"user@host\", env: \"#{value}\"` (will be removed in v2.0)"
|
|
111
|
-
@env = value
|
|
112
|
-
else
|
|
113
|
-
@env
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
87
|
# Ping configuration
|
|
118
88
|
def ping(*urls)
|
|
119
89
|
if urls.empty?
|
|
120
|
-
# Getter
|
|
121
|
-
@ping_urls
|
|
90
|
+
# Getter
|
|
91
|
+
@ping_urls
|
|
122
92
|
elsif urls.first == false
|
|
123
93
|
# Disable ping
|
|
124
94
|
@ping_urls = []
|
|
@@ -164,18 +134,6 @@ module Bard
|
|
|
164
134
|
end
|
|
165
135
|
end
|
|
166
136
|
|
|
167
|
-
# Deprecated strategy configuration methods
|
|
168
|
-
def strategy(name)
|
|
169
|
-
Deprecation.warn "`strategy` is deprecated; use the strategy method directly, e.g., `#{name} \"url\"` instead of `strategy :#{name}` (will be removed in v2.0)"
|
|
170
|
-
@deploy_strategy = name
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def option(key, value)
|
|
174
|
-
Deprecation.warn "`option` is deprecated; pass options as keyword arguments to the strategy method, e.g., `jets \"url\", #{key}: #{value.inspect}` (will be removed in v2.0)"
|
|
175
|
-
@strategy_options_hash[@deploy_strategy] ||= {}
|
|
176
|
-
@strategy_options_hash[@deploy_strategy][key] = value
|
|
177
|
-
end
|
|
178
|
-
|
|
179
137
|
def strategy_options(strategy_name)
|
|
180
138
|
@strategy_options_hash[strategy_name] || {}
|
|
181
139
|
end
|
|
@@ -219,41 +177,42 @@ module Bard
|
|
|
219
177
|
# Remote command execution
|
|
220
178
|
def run!(command, home: false, verbose: false, quiet: false)
|
|
221
179
|
require_capability!(:ssh)
|
|
222
|
-
Command.run!(command, on:
|
|
180
|
+
Command.run!(command, on: server, home: home, verbose: verbose, quiet: quiet)
|
|
223
181
|
end
|
|
224
182
|
|
|
225
183
|
def run(command, home: false, verbose: false, quiet: false)
|
|
226
184
|
require_capability!(:ssh)
|
|
227
|
-
Command.run(command, on:
|
|
185
|
+
Command.run(command, on: server, home: home, verbose: verbose, quiet: quiet)
|
|
228
186
|
end
|
|
229
187
|
|
|
230
188
|
def exec!(command, home: false)
|
|
231
189
|
require_capability!(:ssh)
|
|
232
|
-
Command.exec!(command, on:
|
|
190
|
+
Command.exec!(command, on: server, home: home)
|
|
233
191
|
end
|
|
234
192
|
|
|
235
193
|
# File transfer
|
|
236
194
|
def copy_file(path, to:, verbose: false)
|
|
237
195
|
require_capability!(:ssh)
|
|
238
|
-
to.require_capability!(:ssh)
|
|
196
|
+
to.require_capability!(:ssh)
|
|
239
197
|
Copy.file(path, from: self, to: to, verbose: verbose)
|
|
240
198
|
end
|
|
241
199
|
|
|
242
200
|
def copy_dir(path, to:, verbose: false)
|
|
243
201
|
require_capability!(:ssh)
|
|
244
|
-
to.require_capability!(:ssh)
|
|
202
|
+
to.require_capability!(:ssh)
|
|
245
203
|
Copy.dir(path, from: self, to: to, verbose: verbose)
|
|
246
204
|
end
|
|
247
205
|
|
|
248
206
|
# URI methods for compatibility
|
|
249
207
|
def scp_uri(file_path = nil)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
208
|
+
uri = URI("scp://#{ssh_uri}")
|
|
209
|
+
uri.path = "/#{path}"
|
|
210
|
+
uri.path += "/#{file_path}" if file_path
|
|
211
|
+
uri
|
|
253
212
|
end
|
|
254
213
|
|
|
255
214
|
def rsync_uri(file_path = nil)
|
|
256
|
-
uri = ssh_uri
|
|
215
|
+
uri = URI("ssh://#{ssh_uri}")
|
|
257
216
|
str = "#{uri.user}@#{uri.host}"
|
|
258
217
|
str += ":#{path}"
|
|
259
218
|
str += "/#{file_path}" if file_path
|
|
@@ -276,14 +235,5 @@ module Bard
|
|
|
276
235
|
end
|
|
277
236
|
end
|
|
278
237
|
end
|
|
279
|
-
|
|
280
|
-
private
|
|
281
|
-
|
|
282
|
-
def normalize_ping(value)
|
|
283
|
-
return nil if value == false || value.nil?
|
|
284
|
-
normalized = value.to_s
|
|
285
|
-
normalized = "https://#{normalized}" unless normalized.start_with?("http")
|
|
286
|
-
normalized
|
|
287
|
-
end
|
|
288
238
|
end
|
|
289
239
|
end
|
data/lib/bard/version.rb
CHANGED
|
@@ -3,11 +3,10 @@ FROM ubuntu:22.04
|
|
|
3
3
|
# Prevent interactive prompts
|
|
4
4
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
5
5
|
|
|
6
|
-
# Install SSH server
|
|
6
|
+
# Install SSH server and basic tools
|
|
7
7
|
RUN apt-get update && \
|
|
8
8
|
apt-get install -y \
|
|
9
9
|
openssh-server \
|
|
10
|
-
git \
|
|
11
10
|
sudo \
|
|
12
11
|
&& rm -rf /var/lib/apt/lists/*
|
|
13
12
|
|
data/spec/bard/command_spec.rb
CHANGED
|
@@ -11,7 +11,7 @@ describe Bard::Command do
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
it "should run a command on a remote server" do
|
|
14
|
-
expect(Open3).to receive(:capture3).with("ssh -tt
|
|
14
|
+
expect(Open3).to receive(:capture3).with("ssh -tt user@example.com 'cd /path/to && ls -l'").and_return(["output", "", 0])
|
|
15
15
|
Bard::Command.run "ls -l", on: remote
|
|
16
16
|
end
|
|
17
17
|
end
|
data/spec/bard/copy_spec.rb
CHANGED
|
@@ -2,17 +2,17 @@ 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,
|
|
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
6
|
let(:local) { double("local", key: :local) }
|
|
7
7
|
|
|
8
8
|
context ".file" do
|
|
9
9
|
it "should copy a file from a remote server to the local machine" do
|
|
10
|
-
expect(Bard::Command).to receive(:run!).with("scp
|
|
10
|
+
expect(Bard::Command).to receive(:run!).with("scp user@example.com:/path/to/file path/to/file", verbose: false)
|
|
11
11
|
Bard::Copy.file "path/to/file", from: production, to: local
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it "should copy a file from the local machine to a remote server" do
|
|
15
|
-
expect(Bard::Command).to receive(:run!).with("scp
|
|
15
|
+
expect(Bard::Command).to receive(:run!).with("scp path/to/file user@example.com:/path/to/file", verbose: false)
|
|
16
16
|
Bard::Copy.file "path/to/file", from: local, to: production
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -40,18 +40,14 @@ describe Bard::SSHServer do
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
describe "#ssh_uri" do
|
|
43
|
-
it "returns
|
|
43
|
+
it "returns the SSH connection string" do
|
|
44
44
|
server = described_class.new("deploy@example.com:22")
|
|
45
|
-
expect(server.ssh_uri).to
|
|
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)
|
|
45
|
+
expect(server.ssh_uri).to eq("deploy@example.com:22")
|
|
50
46
|
end
|
|
51
47
|
|
|
52
48
|
it "includes port if non-standard" do
|
|
53
49
|
server = described_class.new("deploy@example.com:2222")
|
|
54
|
-
expect(server.ssh_uri
|
|
50
|
+
expect(server.ssh_uri).to eq("deploy@example.com:2222")
|
|
55
51
|
end
|
|
56
52
|
end
|
|
57
53
|
|
data/spec/bard/target_spec.rb
CHANGED
|
@@ -37,11 +37,7 @@ describe Bard::Target do
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
it "parses SSH URI" do
|
|
40
|
-
expect(target.ssh_uri).to
|
|
41
|
-
expect(target.ssh_uri.scheme).to eq("ssh")
|
|
42
|
-
expect(target.ssh_uri.user).to eq("deploy")
|
|
43
|
-
expect(target.ssh_uri.host).to eq("example.com")
|
|
44
|
-
expect(target.ssh_uri.port).to eq(22)
|
|
40
|
+
expect(target.ssh_uri).to eq("deploy@example.com:22")
|
|
45
41
|
end
|
|
46
42
|
end
|
|
47
43
|
|
|
@@ -76,7 +72,7 @@ describe Bard::Target do
|
|
|
76
72
|
end
|
|
77
73
|
|
|
78
74
|
it "auto-configures ping URL from hostname" do
|
|
79
|
-
expect(target.ping_urls).to include("
|
|
75
|
+
expect(target.ping_urls).to include("example.com")
|
|
80
76
|
end
|
|
81
77
|
end
|
|
82
78
|
|
|
@@ -141,7 +137,7 @@ describe Bard::Target do
|
|
|
141
137
|
|
|
142
138
|
it "executes command on remote server" do
|
|
143
139
|
expect(Bard::Command).to receive(:run!)
|
|
144
|
-
.with("ls", on: target, home: false, verbose: false, quiet: false)
|
|
140
|
+
.with("ls", on: target.server, home: false, verbose: false, quiet: false)
|
|
145
141
|
target.run!("ls")
|
|
146
142
|
end
|
|
147
143
|
end
|
|
@@ -155,7 +151,7 @@ describe Bard::Target do
|
|
|
155
151
|
|
|
156
152
|
it "executes command on remote server without raising" do
|
|
157
153
|
expect(Bard::Command).to receive(:run)
|
|
158
|
-
.with("ls", on: target, home: false, verbose: false, quiet: false)
|
|
154
|
+
.with("ls", on: target.server, home: false, verbose: false, quiet: false)
|
|
159
155
|
target.run("ls")
|
|
160
156
|
end
|
|
161
157
|
end
|
|
@@ -169,7 +165,7 @@ describe Bard::Target do
|
|
|
169
165
|
|
|
170
166
|
it "replaces process with remote command" do
|
|
171
167
|
expect(Bard::Command).to receive(:exec!)
|
|
172
|
-
.with("ls", on: target, home: false)
|
|
168
|
+
.with("ls", on: target.server, home: false)
|
|
173
169
|
target.exec!("ls")
|
|
174
170
|
end
|
|
175
171
|
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:
|
|
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:
|
|
10
|
+
date: 2025-12-18 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: thor
|
|
@@ -171,13 +171,21 @@ files:
|
|
|
171
171
|
- bard.gemspec
|
|
172
172
|
- bin/bard
|
|
173
173
|
- bin/setup
|
|
174
|
-
-
|
|
175
|
-
- features/
|
|
176
|
-
- features/
|
|
177
|
-
- features/
|
|
178
|
-
- features/
|
|
174
|
+
- features/bard_check.feature
|
|
175
|
+
- features/bard_deploy.feature
|
|
176
|
+
- features/bard_pull.feature
|
|
177
|
+
- features/bard_push.feature
|
|
178
|
+
- features/podman_testcontainers.feature
|
|
179
|
+
- features/step_definitions/check_steps.rb
|
|
180
|
+
- features/step_definitions/git_steps.rb
|
|
181
|
+
- features/step_definitions/global_steps.rb
|
|
182
|
+
- features/step_definitions/podman_steps.rb
|
|
183
|
+
- features/step_definitions/rails_steps.rb
|
|
184
|
+
- features/step_definitions/submodule_steps.rb
|
|
179
185
|
- features/support/env.rb
|
|
180
|
-
- features/support/
|
|
186
|
+
- features/support/grit_ext.rb
|
|
187
|
+
- features/support/io.rb
|
|
188
|
+
- features/support/podman.rb
|
|
181
189
|
- install_files/.github/dependabot.yml
|
|
182
190
|
- install_files/.github/workflows/cache-ci.yml
|
|
183
191
|
- install_files/.github/workflows/ci.yml
|
|
@@ -219,7 +227,6 @@ files:
|
|
|
219
227
|
- lib/bard/deploy_strategy.rb
|
|
220
228
|
- lib/bard/deploy_strategy/github_pages.rb
|
|
221
229
|
- lib/bard/deploy_strategy/ssh.rb
|
|
222
|
-
- lib/bard/deprecation.rb
|
|
223
230
|
- lib/bard/git.rb
|
|
224
231
|
- lib/bard/github.rb
|
|
225
232
|
- lib/bard/github_pages.rb
|
|
@@ -272,7 +279,6 @@ files:
|
|
|
272
279
|
- spec/bard/copy_spec.rb
|
|
273
280
|
- spec/bard/deploy_strategy/ssh_spec.rb
|
|
274
281
|
- spec/bard/deploy_strategy_spec.rb
|
|
275
|
-
- spec/bard/deprecation_spec.rb
|
|
276
282
|
- spec/bard/dynamic_dsl_spec.rb
|
|
277
283
|
- spec/bard/git_spec.rb
|
|
278
284
|
- spec/bard/github_pages_spec.rb
|
|
@@ -323,12 +329,21 @@ rubygems_version: 3.6.2
|
|
|
323
329
|
specification_version: 4
|
|
324
330
|
summary: CLI to automate common development tasks.
|
|
325
331
|
test_files:
|
|
326
|
-
- features/
|
|
327
|
-
- features/
|
|
328
|
-
- features/
|
|
329
|
-
- features/
|
|
332
|
+
- features/bard_check.feature
|
|
333
|
+
- features/bard_deploy.feature
|
|
334
|
+
- features/bard_pull.feature
|
|
335
|
+
- features/bard_push.feature
|
|
336
|
+
- features/podman_testcontainers.feature
|
|
337
|
+
- features/step_definitions/check_steps.rb
|
|
338
|
+
- features/step_definitions/git_steps.rb
|
|
339
|
+
- features/step_definitions/global_steps.rb
|
|
340
|
+
- features/step_definitions/podman_steps.rb
|
|
341
|
+
- features/step_definitions/rails_steps.rb
|
|
342
|
+
- features/step_definitions/submodule_steps.rb
|
|
330
343
|
- features/support/env.rb
|
|
331
|
-
- features/support/
|
|
344
|
+
- features/support/grit_ext.rb
|
|
345
|
+
- features/support/io.rb
|
|
346
|
+
- features/support/podman.rb
|
|
332
347
|
- spec/acceptance/.gitignore
|
|
333
348
|
- spec/acceptance/docker/Dockerfile
|
|
334
349
|
- spec/acceptance/docker/test_key
|
|
@@ -357,7 +372,6 @@ test_files:
|
|
|
357
372
|
- spec/bard/copy_spec.rb
|
|
358
373
|
- spec/bard/deploy_strategy/ssh_spec.rb
|
|
359
374
|
- spec/bard/deploy_strategy_spec.rb
|
|
360
|
-
- spec/bard/deprecation_spec.rb
|
|
361
375
|
- spec/bard/dynamic_dsl_spec.rb
|
|
362
376
|
- spec/bard/git_spec.rb
|
|
363
377
|
- spec/bard/github_pages_spec.rb
|
data/cucumber.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
default: --publish-quiet
|
data/features/data.feature
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Feature: bard data
|
|
2
|
-
Copy database from a remote server to local.
|
|
3
|
-
|
|
4
|
-
Background:
|
|
5
|
-
Given a test server is running
|
|
6
|
-
|
|
7
|
-
Scenario: copies database from production to local
|
|
8
|
-
When I run: bard data
|
|
9
|
-
Then the output should contain "Dumping production database to file"
|
|
10
|
-
And the output should contain "Transfering file from production to local"
|
|
11
|
-
And the output should contain "Loading file into local database"
|
|
12
|
-
And a file "db/data.sql.gz" should exist locally
|
data/features/deploy.feature
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Feature: bard deploy
|
|
2
|
-
Deploy code changes to a remote server.
|
|
3
|
-
|
|
4
|
-
Background:
|
|
5
|
-
Given a test server is running
|
|
6
|
-
|
|
7
|
-
Scenario: deploys code changes to the remote server
|
|
8
|
-
Given I create a file "DEPLOYED.txt" with content "deployed by bard"
|
|
9
|
-
And I commit the changes with message "Add deployed marker"
|
|
10
|
-
When I run: bard deploy --skip-ci
|
|
11
|
-
Then the output should contain "Deploy Succeeded"
|
|
12
|
-
When I run: bard run "cat DEPLOYED.txt"
|
|
13
|
-
Then the output should contain "deployed by bard"
|
data/features/run.feature
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Feature: bard run
|
|
2
|
-
Execute commands on a remote server.
|
|
3
|
-
|
|
4
|
-
Background:
|
|
5
|
-
Given a test server is running
|
|
6
|
-
|
|
7
|
-
Scenario: executes a command on the remote server
|
|
8
|
-
When I run: bard run "echo hello"
|
|
9
|
-
Then the output should contain "hello"
|
|
10
|
-
|
|
11
|
-
Scenario: operates in the configured path
|
|
12
|
-
When I run: bard run "pwd"
|
|
13
|
-
Then the output should contain "testproject"
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
Given /^a test server is running$/ do
|
|
2
|
-
raise "Test server failed to start" unless @container && @ssh_port
|
|
3
|
-
end
|
|
4
|
-
|
|
5
|
-
When /^I run: bard (.+)$/ do |command|
|
|
6
|
-
run_bard(command)
|
|
7
|
-
unless @status.success?
|
|
8
|
-
raise "Command failed with status: #{@status}\nOutput: #{@stdout}"
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
When /^I run expecting failure: bard (.+)$/ do |command|
|
|
13
|
-
run_bard(command)
|
|
14
|
-
unless !@status.success?
|
|
15
|
-
raise "Command succeeded but was expected to fail\nOutput: #{@stdout}"
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
Then /^the output should contain "([^\"]+)"$/ do |expected|
|
|
20
|
-
expect(@stdout).to include(expected)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
Given /^I create a file "([^\"]+)" with content "([^\"]+)"$/ do |filename, content|
|
|
24
|
-
Dir.chdir(@test_dir) do
|
|
25
|
-
File.write(filename, content)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
Given /^I commit the changes with message "([^\"]+)"$/ do |message|
|
|
30
|
-
Dir.chdir(@test_dir) do
|
|
31
|
-
system("git add -A", out: File::NULL, err: File::NULL)
|
|
32
|
-
system("git commit -m '#{message}'", out: File::NULL, err: File::NULL)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
Then /^a file "([^\"]+)" should exist locally$/ do |filename|
|
|
37
|
-
path = File.join(@test_dir, filename)
|
|
38
|
-
expect(File.exist?(path)).to be(true), "Expected file #{filename} to exist at #{path}"
|
|
39
|
-
end
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
require "open3"
|
|
3
|
-
require "tmpdir"
|
|
4
|
-
require "docker-api"
|
|
5
|
-
|
|
6
|
-
module TestServerWorld
|
|
7
|
-
class << self
|
|
8
|
-
attr_accessor :server_available, :image_built
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class PrerequisiteError < StandardError; end
|
|
12
|
-
|
|
13
|
-
def ensure_server_available
|
|
14
|
-
return if TestServerWorld.server_available
|
|
15
|
-
|
|
16
|
-
unless system("command -v podman >/dev/null 2>&1")
|
|
17
|
-
raise PrerequisiteError, "podman is not installed"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
configure_container_socket
|
|
21
|
-
build_test_image
|
|
22
|
-
FileUtils.chmod(0o600, ssh_key_path)
|
|
23
|
-
|
|
24
|
-
TestServerWorld.server_available = true
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def configure_container_socket
|
|
28
|
-
if ENV["DOCKER_HOST"]
|
|
29
|
-
Docker.url = ENV["DOCKER_HOST"]
|
|
30
|
-
return
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
socket_path = "/run/user/#{Process.uid}/podman/podman.sock"
|
|
34
|
-
unless File.exist?(socket_path)
|
|
35
|
-
system("systemctl --user start podman.socket 2>/dev/null")
|
|
36
|
-
sleep 2
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
unless File.exist?(socket_path)
|
|
40
|
-
raise PrerequisiteError, "Podman socket not available"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
ENV["DOCKER_HOST"] = "unix://#{socket_path}"
|
|
44
|
-
Docker.url = ENV["DOCKER_HOST"]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def build_test_image
|
|
48
|
-
return if TestServerWorld.image_built
|
|
49
|
-
|
|
50
|
-
# Check if image already exists (e.g., pre-built in CI)
|
|
51
|
-
if image_exists?("bard-test-server")
|
|
52
|
-
TestServerWorld.image_built = true
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
system("podman pull ubuntu:22.04 >/dev/null 2>&1")
|
|
57
|
-
|
|
58
|
-
docker_dir = File.join(ROOT, "spec/acceptance/docker")
|
|
59
|
-
unless system("podman build -t bard-test-server -f #{docker_dir}/Dockerfile #{docker_dir} 2>&1")
|
|
60
|
-
raise PrerequisiteError, "Failed to build test image"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
TestServerWorld.image_built = true
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def image_exists?(name)
|
|
67
|
-
Docker::Image.get(name)
|
|
68
|
-
true
|
|
69
|
-
rescue Docker::Error::NotFoundError
|
|
70
|
-
false
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def start_test_server
|
|
74
|
-
ensure_server_available
|
|
75
|
-
|
|
76
|
-
@container = Docker::Container.create(
|
|
77
|
-
"Image" => "localhost/bard-test-server:latest",
|
|
78
|
-
"ExposedPorts" => { "22/tcp" => {} },
|
|
79
|
-
"HostConfig" => {
|
|
80
|
-
"PortBindings" => { "22/tcp" => [{ "HostPort" => "" }] },
|
|
81
|
-
"PublishAllPorts" => true
|
|
82
|
-
}
|
|
83
|
-
)
|
|
84
|
-
@container.start
|
|
85
|
-
@container.refresh!
|
|
86
|
-
|
|
87
|
-
@ssh_port = @container.info["NetworkSettings"]["Ports"]["22/tcp"].first["HostPort"].to_i
|
|
88
|
-
|
|
89
|
-
wait_for_ssh
|
|
90
|
-
setup_test_directory
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def wait_for_ssh
|
|
94
|
-
30.times do
|
|
95
|
-
return if system(
|
|
96
|
-
"ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null",
|
|
97
|
-
"-o", "ConnectTimeout=1", "-p", @ssh_port.to_s, "-i", ssh_key_path,
|
|
98
|
-
"deploy@localhost", "true",
|
|
99
|
-
out: File::NULL, err: File::NULL
|
|
100
|
-
)
|
|
101
|
-
sleep 0.5
|
|
102
|
-
end
|
|
103
|
-
raise PrerequisiteError, "SSH not ready"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def setup_test_directory
|
|
107
|
-
# Set up git repos on the remote container
|
|
108
|
-
run_ssh "git config --global user.email 'test@example.com'"
|
|
109
|
-
run_ssh "git config --global user.name 'Test User'"
|
|
110
|
-
run_ssh "git config --global init.defaultBranch master"
|
|
111
|
-
run_ssh "mkdir -p ~/repos/testproject.git"
|
|
112
|
-
run_ssh "cd ~/repos/testproject.git && git init --bare"
|
|
113
|
-
run_ssh "git clone ~/repos/testproject.git ~/testproject"
|
|
114
|
-
run_ssh "mkdir -p ~/testproject/bin ~/testproject/db"
|
|
115
|
-
|
|
116
|
-
# bin/setup script
|
|
117
|
-
run_ssh "echo '#!/bin/bash' > ~/testproject/bin/setup"
|
|
118
|
-
run_ssh "echo 'echo Setup complete' >> ~/testproject/bin/setup"
|
|
119
|
-
run_ssh "chmod +x ~/testproject/bin/setup"
|
|
120
|
-
|
|
121
|
-
# bin/rake script for db:dump and db:load
|
|
122
|
-
run_ssh <<~'SETUP'
|
|
123
|
-
cat > ~/testproject/bin/rake << 'SCRIPT'
|
|
124
|
-
#!/bin/bash
|
|
125
|
-
case "$1" in
|
|
126
|
-
db:dump)
|
|
127
|
-
echo "production data" | gzip > db/data.sql.gz
|
|
128
|
-
;;
|
|
129
|
-
db:load)
|
|
130
|
-
gunzip -c db/data.sql.gz > /dev/null
|
|
131
|
-
echo "Data loaded"
|
|
132
|
-
;;
|
|
133
|
-
esac
|
|
134
|
-
SCRIPT
|
|
135
|
-
SETUP
|
|
136
|
-
run_ssh "chmod +x ~/testproject/bin/rake"
|
|
137
|
-
run_ssh "cd ~/testproject && git add . && git commit -m 'Initial commit'"
|
|
138
|
-
run_ssh "cd ~/testproject && git push origin master"
|
|
139
|
-
|
|
140
|
-
# Set up local git repo in isolated temp directory
|
|
141
|
-
setup_local_git_repo
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def setup_local_git_repo
|
|
145
|
-
@test_dir = Dir.mktmpdir("bard_test")
|
|
146
|
-
@ssh_command = "ssh -i #{ssh_key_path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
|
147
|
-
|
|
148
|
-
Dir.chdir(@test_dir) do
|
|
149
|
-
# Clone directly into the temp directory (pass SSH command via env, not global ENV)
|
|
150
|
-
ssh_url = "ssh://deploy@localhost:#{@ssh_port}/home/deploy/repos/testproject.git"
|
|
151
|
-
system({ "GIT_SSH_COMMAND" => @ssh_command }, "git clone #{ssh_url} .", out: File::NULL, err: File::NULL)
|
|
152
|
-
|
|
153
|
-
# Configure git settings locally in this repo only
|
|
154
|
-
system("git config user.email 'test@example.com'", out: File::NULL, err: File::NULL)
|
|
155
|
-
system("git config user.name 'Test User'", out: File::NULL, err: File::NULL)
|
|
156
|
-
system("git config core.sshCommand '#{@ssh_command}'", out: File::NULL, err: File::NULL)
|
|
157
|
-
|
|
158
|
-
# Ensure db directory exists locally
|
|
159
|
-
FileUtils.mkdir_p("db")
|
|
160
|
-
|
|
161
|
-
# Write bard config in the test directory
|
|
162
|
-
File.write("bard.rb", <<~RUBY)
|
|
163
|
-
target :production do
|
|
164
|
-
ssh "deploy@localhost:#{@ssh_port}",
|
|
165
|
-
path: "testproject",
|
|
166
|
-
ssh_key: "#{ssh_key_path}"
|
|
167
|
-
ping false
|
|
168
|
-
end
|
|
169
|
-
RUBY
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def run_ssh(command)
|
|
174
|
-
stdout, status = Open3.capture2e(
|
|
175
|
-
"ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null",
|
|
176
|
-
"-p", @ssh_port.to_s, "-i", ssh_key_path,
|
|
177
|
-
"deploy@localhost", command
|
|
178
|
-
)
|
|
179
|
-
unless status.success?
|
|
180
|
-
raise PrerequisiteError, "SSH command failed: #{command}\nOutput: #{stdout}"
|
|
181
|
-
end
|
|
182
|
-
true
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def run_bard(command)
|
|
186
|
-
Dir.chdir(@test_dir) do
|
|
187
|
-
@stdout, @status = Open3.capture2e("bard #{command}")
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def ssh_key_path
|
|
192
|
-
File.join(ROOT, "spec/acceptance/docker/test_key")
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def stop_test_server
|
|
196
|
-
return unless @container
|
|
197
|
-
@container.stop rescue nil
|
|
198
|
-
@container.delete(force: true) rescue nil
|
|
199
|
-
ensure
|
|
200
|
-
@container = nil
|
|
201
|
-
@ssh_port = nil
|
|
202
|
-
FileUtils.rm_rf(@test_dir) if @test_dir
|
|
203
|
-
@test_dir = nil
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
World(TestServerWorld)
|
|
208
|
-
|
|
209
|
-
Before do
|
|
210
|
-
start_test_server
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
After do
|
|
214
|
-
stop_test_server
|
|
215
|
-
end
|