bard 1.9.0 → 1.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 937ff227cd4b15691d6aa48c4d5f6670aa6c0232adf650672bd8790fb6baa6fa
4
- data.tar.gz: 2858f62171dedf98e5518e16a82b0e1569c8c251024e7a934b9ebcbbec15a568
3
+ metadata.gz: '09960cdea14d636cd11c29d1f88114783daf97c144b379645daee11077f58815'
4
+ data.tar.gz: 52089e9a96cd8aacb4e49bb075b03199e18f138bffdb6bc6e86d5f8262a017a3
5
5
  SHA512:
6
- metadata.gz: 03ecce7b9ee18e490a207d776b92958f71d17d2738f6641ceeb9a9a1605aba169dff86f942ae727134197bbed6b5b80825ed8a9a02b17d912dee3aa176ecae89
7
- data.tar.gz: b39fe1a1e8d4e92961425ffdd5856d90f926d6f96cb19690e884b2b7c00776f1251285c9ef2034303f0cd3490823206cf975903f775ac19104e4b3243ac72b14
6
+ metadata.gz: 564cb4579c1a1451c2281e03f6541f621fc82e5be5e9ac2170e3c06838382d84bb860e02ada2670f444bf319ab62f5a42ff50a574b48c582a9dd304bf5075122
7
+ data.tar.gz: b20e398b8b8e87b177c365944c157f40083231f9478da9555a324eb9f60a929abe4995ba52e421107e8ff501aebc497232ada569fc3aa53647d21537c1c4fb31
@@ -22,7 +22,7 @@ class Bard::CLI::Provision < Bard::CLI::Command
22
22
 
23
23
  desc "provision [ssh_url] --steps=all", "takes an optional ssh url to a raw ubuntu 22.04 install, and readies it in the shape of :production"
24
24
  option :steps, type: :array, default: STEPS
25
- def provision ssh_url=config[:production].ssh
25
+ def provision ssh_url=config[:production].ssh&.to_s
26
26
  # unfreeze the string for later mutation
27
27
  ssh_url = ssh_url.dup
28
28
  options[:steps].each do |step|
@@ -12,7 +12,16 @@ module Bard::CLI::Stage
12
12
  end
13
13
 
14
14
  run! "git push -u origin #{branch}", verbose: true
15
- config[:staging].run! "git fetch && git checkout -f origin/#{branch} && bin/setup"
15
+
16
+ target = config[:staging]
17
+ if target.respond_to?(:deploy_strategy) && target.deploy_strategy
18
+ require "bard/deploy_strategy/#{target.deploy_strategy}"
19
+ strategy = target.deploy_strategy_instance
20
+ strategy.deploy
21
+ else
22
+ target.run! "git fetch && git checkout -f origin/#{branch} && bin/setup"
23
+ end
24
+
16
25
  puts green("Stage Succeeded")
17
26
 
18
27
  ping :staging
data/lib/bard/cli.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # this file gets loaded in the CLI context, not the Rails boot context
2
2
 
3
3
  require "thor"
4
+ require "bard/version"
4
5
  require "bard/config"
5
6
  require "bard/command"
6
7
  require "bard/plugin"
@@ -33,6 +34,12 @@ module Bard
33
34
  require "bard/ci/local"
34
35
  require "bard/ci/github_actions"
35
36
 
37
+ map "--version" => :version
38
+ desc "version", "Display version"
39
+ def version
40
+ puts Bard::VERSION
41
+ end
42
+
36
43
  def self.exit_on_failure? = true
37
44
 
38
45
  no_commands do
data/lib/bard/command.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "open3"
2
+ require "shellwords"
2
3
 
3
4
  module Bard
4
5
  class Command < Struct.new(:command, :on, :home)
@@ -17,9 +18,9 @@ module Bard
17
18
  end
18
19
 
19
20
  def run! verbose: false, quiet: false
20
- if !run(verbose:, quiet:)
21
- raise Error.new(full_command)
22
- end
21
+ result = run(verbose:, quiet:)
22
+ raise Error.new(full_command) unless result
23
+ result
23
24
  end
24
25
 
25
26
  def run verbose: false, quiet: false
@@ -100,7 +101,7 @@ module Bard
100
101
  end
101
102
  end
102
103
 
103
- cmd = "ssh #{ssh_opts.join(' ')} #{ssh_target} '#{cmd}'"
104
+ cmd = "ssh #{ssh_opts.join(' ')} #{ssh_target} #{Shellwords.shellescape(cmd)}"
104
105
 
