bard 1.4.8 → 1.4.9

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -0
  3. data/Gemfile +4 -0
  4. data/bard.gemspec +3 -2
  5. data/lib/bard/git.rb +5 -1
  6. data/lib/bard/provision/repo.rb +21 -0
  7. data/lib/bard/provision/ssh.rb +21 -0
  8. data/lib/bard/server.rb +2 -2
  9. data/lib/bard/version.rb +1 -1
  10. data/spec/bard/cli/ci_spec.rb +139 -0
  11. data/spec/bard/cli/command_spec.rb +50 -0
  12. data/spec/bard/cli/data_spec.rb +77 -0
  13. data/spec/bard/cli/deploy_spec.rb +202 -0
  14. data/spec/bard/cli/hurt_spec.rb +23 -0
  15. data/spec/bard/cli/install_spec.rb +25 -0
  16. data/spec/bard/cli/master_key_spec.rb +52 -0
  17. data/spec/bard/cli/new_spec.rb +60 -0
  18. data/spec/bard/cli/open_spec.rb +67 -0
  19. data/spec/bard/cli/ping_spec.rb +47 -0
  20. data/spec/bard/cli/provision_spec.rb +42 -0
  21. data/spec/bard/cli/run_spec.rb +51 -0
  22. data/spec/bard/cli/setup_spec.rb +76 -0
  23. data/spec/bard/cli/ssh_spec.rb +55 -0
  24. data/spec/bard/cli/stage_spec.rb +77 -0
  25. data/spec/bard/cli/vim_spec.rb +34 -0
  26. data/spec/bard/command_spec.rb +37 -0
  27. data/spec/bard/copy_spec.rb +33 -0
  28. data/spec/bard/git_spec.rb +61 -0
  29. data/spec/bard/github_pages_spec.rb +80 -0
  30. data/spec/bard/github_spec.rb +45 -0
  31. data/spec/bard/ping_spec.rb +31 -0
  32. data/spec/bard/provision/app_spec.rb +33 -0
  33. data/spec/bard/provision/apt_spec.rb +39 -0
  34. data/spec/bard/provision/authorizedkeys_spec.rb +40 -0
  35. data/spec/bard/provision/data_spec.rb +54 -0
  36. data/spec/bard/provision/deploy_spec.rb +33 -0
  37. data/spec/bard/provision/http_spec.rb +57 -0
  38. data/spec/bard/provision/logrotation_spec.rb +34 -0
  39. data/spec/bard/provision/masterkey_spec.rb +63 -0
  40. data/spec/bard/provision/mysql_spec.rb +55 -0
  41. data/spec/bard/provision/passenger_spec.rb +81 -0
  42. data/spec/bard/provision/repo_spec.rb +208 -0
  43. data/spec/bard/provision/rvm_spec.rb +49 -0
  44. data/spec/bard/provision/ssh_spec.rb +229 -0
  45. data/spec/bard/provision/swapfile_spec.rb +32 -0
  46. data/spec/bard/provision/user_spec.rb +103 -0
  47. data/spec/bard/provision_spec.rb +28 -0
  48. data/spec/spec_helper.rb +6 -1
  49. metadata +96 -9
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+ require "bard/provision"
3
+ require "bard/provision/passenger"
4
+
5
+ describe Bard::Provision::Passenger do
6
+ let(:server) { double("server", project_name: "test_app") }
7
+ let(:config) { { production: server } }
8
+ let(:ssh_url) { "user@example.com" }
9
+ let(:provision_server) { double("provision_server") }
10
+ let(:passenger) { Bard::Provision::Passenger.new(config, ssh_url) }
11
+
12
+ before do
13
+ allow(passenger).to receive(:server).and_return(server)
14
+ allow(passenger).to receive(:provision_server).and_return(provision_server)
15
+ allow(provision_server).to receive_message_chain(:ssh_uri, :host).and_return("192.168.1.100")
16
+ allow(passenger).to receive(:print)
17
+ allow(passenger).to receive(:puts)
18
+ allow(passenger).to receive(:system)
19
+ end
20
+
21
+ describe "#call" do
22
+ context "when HTTP is not responding" do
23
+ it "installs nginx and passenger" do
24
+ allow(passenger).to receive(:http_responding?).and_return(false)
25
+ allow(passenger).to receive(:app_configured?).and_return(true)
26
+
27
+ expect(provision_server).to receive(:run!).with(/grep -qxF.*RAILS_ENV/, home: true)
28
+
29
+ passenger.call
30
+ end
31
+ end
32
+
33
+ context "when app is not configured" do
34
+ it "creates nginx config" do
35
+ allow(passenger).to receive(:http_responding?).and_return(true)
36
+ allow(passenger).to receive(:app_configured?).and_return(false)
37
+
38
+ expect(provision_server).to receive(:run!).with("bard setup")
39
+
40
+ passenger.call
41
+ end
42
+ end
43
+
44
+ context "when everything is already set up" do
45
+ it "skips installation and configuration" do
46
+ allow(passenger).to receive(:http_responding?).and_return(true)
47
+ allow(passenger).to receive(:app_configured?).and_return(true)
48
+
49
+ expect(provision_server).not_to receive(:run!)
50
+
51
+ passenger.call
52
+ end
53
+ end
54
+
55
+ it "prints status messages" do
56
+ allow(passenger).to receive(:http_responding?).and_return(true)
57
+ allow(passenger).to receive(:app_configured?).and_return(true)
58
+
59
+ expect(passenger).to receive(:print).with("Passenger:")
60
+ expect(passenger).to receive(:puts).with(" ✓")
61
+
62
+ passenger.call
63
+ end
64
+ end
65
+
66
+ describe "#http_responding?" do
67
+ it "checks if port 80 is responding" do
68
+ expect(passenger).to receive(:system).with("nc -zv 192.168.1.100 80 2>/dev/null")
69
+
70
+ passenger.http_responding?
71
+ end
72
+ end
73
+
74
+ describe "#app_configured?" do
75
+ it "checks if nginx config exists for the app" do
76
+ expect(provision_server).to receive(:run).with("[ -f /etc/nginx/sites-enabled/test_app ]", quiet: true)
77
+
78
+ passenger.app_configured?
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,208 @@
1
+ require "spec_helper"
2
+ require "bard/provision"
3
+ require "bard/provision/repo"
4
+
5
+ describe Bard::Provision::Repo do
6
+ let(:ssh_uri) { double("ssh_uri", user: "deploy", host: "example.com") }
7
+ let(:server) { double("server", ssh_uri: ssh_uri, project_name: "test_project") }
8
+ let(:config) { { production: server } }
9
+ let(:ssh_url) { "deploy@example.com" }
10
+ let(:provision_server) { double("provision_server") }
11
+ let(:github_api) { double("github_api") }
12
+ let(:repo_provisioner) { Bard::Provision::Repo.new(config, ssh_url) }
13
+
14
+ before do
15
+ allow(repo_provisioner).to receive(:server).and_return(server)
16
+ allow(repo_provisioner).to receive(:provision_server).and_return(provision_server)
17
+ allow(repo_provisioner).to receive(:print)
18
+ allow(repo_provisioner).to receive(:puts)
19
+ allow(Bard::Github).to receive(:new).and_return(github_api)
20
+ end
21
+
22
+ describe "#call" do
23
+ context "when repository is already cloned" do
24
+ context "when not on latest master" do
25
+ it "updates to latest master" do
26
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(true)
27
+ allow(repo_provisioner).to receive(:on_latest_master?).and_return(false)
28
+
29
+ expect(repo_provisioner).to receive(:update_to_latest_master!)
30
+ expect(github_api).not_to receive(:add_deploy_key)
31
+
32
+ repo_provisioner.call
33
+ end
34
+
35
+ it "prints status message when updating to latest master" do
36
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(true)
37
+ allow(repo_provisioner).to receive(:on_latest_master?).and_return(false)
38
+ allow(repo_provisioner).to receive(:update_to_latest_master!)
39
+
40
+ expect(repo_provisioner).to receive(:print).with("Repo:")
41
+ expect(repo_provisioner).to receive(:print).with(" Updating to latest master,")
42
+ expect(repo_provisioner).to receive(:puts).with(" ✓")
43
+
44
+ repo_provisioner.call
45
+ end
46
+ end
47
+
48
+ context "when already on latest master" do
49
+ it "skips update" do
50
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(true)
51
+ allow(repo_provisioner).to receive(:on_latest_master?).and_return(true)
52
+
53
+ expect(repo_provisioner).not_to receive(:update_to_latest_master!)
54
+ expect(github_api).not_to receive(:add_deploy_key)
55
+
56
+ repo_provisioner.call
57
+ end
58
+
59
+ it "only prints repo header and checkbox" do
60
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(true)
61
+ allow(repo_provisioner).to receive(:on_latest_master?).and_return(true)
62
+
63
+ expect(repo_provisioner).to receive(:print).with("Repo:")
64
+ expect(repo_provisioner).not_to receive(:print).with(" Updating to latest master,")
65
+ expect(repo_provisioner).to receive(:puts).with(" ✓")
66
+
67
+ repo_provisioner.call
68
+ end
69
+ end
70
+ end
71
+
72
+ context "when repository is not cloned but can be cloned" do
73
+ it "clones the repository directly" do
74
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(false)
75
+ allow(repo_provisioner).to receive(:can_clone_project?).and_return(true)
76
+
77
+ expect(provision_server).to receive(:run!).with("git clone git@github.com:botandrosedesign/test_project", home: true)
78
+ expect(github_api).not_to receive(:add_deploy_key)
79
+
80
+ repo_provisioner.call
81
+ end
82
+ end
83
+
84
+ context "when repository cannot be cloned and SSH keypair exists" do
85
+ it "adds deploy key and clones repository" do
86
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(false)
87
+ allow(repo_provisioner).to receive(:can_clone_project?).and_return(false)
88
+ allow(repo_provisioner).to receive(:ssh_keypair?).and_return(true)
89
+ allow(provision_server).to receive(:run).with("cat ~/.ssh/id_rsa.pub", home: true).and_return("ssh-rsa AAAAB3...")
90
+
91
+ expect(github_api).to receive(:add_deploy_key).with(title: "deploy@example.com", key: "ssh-rsa AAAAB3...")
92
+ expect(provision_server).to receive(:run!).with("git clone git@github.com:botandrosedesign/test_project", home: true)
93
+
94
+ repo_provisioner.call
95
+ end
96
+ end
97
+
98
+ context "when repository cannot be cloned and no SSH keypair exists" do
99
+ it "generates keypair, adds deploy key, and clones repository" do
100
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(false)
101
+ allow(repo_provisioner).to receive(:can_clone_project?).and_return(false)
102
+ allow(repo_provisioner).to receive(:ssh_keypair?).and_return(false)
103
+ allow(provision_server).to receive(:run).with("cat ~/.ssh/id_rsa.pub", home: true).and_return("ssh-rsa AAAAB3...")
104
+
105
+ expect(provision_server).to receive(:run!).with('ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""', home: true)
106
+ expect(github_api).to receive(:add_deploy_key).with(title: "deploy@example.com", key: "ssh-rsa AAAAB3...")
107
+ expect(provision_server).to receive(:run!).with("git clone git@github.com:botandrosedesign/test_project", home: true)
108
+
109
+ repo_provisioner.call
110
+ end
111
+
112
+ it "prints status messages during setup" do
113
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(false)
114
+ allow(repo_provisioner).to receive(:can_clone_project?).and_return(false)
115
+ allow(repo_provisioner).to receive(:ssh_keypair?).and_return(false)
116
+ allow(provision_server).to receive(:run).and_return("ssh-rsa AAAAB3...")
117
+ allow(provision_server).to receive(:run!)
118
+ allow(github_api).to receive(:add_deploy_key)
119
+
120
+ expect(repo_provisioner).to receive(:print).with("Repo:")
121
+ expect(repo_provisioner).to receive(:print).with(" Generating keypair in ~/.ssh,")
122
+ expect(repo_provisioner).to receive(:print).with(" Add public key to GitHub repo deploy keys,")
123
+ expect(repo_provisioner).to receive(:print).with(" Cloning repo,")
124
+ expect(repo_provisioner).to receive(:puts).with(" ✓")
125
+
126
+ repo_provisioner.call
127
+ end
128
+ end
129
+
130
+ it "always prints success message" do
131
+ allow(repo_provisioner).to receive(:already_cloned?).and_return(true)
132
+ allow(repo_provisioner).to receive(:on_latest_master?).and_return(true)
133
+
134
+ expect(repo_provisioner).to receive(:print).with("Repo:")
135
+ expect(repo_provisioner).to receive(:puts).with(" ✓")
136
+
137
+ repo_provisioner.call
138
+ end
139
+ end
140
+
141
+ describe "private methods" do
142
+ describe "#ssh_keypair?" do
143
+ it "checks if SSH public key exists" do
144
+ expect(provision_server).to receive(:run).with("[ -f ~/.ssh/id_rsa.pub ]", home: true, quiet: true)
145
+
146
+ repo_provisioner.send(:ssh_keypair?)
147
+ end
148
+ end
149
+
150
+ describe "#already_cloned?" do
151
+ it "checks if git directory exists" do
152
+ expect(provision_server).to receive(:run).with("[ -d ~/test_project/.git ]", home: true, quiet: true)
153
+
154
+ repo_provisioner.send(:already_cloned?)
155
+ end
156
+ end
157
+
158
+ describe "#can_clone_project?" do
159
+ it "tests if repository can be cloned" do
160
+ expected_commands = [
161
+ "needle=$(ssh-keyscan -t ed25519 github.com 2>/dev/null | cut -d \" \" -f 2-3)",
162
+ "grep -q \"$needle\" ~/.ssh/known_hosts || ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2>/dev/null",
163
+ "git ls-remote git@github.com:botandrosedesign/test_project"
164
+ ].join("; ")
165
+
166
+ expect(provision_server).to receive(:run).with(expected_commands, home: true, quiet: true)
167
+
168
+ repo_provisioner.send(:can_clone_project?)
169
+ end
170
+ end
171
+
172
+ describe "#project_name" do
173
+ it "returns the server's project name" do
174
+ expect(repo_provisioner.send(:project_name)).to eq("test_project")
175
+ end
176
+ end
177
+
178
+ describe "#on_latest_master?" do
179
+ it "checks if current HEAD matches origin/master after fetching" do
180
+ expected_command = "cd ~/test_project && git fetch origin && [ $(git rev-parse HEAD) = $(git rev-parse origin/master) ]"
181
+ expect(provision_server).to receive(:run).with(expected_command, home: true, quiet: true)
182
+
183
+ repo_provisioner.send(:on_latest_master?)
184
+ end
185
+
186
+ it "returns true when on latest master" do
187
+ allow(provision_server).to receive(:run).and_return(true)
188
+
189
+ expect(repo_provisioner.send(:on_latest_master?)).to be true
190
+ end
191
+
192
+ it "returns false when not on latest master" do
193
+ allow(provision_server).to receive(:run).and_return(false)
194
+
195
+ expect(repo_provisioner.send(:on_latest_master?)).to be false
196
+ end
197
+ end
198
+
199
+ describe "#update_to_latest_master!" do
200
+ it "checks out master and resets to origin/master" do
201
+ expected_command = "cd ~/test_project && git checkout master && git reset --hard origin/master"
202
+ expect(provision_server).to receive(:run!).with(expected_command, home: true)
203
+
204
+ repo_provisioner.send(:update_to_latest_master!)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+ require "bard/provision"
3
+ require "bard/provision/rvm"
4
+
5
+ describe Bard::Provision::RVM do
6
+ let(:config) { { production: double("production") } }
7
+ let(:ssh_url) { "user@example.com" }
8
+ let(:provision_server) { double("provision_server") }
9
+ let(:rvm) { Bard::Provision::RVM.new(config, ssh_url) }
10
+
11
+ before do
12
+ allow(rvm).to receive(:provision_server).and_return(provision_server)
13
+ allow(rvm).to receive(:print)
14
+ allow(rvm).to receive(:puts)
15
+ allow(File).to receive(:read).with(".ruby-version").and_return("3.2.0\n")
16
+ end
17
+
18
+ describe "#call" do
19
+ context "when RVM is not installed" do
20
+ it "installs RVM and Ruby" do
21
+ allow(provision_server).to receive(:run).with("[ -d ~/.rvm ]", quiet: true).and_return(false)
22
+
23
+ expect(provision_server).to receive(:run!).with(/sed -i.*bashrc/)
24
+ expect(provision_server).to receive(:run!).with("rvm install 3.2.0")
25
+
26
+ rvm.call
27
+ end
28
+ end
29
+
30
+ context "when RVM is already installed" do
31
+ it "skips installation" do
32
+ allow(provision_server).to receive(:run).with("[ -d ~/.rvm ]", quiet: true).and_return(true)
33
+
34
+ expect(provision_server).not_to receive(:run!)
35
+
36
+ rvm.call
37
+ end
38
+ end
39
+
40
+ it "prints status messages" do
41
+ allow(provision_server).to receive(:run).and_return(true)
42
+
43
+ expect(rvm).to receive(:print).with("RVM:")
44
+ expect(rvm).to receive(:puts).with(" ✓")
45
+
46
+ rvm.call
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,229 @@
1
+ require "spec_helper"
2
+ require "bard/provision"
3
+ require "bard/provision/ssh"
4
+
5
+ describe Bard::Provision::SSH do
6
+ let(:ssh_uri) { double("ssh_uri", host: "example.com", port: 2222) }
7
+ let(:provision_ssh_uri) { double("provision_ssh_uri", host: "example.com", port: nil) }
8
+ let(:server) { double("server", ssh_uri: ssh_uri) }
9
+ let(:config) { { production: server } }
10
+ let(:ssh_url) { "user@example.com" }
11
+ let(:provision_server) { double("provision_server", ssh_uri: provision_ssh_uri) }
12
+ let(:ssh_provisioner) { Bard::Provision::SSH.new(config, ssh_url) }
13
+
14
+ before do
15
+ allow(ssh_provisioner).to receive(:server).and_return(server)
16
+ allow(ssh_provisioner).to receive(:provision_server).and_return(provision_server)
17
+ allow(ssh_provisioner).to receive(:print)
18
+ allow(ssh_provisioner).to receive(:puts)
19
+ allow(ssh_provisioner).to receive(:system)
20
+ end
21
+
22
+ describe "#call" do
23
+ context "when SSH is already available on target port" do
24
+ it "skips port reconfiguration but checks known hosts" do
25
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
26
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
27
+ allow(ssh_provisioner).to receive(:ssh_known_host?).with(provision_ssh_uri).and_return(true)
28
+
29
+ expect(provision_server).not_to receive(:run!)
30
+
31
+ ssh_provisioner.call
32
+ end
33
+
34
+ it "adds to known hosts if not present" do
35
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
36
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
37
+ allow(ssh_provisioner).to receive(:ssh_known_host?).with(provision_ssh_uri).and_return(false)
38
+
39
+ expect(ssh_provisioner).to receive(:add_ssh_known_host!).with(provision_ssh_uri)
40
+
41
+ ssh_provisioner.call
42
+ end
43
+ end
44
+
45
+ context "when SSH is not available on target port but available on default port" do
46
+ it "reconfigures SSH port and adds to known hosts" do
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)
49
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(true)
50
+ allow(ssh_provisioner).to receive(:ssh_known_host?).with(provision_ssh_uri).and_return(false)
51
+
52
+ expect(ssh_provisioner).to receive(:add_ssh_known_host!).with(provision_ssh_uri).twice
53
+ 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
+ home: true
56
+ )
57
+
58
+ ssh_provisioner.call
59
+ end
60
+
61
+ it "prints status messages during reconfiguration" do
62
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
63
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false)
64
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(true)
65
+ allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(false)
66
+ allow(ssh_provisioner).to receive(:add_ssh_known_host!)
67
+ allow(provision_server).to receive(:run!)
68
+
69
+ expect(ssh_provisioner).to receive(:print).with("SSH:")
70
+ expect(ssh_provisioner).to receive(:print).with(" Adding known host,")
71
+ expect(ssh_provisioner).to receive(:print).with(" Reconfiguring port to 2222,")
72
+ expect(ssh_provisioner).to receive(:print).with(" Adding known host,")
73
+ expect(ssh_provisioner).to receive(:puts).with(" ✓")
74
+
75
+ ssh_provisioner.call
76
+ end
77
+ end
78
+
79
+ context "when SSH is not available on either port" do
80
+ it "raises an error" do
81
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
82
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(false)
83
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri).and_return(false)
84
+
85
+ expect { ssh_provisioner.call }.to raise_error("can't find SSH on port 2222 or 22")
86
+ end
87
+ end
88
+
89
+ context "when password authentication is enabled" do
90
+ it "disables password authentication" do
91
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(true)
92
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
93
+ allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(true)
94
+
95
+ expect(ssh_provisioner).to receive(:disable_password_auth!)
96
+
97
+ ssh_provisioner.call
98
+ end
99
+
100
+ it "prints status message when disabling password authentication" do
101
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(true)
102
+ allow(ssh_provisioner).to receive(:disable_password_auth!)
103
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
104
+ allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(true)
105
+
106
+ expect(ssh_provisioner).to receive(:print).with("SSH:")
107
+ expect(ssh_provisioner).to receive(:print).with(" Disabling password authentication,")
108
+ expect(ssh_provisioner).to receive(:puts).with(" ✓")
109
+
110
+ ssh_provisioner.call
111
+ end
112
+ end
113
+
114
+ context "when password authentication is already disabled" do
115
+ it "skips disabling password authentication" do
116
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
117
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
118
+ allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(true)
119
+
120
+ expect(ssh_provisioner).not_to receive(:disable_password_auth!)
121
+
122
+ ssh_provisioner.call
123
+ end
124
+ end
125
+
126
+ it "updates the SSH URL with the target port" do
127
+ allow(ssh_provisioner).to receive(:password_auth_enabled?).and_return(false)
128
+ allow(ssh_provisioner).to receive(:ssh_available?).with(provision_ssh_uri, port: 2222).and_return(true)
129
+ allow(ssh_provisioner).to receive(:ssh_known_host?).and_return(true)
130
+
131
+ ssh_provisioner.call
132
+ end
133
+ end
134
+
135
+ describe "private methods" do
136
+ describe "#target_port" do
137
+ it "returns the server's SSH port" do
138
+ expect(ssh_provisioner.send(:target_port)).to eq(2222)
139
+ end
140
+
141
+ it "defaults to 22 if no port is specified" do
142
+ allow(ssh_uri).to receive(:port).and_return(nil)
143
+ expect(ssh_provisioner.send(:target_port)).to eq(22)
144
+ end
145
+ end
146
+
147
+ describe "#ssh_available?" do
148
+ it "tests SSH connectivity on specified port" do
149
+ expect(ssh_provisioner).to receive(:system).with("nc -zv example.com 2222 2>/dev/null")
150
+
151
+ ssh_provisioner.send(:ssh_available?, provision_ssh_uri, port: 2222)
152
+ end
153
+
154
+ it "uses SSH URI port if no port specified" do
155
+ allow(provision_ssh_uri).to receive(:port).and_return(2222)
156
+ expect(ssh_provisioner).to receive(:system).with("nc -zv example.com 2222 2>/dev/null")
157
+
158
+ ssh_provisioner.send(:ssh_available?, provision_ssh_uri)
159
+ end
160
+
161
+ it "defaults to port 22 if no port in URI or parameter" do
162
+ expect(ssh_provisioner).to receive(:system).with("nc -zv example.com 22 2>/dev/null")
163
+
164
+ ssh_provisioner.send(:ssh_available?, provision_ssh_uri)
165
+ end
166
+ end
167
+
168
+ describe "#ssh_known_host?" do
169
+ it "checks if host is in known_hosts file" do
170
+ expected_command = 'grep -q "$(ssh-keyscan -t ed25519 -p22 example.com 2>/dev/null | cut -d \' \' -f 2-3)" ~/.ssh/known_hosts'
171
+ expect(ssh_provisioner).to receive(:system).with(expected_command)
172
+
173
+ ssh_provisioner.send(:ssh_known_host?, provision_ssh_uri)
174
+ end
175
+ end
176
+
177
+ describe "#add_ssh_known_host!" do
178
+ it "adds host to known_hosts file" do
179
+ expected_command = "ssh-keyscan -p22 -H example.com >> ~/.ssh/known_hosts 2>/dev/null"
180
+ expect(ssh_provisioner).to receive(:system).with(expected_command)
181
+
182
+ ssh_provisioner.send(:add_ssh_known_host!, provision_ssh_uri)
183
+ end
184
+ end
185
+
186
+ describe "#password_auth_enabled?" do
187
+ it "returns true when password authentication is enabled" do
188
+ expect(provision_server).to receive(:run!).with(
189
+ %q{grep -E '^\s*PasswordAuthentication\s+yes' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true},
190
+ home: true,
191
+ capture: true
192
+ ).and_return("PasswordAuthentication yes\n")
193
+
194
+ expect(ssh_provisioner.send(:password_auth_enabled?)).to be true
195
+ end
196
+
197
+ it "returns false when password authentication is disabled" do
198
+ expect(provision_server).to receive(:run!).with(
199
+ %q{grep -E '^\s*PasswordAuthentication\s+yes' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true},
200
+ home: true,
201
+ capture: true
202
+ ).and_return("")
203
+
204
+ expect(ssh_provisioner.send(:password_auth_enabled?)).to be false
205
+ end
206
+
207
+ it "returns false when grep returns nil" do
208
+ expect(provision_server).to receive(:run!).with(
209
+ %q{grep -E '^\s*PasswordAuthentication\s+yes' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true},
210
+ home: true,
211
+ capture: true
212
+ ).and_return(nil)
213
+
214
+ expect(ssh_provisioner.send(:password_auth_enabled?)).to be false
215
+ end
216
+ end
217
+
218
+ describe "#disable_password_auth!" do
219
+ it "creates sshd config file to disable password authentication and restarts ssh" do
220
+ 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},
222
+ home: true
223
+ )
224
+
225
+ ssh_provisioner.send(:disable_password_auth!)
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+ require "bard/provision"
3
+ require "bard/provision/swapfile"
4
+
5
+ describe Bard::Provision::Swapfile do
6
+ let(:config) { { production: double("production") } }
7
+ let(:ssh_url) { "user@example.com" }
8
+ let(:provision_server) { double("provision_server") }
9
+ let(:swapfile) { Bard::Provision::Swapfile.new(config, ssh_url) }
10
+
11
+ before do
12
+ allow(swapfile).to receive(:provision_server).and_return(provision_server)
13
+ allow(swapfile).to receive(:print)
14
+ allow(swapfile).to receive(:puts)
15
+ end
16
+
17
+ describe "#call" do
18
+ it "sets up swapfile on the server" do
19
+ expect(provision_server).to receive(:run!).with(/if \[ ! -f \/swapfile \]/)
20
+
21
+ swapfile.call
22
+ end
23
+
24
+ it "prints status messages" do
25
+ allow(provision_server).to receive(:run!)
26
+ expect(swapfile).to receive(:print).with("Swapfile:")
27
+ expect(swapfile).to receive(:puts).with(" ✓")
28
+
29
+ swapfile.call
30
+ end
31
+ end
32
+ end