105
106
  cmd += " 2>&1" if quiet
106
107
  cmd
data/lib/bard/config.rb CHANGED
@@ -52,7 +52,9 @@ module Bard
52
52
  # New v2.0 API - creates Target instances
53
53
  def target(key, &block)
54
54
  key = key.to_sym
55
- @servers[key] ||= Target.new(key, self)
55
+ unless @servers[key].is_a?(Target)
56
+ @servers[key] = Target.new(key, self)
57
+ end
56
58
  @servers[key].instance_eval(&block) if block
57
59
  @servers[key]
58
60
  end
@@ -5,7 +5,7 @@ class Bard::Provision::LogRotation < Bard::Provision
5
5
  print "Log Rotation:"
6
6
 
7
7
  provision_server.run! <<~SH, quiet: true
8
- file=/etc/logrotate.d/#{server.project_name}
8
+ file=/etc/logrotate.d/#{config.project_name}
9
9
  if [ ! -f $file ]; then
10
10
  sudo tee $file > /dev/null <<EOF
11
11
  $(pwd)/log/*.log {
@@ -9,7 +9,7 @@ class Bard::Provision::Passenger < Bard::Provision
9
9
  %(grep -qxF "RAILS_ENV=production" /etc/environment || echo "RAILS_ENV=production" | sudo tee -a /etc/environment),
10
10
  %(grep -qxF "EDITOR=vim" /etc/environment || echo "EDITOR=vim" | sudo tee -a /etc/environment),
11
11
  "sudo apt-get install -y vim dirmngr gnupg apt-transport-https ca-certificates",
12
- "curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key.txt | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/phusion.gpg >/dev/null",
12
+ "curl https://oss-binaries.phusionpassenger.com/auto-software-signing-gpg-key-2025.txt | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/phusion.gpg >/dev/null",
13
13
  %(echo "deb https://oss-binaries.phusionpassenger.com/apt/passenger jammy main" | sudo tee /etc/apt/sources.list.d/passenger.list),
14
14
  "sudo apt-get update -y",
15
15
  "sudo apt-get install -y nginx libnginx-mod-http-passenger",
@@ -30,7 +30,7 @@ class Bard::Provision::Passenger < Bard::Provision
30
30
  end
31
31
 
32
32
  def app_configured?
33
- provision_server.run "[ -f /etc/nginx/sites-enabled/#{server.project_name} ]", quiet: true
33
+ provision_server.run "[ -f /etc/nginx/sites-enabled/#{config.project_name} ]", quiet: true
34
34
  end
35
35
  end
36
36
 
@@ -16,7 +16,7 @@ class Bard::Provision::Repo < Bard::Provision
16
16
  print " Add public key to GitHub repo deploy keys,"
17
17
  title = "#{server.ssh_uri.user}@#{server.ssh_uri.host}"
18
18
  key = provision_server.run "cat ~/.ssh/id_rsa.pub", home: true
19
- Bard::Github.new(server.project_name).add_deploy_key title:, key:
19
+ Bard::Github.new(config.project_name).add_deploy_key title:, key:
20
20
  end
21
21
  print " Cloning repo,"
22
22
  provision_server.run! "git clone git@github.com:botandrosedesign/#{project_name}", home: true
@@ -50,7 +50,7 @@ class Bard::Provision::Repo < Bard::Provision
50
50
  end
51
51
 
52
52
  def project_name
53
- server.project_name
53
+ config.project_name
54
54
  end
55
55
 
56
56
  def on_latest_master?
@@ -19,7 +19,14 @@ class Bard::Provision::SSH < Bard::Provision
19
19
  add_ssh_known_host!(provision_server.ssh_uri)
20
20
  end
21
21
  print " Reconfiguring port to #{target_port},"
22
- provision_server.run! %(echo "Port #{target_port}" | sudo tee /etc/ssh/sshd_config.d/port_#{target_port}.conf; sudo service ssh restart), home: true
22
+ provision_server.run! %(echo "Port #{target_port}" | sudo tee /etc/ssh/sshd_config.d/port_#{target_port}.conf && sudo service ssh restart), home: true
23
+ 5.times do
24
+ sleep 1
25
+ break if ssh_available?(provision_server.ssh_uri, port: target_port)
26
+ end
27
+ if !ssh_available?(provision_server.ssh_uri, port: target_port)
28
+ raise "reconfigured SSH to port #{target_port} but it's not responding — check firewall and sshd_config Include directive"
29
+ end
23
30
  end
24
31
 
25
32
  if !ssh_known_host?(provision_server.ssh_uri)
@@ -65,7 +72,7 @@ class Bard::Provision::SSH < Bard::Provision
65
72
 
66
73
  def disable_password_auth!
67
74
  provision_server.run!(
68
- %q{echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/disable_password_auth.conf; sudo service ssh restart},
75
+ %q{echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/disable_password_auth.conf && sudo service ssh restart},
69
76
  home: true
70
77
  )
71
78
  end
@@ -4,7 +4,7 @@ class Bard::Provision::Swapfile < Bard::Provision
4
4
  def call
5
5
  print "Swapfile:"
6
6
 
7
- provision_server.run! <<~SH
7
+ provision_server.run! <<~SH, home: true
8
8
  if [ ! -f /swapfile ]; then
9
9
  sudo fallocate -l $(grep MemTotal /proc/meminfo | awk '{print $2}')K /swapfile
10
10
  fi
@@ -14,6 +14,8 @@ class Bard::Provision::Swapfile < Bard::Provision
14
14
  grep -q '/swapfile none swap sw 0 0' /etc/fstab || echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
15
15
  SH
16
16
 
17
+ provision_server.run! "sudo swapon --show | grep -q /swapfile", home: true
18
+
17
19
  puts " ✓"
18
20
  end
19
21
  end
data/lib/bard/server.rb CHANGED
@@ -135,8 +135,9 @@ module Bard
135
135
  key
136
136
  end
137
137
 
138
- def run! command, home: false, verbose: false, quiet: false
139
- Bard::Command.run! command, on: self, home:, verbose:, quiet:
138
+ def run! command, home: false, verbose: false, quiet: false, capture: false
139
+ result = Bard::Command.run!(command, on: self, home:, verbose:, quiet:)
140
+ result if capture
140
141
  end
141
142
 
142
143
  def run command, home: false, verbose: false, quiet: false
@@ -1,4 +1,5 @@
1
1
  require "uri"
2
+ require "shellwords"
2
3
  require "bard/command"
3
4
 
4
5
  module Bard
@@ -30,6 +31,12 @@ module Bard
30
31
  host
31
32
  end
32
33
 
34
+ def to_s
35
+ str = "#{user}@#{host}"
36
+ str += ":#{port}" if port && port != "22"
37
+ str
38
+ end
39
+
33
40
  def connection_string
34
41
  "#{user}@#{host}"
35
42
  end
@@ -93,7 +100,7 @@ module Bard
93
100
  remote_cmd += "cd #{path} && " if path
94
101
  remote_cmd += command
95
102
 
96
- cmd += " '#{remote_cmd}'"
103
+ cmd += " #{Shellwords.shellescape(remote_cmd)}"
97
104
  cmd
98
105
  end
99
106
  end
data/lib/bard/target.rb CHANGED
@@ -217,9 +217,10 @@ module Bard
217
217
  end
218
218
 
219
219
  # Remote command execution
220
- def run!(command, home: false, verbose: false, quiet: false)
220
+ def run!(command, home: false, verbose: false, quiet: false, capture: false)
221
221
  require_capability!(:ssh)
222
- Command.run!(command, on: self, home: home, verbose: verbose, quiet: quiet)
222
+ result = Command.run!(command, on: self, home: home, verbose: verbose, quiet: quiet)
223
+ result if capture
223
224
  end
224
225
 
225
226
  def run(command, home: false, verbose: false, quiet: false)
data/lib/bard/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Bard
2
- VERSION = "1.9.0"
2
+ VERSION = "1.9.2"
3
3
  end
4
4
 
@@ -55,6 +55,26 @@ describe Bard::CLI::Stage do
55
55
  end
56
56
  end
57
57
 
58
+ context "when staging target has a deploy strategy" do
59
+ let(:strategy_instance) { double("strategy") }
60
+ let(:staging_server) { double("staging", deploy_strategy: :fake) }
61
+
62
+ before do
63
+ allow(staging_server).to receive(:respond_to?).with(:deploy_strategy).and_return(true)
64
+ allow(staging_server).to receive(:deploy_strategy_instance).and_return(strategy_instance)
65
+ allow(cli).to receive(:require).with("bard/deploy_strategy/fake").and_return(true)
66
+ end
67
+
68
+ it "uses the deploy strategy instead of SSH" do
69
+ expect(cli).to receive(:run!).with("git push -u origin main", verbose: true)
70
+ expect(strategy_instance).to receive(:deploy)
71
+ expect(staging_server).not_to receive(:run!)
72
+ expect(cli).to receive(:ping).with(:staging)
73
+
74
+ cli.stage
75
+ end
76
+ end
77
+
58
78
  context "when production server is not defined" do
59
79
  let(:servers) { { staging: staging_server } }
60
80
 
@@ -1,5 +1,6 @@
1
1
  require "spec_helper"
2
2
  require "bard/command"
3
+ require "shellwords"
3
4
 
4
5
  describe Bard::Command do
5
6
  let(:remote) { double("remote", to_sym: :remote, ssh: true, env: nil, path: "/path/to", ssh_key: nil, ssh_uri: "user@example.com", gateway: nil) }
@@ -11,7 +12,8 @@ describe Bard::Command do
11
12
  end
12
13
 
13
14
  it "should run a command on a remote server" do
14
- expect(Open3).to receive(:capture3).with("ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR user@example.com 'cd /path/to && ls -l'").and_return(["output", "", 0])
15
+ expected_cmd = "ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR user@example.com #{Shellwords.shellescape("cd /path/to && ls -l")}"
16
+ expect(Open3).to receive(:capture3).with(expected_cmd).and_return(["output", "", 0])
15
17
  Bard::Command.run "ls -l", on: remote
16
18
  end
17
19
  end
@@ -148,6 +148,18 @@ describe Bard::Config do
148
148
  end
149
149
  end
150
150
 
151
+ context "with target overriding default server" do
152
+ subject { described_class.new("tracker", source: <<~SOURCE) }
153
+ target :staging do
154
+ ssh false
155
+ end
156
+ SOURCE
157
+
158
+ it "replaces the default Server with a Target" do
159
+ expect(subject[:staging]).to be_a(Bard::Target)
160
+ end
161
+ end
162
+
151
163
  context "with github_pages directive" do
152
164
  subject { described_class.new("test", source: "github_pages 'example.com'") }
153
165
 
@@ -4,7 +4,7 @@ require "bard/provision/logrotation"
4
4
 
5
5
  describe Bard::Provision::LogRotation do
6
6
  let(:server) { double("server", project_name: "test_app") }
7
- let(:config) { { production: server } }
7
+ let(:config) { double("config", project_name: "test_app", :[] => server) }
8
8
  let(:ssh_url) { "user@example.com" }
9
9
  let(:provision_server) { double("provision_server") }
10
10
  let(:logrotation) { Bard::Provision::LogRotation.new(config, ssh_url) }
@@ -4,7 +4,7 @@ require "bard/provision/passenger"
4
4
 
5
5
  describe Bard::Provision::Passenger do
6
6
  let(:server) { double("server", project_name: "test_app") }
7
- let(:config) { { production: server } }
7
+ let(:config) { double("config", project_name: "test_app", :[] => server) }
8
8
  let(:ssh_url) { "user@example.com" }
9
9
  let(:provision_server) { double("provision_server") }
10
10
  let(:passenger) { Bard::Provision::Passenger.new(config, ssh_url) }
@@ -5,7 +5,7 @@ require "bard/provision/repo"
5
5
  describe Bard::Provision::Repo do
6
6
  let(:ssh_uri) { double("ssh_uri", user: "deploy", host: "example.com") }
7
7
  let(:server) { double("server", ssh_uri: ssh_uri, project_name: "test_project") }
8
- let(:config) { { production: server } }
8
+ let(:config) { double("config", project_name: "test_project", :[] => server) }
9
9
  let(:ssh_url) { "deploy@example.com" }
10
10
  let(:provision_server) { double("provision_server") }
11
11
  let(:github_api) { double("github_api") }
@@ -45,26 +45,39 @@ describe Bard::Provision::SSH do
45
45
  context "when SSH is not available on target port but available on default port" do
46
46
  it "reconfigures SSH port and adds to known hosts" do
47
47
  allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
48
- allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false)
48
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false, true)
49
49
  allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(true)
50
50
  allow(ssh_provisioner).to receive(:ssh_known_host?).with(provision_ssh_uri).and_return(false)
51
+ allow(ssh_provisioner).to receive(:sleep)
51
52
 
52
53
  expect(ssh_provisioner).to receive(:add_ssh_known_host!).with(provision_ssh_uri).twice
53
54
  expect(provision_server).to receive(:run!).with(
54
- 'echo "Port 2222" | sudo tee /etc/ssh/sshd_config.d/port_2222.conf; sudo service ssh restart',
55
+ 'echo "Port 2222" | sudo tee /etc/ssh/sshd_config.d/port_2222.conf && sudo service ssh restart',
55
56
  home: true
56
57
  )
57
58
 
58
59
  ssh_provisioner.call
59
60
  end
60
61
 
61
- it "prints status messages during reconfiguration" do
62
+ it "raises if new port is not responding after reconfiguration" do
62
63
  allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
63
64
  allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false)
64
65
  allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(true)
66
+ allow(ssh_provisioner).to receive(:ssh_known_host?).with(provision_ssh_uri).and_return(true)
67
+ allow(provision_server).to receive(:run!)
68
+ allow(ssh_provisioner).to receive(:sleep)
69
+
70
+ expect { ssh_provisioner.call }.to raise_error(/reconfigured SSH to port 2222 but it's not responding/)
71
+ end
72
+
73
+ it "prints status messages during reconfiguration" do
74
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
75
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false, true)
76
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(true)
65
77
  allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(false)
66
78
  allow(ssh_provisioner).to receive(:add_ssh_known_host!)
67
79
  allow(provision_server).to receive(:run!)
80
+ allow(ssh_provisioner).to receive(:sleep)
68
81
 
69
82
  expect(ssh_provisioner).to receive(:print).with("SSH:")
70
83
  expect(ssh_provisioner).to receive(:print).with(" Adding known host,")
@@ -218,7 +231,7 @@ describe Bard::Provision::SSH do
218
231
  describe "#disable_password_auth!" do
219
232
  it "creates sshd config file to disable password authentication and restarts ssh" do
220
233
  expect(provision_server).to receive(:run!).with(
221
- %q{echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/disable_password_auth.conf; sudo service ssh restart},
234
+ %q{echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/disable_password_auth.conf && sudo service ssh restart},
222
235
  home: true
223
236
  )
224
237
 
@@ -16,7 +16,8 @@ describe Bard::Provision::Swapfile do
16
16
 
17
17
  describe "#call" do
18
18
  it "sets up swapfile on the server" do
19
- expect(provision_server).to receive(:run!).with(/if \[ ! -f \/swapfile \]/)
19
+ expect(provision_server).to receive(:run!).with(/if \[ ! -f \/swapfile \]/, home: true).ordered
20
+ expect(provision_server).to receive(:run!).with("sudo swapon --show | grep -q /swapfile", home: true).ordered
20
21
 
21
22
  swapfile.call
22
23
  end
@@ -81,7 +81,7 @@ describe Bard::SSHServer do
81
81
 
82
82
  it "executes command via SSH" do
83
83
  expect(Open3).to receive(:capture3)
84
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
84
+ .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
85
85
  .and_return(["output", "", 0])
86
86
 
87
87
  server.run("ls")
@@ -94,7 +94,7 @@ describe Bard::SSHServer do
94
94
  )
95
95
 
96
96
  expect(Open3).to receive(:capture3)
97
- .with(/RAILS_ENV=production/)
97
+ .with(/RAILS_ENV.*production/)
98
98
  .and_return(["output", "", 0])
99
99
 
100
100
  server_with_env.run("ls")
@@ -108,7 +108,7 @@ describe Bard::SSHServer do
108
108
 
109
109
  it "executes command via SSH" do
110
110
  expect(Open3).to receive(:capture3)
111
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
111
+ .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
112
112
  .and_return(["output", "", 0])
113
113
 
114
114
  server.run!("ls")
@@ -129,7 +129,7 @@ describe Bard::SSHServer do
129
129
 
130
130
  it "replaces current process with SSH command" do
131
131
  expect(server).to receive(:exec)
132
- .with(/ssh.*deploy@example.com.*cd \/app && ls/)
132
+ .with(/ssh.*deploy@example.com.*cd.+\/app.+ls/)
133
133
 
134
134
  server.exec!("ls")
135
135
  end
@@ -140,7 +140,7 @@ describe Bard::SSHServer do
140
140
  server = described_class.new("deploy@example.com:22", path: "/var/www/app")
141
141
 
142
142
  expect(Open3).to receive(:capture3)
143
- .with(/cd \/var\/www\/app && ls/)
143
+ .with(/cd.+\/var\/www\/app.+ls/)
144
144
  .and_return(["output", "", 0])
145
145
 
146
146
  server.run("ls")
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.9.0
4
+ version: 1.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-10 00:00:00.000000000 Z
10
+ date: 2026-02-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor