bard 2.0.0.beta → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -1
- data/CLAUDE.md +76 -0
- data/MIGRATION_GUIDE.md +24 -9
- data/PLUGINS.md +99 -0
- data/README.md +14 -6
- data/Rakefile +3 -1
- data/bard.gemspec +2 -1
- data/cucumber.yml +1 -0
- data/features/ci.feature +63 -0
- data/features/data.feature +13 -0
- data/features/deploy.feature +14 -0
- data/features/deploy_git_workflow.feature +89 -0
- data/features/run.feature +14 -0
- data/features/step_definitions/bard_steps.rb +136 -0
- data/features/support/bard-coverage +16 -0
- data/features/support/env.rb +14 -39
- data/features/support/test_server.rb +216 -0
- data/lib/bard/cli.rb +14 -31
- data/lib/bard/command.rb +10 -69
- data/lib/bard/config.rb +40 -183
- data/lib/bard/copy.rb +28 -103
- data/lib/bard/plugins/data.rb +56 -0
- data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
- data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
- data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
- data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
- data/lib/bard/plugins/deploy/ci.rb +38 -0
- data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
- data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
- data/lib/bard/plugins/deploy.rb +240 -0
- data/lib/bard/{git.rb → plugins/git.rb} +6 -3
- data/lib/bard/{github.rb → plugins/github.rb} +4 -6
- data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +13 -6
- data/lib/bard/plugins/github_pages.rb +30 -0
- data/lib/bard/plugins/hurt.rb +13 -0
- data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
- data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
- data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
- data/lib/bard/plugins/install.rb +9 -0
- data/lib/bard/plugins/open.rb +20 -0
- data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
- data/lib/bard/plugins/ping/target_methods.rb +23 -0
- data/lib/bard/plugins/ping.rb +10 -0
- data/lib/bard/plugins/run.rb +19 -0
- data/lib/bard/plugins/setup.rb +54 -0
- data/lib/bard/plugins/ssh/connection.rb +75 -0
- data/lib/bard/plugins/ssh/copy.rb +95 -0
- data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
- data/lib/bard/plugins/ssh/target_methods.rb +20 -0
- data/lib/bard/plugins/ssh.rb +10 -0
- data/lib/bard/plugins/url/target_methods.rb +23 -0
- data/lib/bard/plugins/url.rb +1 -0
- data/lib/bard/plugins/vim.rb +6 -0
- data/lib/bard/retryable.rb +25 -0
- data/lib/bard/secrets.rb +10 -0
- data/lib/bard/target.rb +27 -185
- data/lib/bard/version.rb +1 -1
- data/lib/bard.rb +1 -3
- data/spec/acceptance/docker/Dockerfile +3 -2
- data/spec/bard/capability_spec.rb +8 -50
- data/spec/bard/ci/github_actions_spec.rb +117 -14
- data/spec/bard/ci/jenkins_spec.rb +139 -0
- data/spec/bard/ci/runner_spec.rb +61 -0
- data/spec/bard/ci_spec.rb +1 -1
- data/spec/bard/cli/ci_spec.rb +34 -27
- data/spec/bard/cli/data_spec.rb +7 -26
- data/spec/bard/cli/deploy_spec.rb +87 -46
- data/spec/bard/cli/hurt_spec.rb +3 -9
- data/spec/bard/cli/install_spec.rb +5 -11
- data/spec/bard/cli/master_key_spec.rb +5 -19
- data/spec/bard/cli/open_spec.rb +14 -30
- data/spec/bard/cli/ping_spec.rb +8 -23
- data/spec/bard/cli/run_spec.rb +27 -21
- data/spec/bard/cli/setup_spec.rb +10 -27
- data/spec/bard/cli/ssh_spec.rb +10 -25
- data/spec/bard/cli/stage_spec.rb +28 -23
- data/spec/bard/cli/vim_spec.rb +3 -9
- data/spec/bard/command_spec.rb +1 -8
- data/spec/bard/config_spec.rb +78 -98
- data/spec/bard/copy_spec.rb +54 -18
- data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
- data/spec/bard/deploy_strategy_spec.rb +1 -1
- data/spec/bard/dynamic_dsl_spec.rb +18 -98
- data/spec/bard/git_spec.rb +9 -5
- data/spec/bard/github_spec.rb +2 -2
- data/spec/bard/ping_spec.rb +5 -5
- data/spec/bard/ssh_copy_spec.rb +44 -0
- data/spec/bard/ssh_server_spec.rb +8 -101
- data/spec/bard/target_spec.rb +66 -109
- data/spec/spec_helper.rb +6 -1
- metadata +79 -143
- data/README.rdoc +0 -15
- data/features/bard_check.feature +0 -94
- data/features/bard_deploy.feature +0 -18
- data/features/bard_pull.feature +0 -112
- data/features/bard_push.feature +0 -112
- data/features/podman_testcontainers.feature +0 -16
- data/features/step_definitions/check_steps.rb +0 -47
- data/features/step_definitions/git_steps.rb +0 -73
- data/features/step_definitions/global_steps.rb +0 -56
- data/features/step_definitions/podman_steps.rb +0 -23
- data/features/step_definitions/rails_steps.rb +0 -44
- data/features/step_definitions/submodule_steps.rb +0 -110
- data/features/support/grit_ext.rb +0 -13
- data/features/support/io.rb +0 -32
- data/features/support/podman.rb +0 -153
- data/lib/bard/ci/jenkins.rb +0 -105
- data/lib/bard/ci/retryable.rb +0 -27
- data/lib/bard/ci.rb +0 -50
- data/lib/bard/cli/ci.rb +0 -66
- data/lib/bard/cli/command.rb +0 -26
- data/lib/bard/cli/data.rb +0 -45
- data/lib/bard/cli/deploy.rb +0 -85
- data/lib/bard/cli/hurt.rb +0 -20
- data/lib/bard/cli/install.rb +0 -16
- data/lib/bard/cli/master_key.rb +0 -17
- data/lib/bard/cli/new.rb +0 -101
- data/lib/bard/cli/new_rails_template.rb +0 -197
- data/lib/bard/cli/open.rb +0 -22
- data/lib/bard/cli/ping.rb +0 -18
- data/lib/bard/cli/provision.rb +0 -34
- data/lib/bard/cli/run.rb +0 -24
- data/lib/bard/cli/setup.rb +0 -56
- data/lib/bard/cli/ssh.rb +0 -14
- data/lib/bard/cli/stage.rb +0 -27
- data/lib/bard/cli/vim.rb +0 -13
- data/lib/bard/default_config.rb +0 -35
- data/lib/bard/deploy_strategy/ssh.rb +0 -19
- data/lib/bard/github_pages.rb +0 -134
- data/lib/bard/provision/app.rb +0 -10
- data/lib/bard/provision/apt.rb +0 -16
- data/lib/bard/provision/authorizedkeys.rb +0 -25
- data/lib/bard/provision/data.rb +0 -27
- data/lib/bard/provision/deploy.rb +0 -10
- data/lib/bard/provision/http.rb +0 -16
- data/lib/bard/provision/logrotation.rb +0 -30
- data/lib/bard/provision/masterkey.rb +0 -18
- data/lib/bard/provision/mysql.rb +0 -22
- data/lib/bard/provision/passenger.rb +0 -37
- data/lib/bard/provision/repo.rb +0 -72
- data/lib/bard/provision/rvm.rb +0 -22
- data/lib/bard/provision/ssh.rb +0 -72
- data/lib/bard/provision/swapfile.rb +0 -21
- data/lib/bard/provision/user.rb +0 -42
- data/lib/bard/provision.rb +0 -16
- data/lib/bard/server.rb +0 -117
- data/spec/bard/cli/command_spec.rb +0 -50
- data/spec/bard/cli/new_spec.rb +0 -73
- data/spec/bard/cli/provision_spec.rb +0 -42
- data/spec/bard/github_pages_spec.rb +0 -143
- data/spec/bard/provision/app_spec.rb +0 -33
- data/spec/bard/provision/apt_spec.rb +0 -39
- data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
- data/spec/bard/provision/data_spec.rb +0 -54
- data/spec/bard/provision/deploy_spec.rb +0 -33
- data/spec/bard/provision/http_spec.rb +0 -57
- data/spec/bard/provision/logrotation_spec.rb +0 -34
- data/spec/bard/provision/masterkey_spec.rb +0 -63
- data/spec/bard/provision/mysql_spec.rb +0 -55
- data/spec/bard/provision/passenger_spec.rb +0 -81
- data/spec/bard/provision/repo_spec.rb +0 -208
- data/spec/bard/provision/rvm_spec.rb +0 -49
- data/spec/bard/provision/ssh_spec.rb +0 -229
- data/spec/bard/provision/swapfile_spec.rb +0 -32
- data/spec/bard/provision/user_spec.rb +0 -103
- data/spec/bard/provision_spec.rb +0 -28
- data/spec/bard/server_spec.rb +0 -127
- /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/ci +0 -0
- /data/{install_files → lib/bard/plugins/install}/setup +0 -0
- /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
FROM ubuntu:
|
|
1
|
+
FROM ubuntu:24.04
|
|
2
2
|
|
|
3
3
|
# Prevent interactive prompts
|
|
4
4
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
5
5
|
|
|
6
|
-
# Install SSH server and basic tools
|
|
6
|
+
# Install SSH server, git, and basic tools
|
|
7
7
|
RUN apt-get update && \
|
|
8
8
|
apt-get install -y \
|
|
9
9
|
openssh-server \
|
|
10
|
+
git \
|
|
10
11
|
sudo \
|
|
11
12
|
&& rm -rf /var/lib/apt/lists/*
|
|
12
13
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
require "bard/target"
|
|
3
|
+
require "bard/plugins/ssh/target_methods"
|
|
4
|
+
require "bard/plugins/ping/target_methods"
|
|
3
5
|
|
|
4
6
|
describe "Capability System" do
|
|
5
7
|
let(:config) { double("config", project_name: "testapp") }
|
|
@@ -13,9 +15,9 @@ describe "Capability System" do
|
|
|
13
15
|
|
|
14
16
|
it "can enable multiple capabilities" do
|
|
15
17
|
target.enable_capability(:ssh)
|
|
16
|
-
target.enable_capability(:
|
|
18
|
+
target.enable_capability(:url)
|
|
17
19
|
expect(target.has_capability?(:ssh)).to be true
|
|
18
|
-
expect(target.has_capability?(:
|
|
20
|
+
expect(target.has_capability?(:url)).to be true
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
@@ -38,59 +40,15 @@ describe "Capability System" do
|
|
|
38
40
|
|
|
39
41
|
it "raises an error if capability is not enabled" do
|
|
40
42
|
expect { target.require_capability!(:ssh) }
|
|
41
|
-
.to raise_error(/
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it "provides custom error message for ping capability" do
|
|
45
|
-
expect { target.require_capability!(:ping) }
|
|
46
|
-
.to raise_error(/Ping URL not configured for this target/)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it "provides generic error message for unknown capabilities" do
|
|
50
|
-
expect { target.require_capability!(:unknown) }
|
|
51
|
-
.to raise_error(/unknown capability not configured for this target/)
|
|
43
|
+
.to raise_error(/ssh capability not configured for this target/)
|
|
52
44
|
end
|
|
53
45
|
end
|
|
54
46
|
|
|
55
47
|
describe "capability dependency checking" do
|
|
56
|
-
context "
|
|
57
|
-
it "
|
|
58
|
-
expect { target.run!("ls") }
|
|
59
|
-
.to raise_error(/SSH not configured/)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "run requires SSH capability" do
|
|
63
|
-
expect { target.run("ls") }
|
|
64
|
-
.to raise_error(/SSH not configured/)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "exec! requires SSH capability" do
|
|
68
|
-
expect { target.exec!("ls") }
|
|
69
|
-
.to raise_error(/SSH not configured/)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it "copy_file requires SSH capability" do
|
|
73
|
-
other_target = Bard::Target.new(:staging, config)
|
|
74
|
-
expect { target.copy_file("test.txt", to: other_target) }
|
|
75
|
-
.to raise_error(/SSH not configured/)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "copy_dir requires SSH capability" do
|
|
79
|
-
other_target = Bard::Target.new(:staging, config)
|
|
80
|
-
expect { target.copy_dir("test/", to: other_target) }
|
|
81
|
-
.to raise_error(/SSH not configured/)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
context "Ping-dependent methods" do
|
|
86
|
-
it "ping! requires ping capability" do
|
|
48
|
+
context "URL-dependent methods" do
|
|
49
|
+
it "ping! requires url capability" do
|
|
87
50
|
expect { target.ping! }
|
|
88
|
-
.to raise_error(/
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it "open requires ping capability" do
|
|
92
|
-
expect { target.open }
|
|
93
|
-
.to raise_error(/Ping URL not configured/)
|
|
51
|
+
.to raise_error(/url capability not configured/)
|
|
94
52
|
end
|
|
95
53
|
end
|
|
96
54
|
end
|
|
@@ -1,35 +1,138 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "bard/plugins/deploy/ci/github_actions"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
BASE_URL = "https://api.github.com/repos/botandrosedesign/metrc"
|
|
5
|
+
|
|
6
|
+
RSpec.shared_context "github actions stubs" do
|
|
7
|
+
let(:run_id) { 123 }
|
|
8
|
+
let(:job_id) { 456 }
|
|
9
|
+
let(:started_at) { "2024-01-15T10:00:00Z" }
|
|
10
|
+
let(:completed_at) { "2024-01-15T10:01:30Z" }
|
|
11
|
+
let(:sha) { "abc123" }
|
|
12
|
+
|
|
13
|
+
let(:run_json) do
|
|
14
|
+
{
|
|
15
|
+
"id" => run_id,
|
|
16
|
+
"status" => "completed",
|
|
17
|
+
"conclusion" => "success",
|
|
18
|
+
"head_branch" => "master",
|
|
19
|
+
"head_sha" => sha,
|
|
20
|
+
"run_started_at" => started_at,
|
|
21
|
+
"updated_at" => completed_at,
|
|
22
|
+
}
|
|
23
|
+
end
|
|
5
24
|
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
let(:job_json) do
|
|
26
|
+
{
|
|
27
|
+
"id" => job_id,
|
|
28
|
+
"started_at" => started_at,
|
|
29
|
+
"completed_at" => completed_at,
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
before do
|
|
34
|
+
allow(Bard::Secrets).to receive(:fetch).with("github-apikey").and_return("test-key")
|
|
8
35
|
end
|
|
9
36
|
end
|
|
10
37
|
|
|
11
38
|
describe Bard::CI::GithubActions::API do
|
|
39
|
+
include_context "github actions stubs"
|
|
40
|
+
|
|
12
41
|
subject { described_class.new("metrc") }
|
|
13
42
|
|
|
14
43
|
describe "#last_successful_run" do
|
|
15
|
-
|
|
44
|
+
before do
|
|
45
|
+
stub_request(:get, "#{BASE_URL}/actions/runs")
|
|
46
|
+
.with(query: hash_including("status" => "success"))
|
|
47
|
+
.to_return(
|
|
48
|
+
headers: { "Content-Type" => "application/json" },
|
|
49
|
+
body: JSON.dump("workflow_runs" => [run_json]),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
stub_request(:get, "#{BASE_URL}/actions/runs/#{run_id}/jobs")
|
|
53
|
+
.with(query: hash_including("filter" => "latest"))
|
|
54
|
+
.to_return(
|
|
55
|
+
headers: { "Content-Type" => "application/json" },
|
|
56
|
+
body: JSON.dump("jobs" => [job_json]),
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "has #time_elapsed" do
|
|
16
61
|
run = subject.last_successful_run
|
|
17
|
-
run.time_elapsed
|
|
62
|
+
expect(run.time_elapsed).to eq 90
|
|
18
63
|
end
|
|
19
64
|
|
|
20
|
-
|
|
21
|
-
|
|
65
|
+
it "has #console" do
|
|
66
|
+
stub_request(:get, "#{BASE_URL}/actions/jobs/#{job_id}/logs")
|
|
67
|
+
.to_return(
|
|
68
|
+
headers: { "Content-Type" => "text/plain" },
|
|
69
|
+
body: "build log output here",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
expect(subject.last_successful_run.console).to eq "build log output here"
|
|
22
73
|
end
|
|
23
74
|
end
|
|
24
75
|
|
|
25
76
|
describe "#create_run!" do
|
|
26
|
-
|
|
27
|
-
|
|
77
|
+
it "returns a run" do
|
|
78
|
+
stub_request(:post, "#{BASE_URL}/actions/workflows/ci.yml/dispatches")
|
|
79
|
+
.to_return(status: 204, body: "")
|
|
80
|
+
|
|
81
|
+
allow(subject).to receive(:`).with("git rev-parse master").and_return("#{sha}\n")
|
|
82
|
+
|
|
83
|
+
stub_request(:get, "#{BASE_URL}/actions/runs")
|
|
84
|
+
.with(query: hash_including("head_sha" => sha))
|
|
85
|
+
.to_return(
|
|
86
|
+
headers: { "Content-Type" => "application/json" },
|
|
87
|
+
body: JSON.dump("workflow_runs" => [run_json]),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
run = subject.create_run!("master")
|
|
91
|
+
expect(run).to be_a Bard::CI::GithubActions::Run
|
|
92
|
+
expect(run.id).to eq run_id
|
|
28
93
|
end
|
|
29
94
|
end
|
|
30
95
|
end
|
|
31
96
|
|
|
32
|
-
describe Bard::
|
|
33
|
-
|
|
34
|
-
|
|
97
|
+
describe Bard::CI::GithubActions do
|
|
98
|
+
include_context "github actions stubs"
|
|
99
|
+
|
|
100
|
+
subject { described_class.new("metrc", "master", sha) }
|
|
101
|
+
|
|
102
|
+
it "returns true on successful run" do
|
|
103
|
+
stub_request(:post, "#{BASE_URL}/actions/workflows/ci.yml/dispatches")
|
|
104
|
+
.to_return(status: 204, body: "")
|
|
105
|
+
|
|
106
|
+
allow_any_instance_of(Bard::CI::GithubActions::API)
|
|
107
|
+
.to receive(:`).with("git rev-parse master").and_return("#{sha}\n")
|
|
35
108
|
|
|
109
|
+
stub_request(:get, "#{BASE_URL}/actions/runs")
|
|
110
|
+
.with(query: hash_including("head_sha" => sha))
|
|
111
|
+
.to_return(
|
|
112
|
+
headers: { "Content-Type" => "application/json" },
|
|
113
|
+
body: JSON.dump("workflow_runs" => [run_json]),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
stub_request(:get, "#{BASE_URL}/actions/runs")
|
|
117
|
+
.with(query: hash_including("status" => "success"))
|
|
118
|
+
.to_return(
|
|
119
|
+
headers: { "Content-Type" => "application/json" },
|
|
120
|
+
body: JSON.dump("workflow_runs" => [run_json]),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
stub_request(:get, "#{BASE_URL}/actions/runs/#{run_id}/jobs")
|
|
124
|
+
.with(query: hash_including("filter" => "latest"))
|
|
125
|
+
.to_return(
|
|
126
|
+
headers: { "Content-Type" => "application/json" },
|
|
127
|
+
body: JSON.dump("jobs" => [job_json]),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
stub_request(:get, "#{BASE_URL}/actions/runs/#{run_id}")
|
|
131
|
+
.to_return(
|
|
132
|
+
headers: { "Content-Type" => "application/json" },
|
|
133
|
+
body: JSON.dump(run_json),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
expect(subject.run { }).to eq true
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "bard/plugins/deploy/ci/jenkins"
|
|
3
|
+
|
|
4
|
+
RSpec.describe Bard::CI::Jenkins do
|
|
5
|
+
let(:jenkins) { described_class.new("test-project", "master", "abc123") }
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
allow(Bard::Secrets).to receive(:fetch).with("jenkins-user").and_return("micah")
|
|
9
|
+
allow(Bard::Secrets).to receive(:fetch).with("jenkins-token").and_return("fake-token")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#get_last_time_elapsed" do
|
|
13
|
+
it "returns the duration in seconds from the last stable build" do
|
|
14
|
+
xml = "<build><duration>120000</duration></build>"
|
|
15
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/lastStableBuild/api/xml").and_return(xml)
|
|
16
|
+
|
|
17
|
+
result = jenkins.send(:get_last_time_elapsed)
|
|
18
|
+
expect(result).to eq 120
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#run" do
|
|
23
|
+
let(:ci_url) { "http://micah:fake-token@ci.botandrose.com/job/test-project" }
|
|
24
|
+
|
|
25
|
+
before do
|
|
26
|
+
allow(jenkins).to receive(:sleep)
|
|
27
|
+
state = instance_double(Bard::CI::State, save: nil, delete: nil)
|
|
28
|
+
allow(jenkins).to receive(:state).and_return(state)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "waits until the build has started before polling" do
|
|
32
|
+
allow(jenkins).to receive(:`).with("curl -s -I -X POST -L '#{ci_url}/buildWithParameters?GIT_REF=master'").and_return("Location: http://ci.botandrose.com/queue/item/99/\r\n")
|
|
33
|
+
allow(jenkins).to receive(:`).with("curl -s #{ci_url}/lastStableBuild/api/xml").and_return("<build><duration>60000</duration></build>")
|
|
34
|
+
allow(jenkins).to receive(:`).with("curl -s -g '#{ci_url}/api/json?depth=1&tree=builds[queueId,number]'").and_return(
|
|
35
|
+
'{"builds":[{"queueId":1,"number":1}]}',
|
|
36
|
+
'{"builds":[{"queueId":99,"number":5}]}',
|
|
37
|
+
'{"builds":[{"queueId":99,"number":5}]}'
|
|
38
|
+
)
|
|
39
|
+
allow(jenkins).to receive(:`).with("curl -s #{ci_url}/5/api/json?tree=building,result").and_return('{"building":false,"result":"SUCCESS"}')
|
|
40
|
+
|
|
41
|
+
result = jenkins.run { |elapsed, last_time| }
|
|
42
|
+
expect(result).to eq true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#started?" do
|
|
47
|
+
before do
|
|
48
|
+
jenkins.instance_variable_set(:@queueId, 99)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "retries when builds list is empty (job just created)" do
|
|
52
|
+
allow(jenkins).to receive(:`).with(/api\/json\?depth=1/).and_return(
|
|
53
|
+
'{"builds":[]}',
|
|
54
|
+
'{"builds":[{"queueId":99,"number":1}]}'
|
|
55
|
+
)
|
|
56
|
+
allow(jenkins).to receive(:sleep)
|
|
57
|
+
|
|
58
|
+
expect(jenkins.send(:started?)).to eq true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#exists?" do
|
|
63
|
+
it "returns truthy when the job exists" do
|
|
64
|
+
allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 200 OK\r\n")
|
|
65
|
+
|
|
66
|
+
expect(jenkins.exists?).to be_truthy
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "auto-creates the job when it does not exist" do
|
|
70
|
+
allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 404 Not Found\r\n")
|
|
71
|
+
allow(jenkins).to receive(:`).with("git remote get-url origin").and_return("git@gitlab.com:botandrose/test-project.git\n")
|
|
72
|
+
allow(File).to receive(:exist?).with("config/master.key").and_return(false)
|
|
73
|
+
expect(jenkins).to receive(:`).with(/curl -s -X POST.*createItem\?name=test-project.*Content-Type: application\/xml/)
|
|
74
|
+
|
|
75
|
+
jenkins.exists?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "includes a master key build step when config/master.key exists" do
|
|
79
|
+
allow(jenkins).to receive(:`).with(/curl -s -I/).and_return("HTTP/1.1 404 Not Found\r\n")
|
|
80
|
+
allow(jenkins).to receive(:`).with("git remote get-url origin").and_return("git@gitlab.com:botandrose/test-project.git\n")
|
|
81
|
+
allow(File).to receive(:exist?).with("config/master.key").and_return(true)
|
|
82
|
+
allow(File).to receive(:read).with("config/master.key").and_return("abc123secret")
|
|
83
|
+
|
|
84
|
+
config_xml = nil
|
|
85
|
+
allow(jenkins).to receive(:`).with(/createItem/) do |cmd|
|
|
86
|
+
config_xml = cmd
|
|
87
|
+
""
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
jenkins.exists?
|
|
91
|
+
expect(config_xml).to include("abc123secret")
|
|
92
|
+
expect(config_xml).to include("config/master.key")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe "#building? and #success?" do
|
|
97
|
+
before do
|
|
98
|
+
jenkins.instance_variable_set(:@job_id, 42)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "detects a successful build" do
|
|
102
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":false,"result":"SUCCESS"}')
|
|
103
|
+
|
|
104
|
+
expect(jenkins.send(:building?)).to eq false
|
|
105
|
+
expect(jenkins.send(:success?)).to eq true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "detects a failed build" do
|
|
109
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":false,"result":"FAILURE"}')
|
|
110
|
+
|
|
111
|
+
expect(jenkins.send(:building?)).to eq false
|
|
112
|
+
expect(jenkins.send(:success?)).to eq false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "detects a build in progress" do
|
|
116
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"building":true,"result":null}')
|
|
117
|
+
|
|
118
|
+
expect(jenkins.send(:building?)).to eq true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "handles JSON with spaces in keys" do
|
|
122
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return('{"_class":"hudson.model.FreeStyleBuild","building":false,"result":"SUCCESS"}')
|
|
123
|
+
|
|
124
|
+
expect(jenkins.send(:building?)).to eq false
|
|
125
|
+
expect(jenkins.send(:success?)).to eq true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "success? reflects the last response from building?" do
|
|
129
|
+
allow(jenkins).to receive(:`).with("curl -s http://micah:fake-token@ci.botandrose.com/job/test-project/42/api/json?tree=building,result").and_return(
|
|
130
|
+
'{"building":true,"result":null}',
|
|
131
|
+
'{"building":false,"result":"SUCCESS"}'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
jenkins.send(:building?) # first call — still building
|
|
135
|
+
jenkins.send(:building?) # second call — done
|
|
136
|
+
expect(jenkins.send(:success?)).to eq true
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "bard/plugins/deploy/ci/runner"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Bard::CI::Runner do
|
|
4
|
+
describe ".runners" do
|
|
5
|
+
it "is a hash registry" do
|
|
6
|
+
expect(described_class.runners).to be_a(Hash)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe ".[]" do
|
|
11
|
+
before do
|
|
12
|
+
require "bard/plugins/deploy/ci/local"
|
|
13
|
+
require "bard/plugins/deploy/ci/github_actions"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "looks up runners by name" do
|
|
17
|
+
expect(described_class[:local]).to eq Bard::CI::Local
|
|
18
|
+
expect(described_class[:github_actions]).to eq Bard::CI::GithubActions
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "returns nil for unknown runners" do
|
|
22
|
+
expect(described_class[:nonexistent]).to be_nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe ".default" do
|
|
27
|
+
it "returns the last registered runner" do
|
|
28
|
+
# Whatever was registered last in the current test run
|
|
29
|
+
expect(described_class.default).to be_a(Class)
|
|
30
|
+
expect(described_class.default.ancestors).to include(Bard::CI::Runner)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "auto-registration via inherited" do
|
|
35
|
+
it "registers subclasses automatically" do
|
|
36
|
+
eval <<-RUBY
|
|
37
|
+
module Bard
|
|
38
|
+
class CI
|
|
39
|
+
class SpecTestRunner < Runner
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
RUBY
|
|
44
|
+
|
|
45
|
+
expect(described_class[:spec_test_runner]).to eq Bard::CI::SpecTestRunner
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "newly registered runner becomes the default" do
|
|
49
|
+
eval <<-RUBY
|
|
50
|
+
module Bard
|
|
51
|
+
class CI
|
|
52
|
+
class AnotherTestRunner < Runner
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
RUBY
|
|
57
|
+
|
|
58
|
+
expect(described_class.default).to eq Bard::CI::AnotherTestRunner
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/spec/bard/ci_spec.rb
CHANGED
data/spec/bard/cli/ci_spec.rb
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
require "bard/cli"
|
|
3
|
-
require "
|
|
4
|
-
require "thor"
|
|
3
|
+
require "ostruct"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
attr_reader :options
|
|
10
|
-
|
|
11
|
-
def initialize
|
|
12
|
-
super
|
|
13
|
-
@options = {}
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def project_name
|
|
17
|
-
"test_project"
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
describe Bard::CLI::CI do
|
|
22
|
-
let(:cli) { TestCICLI.new }
|
|
5
|
+
describe "bard ci" do
|
|
6
|
+
let(:cli) { Bard::CLI.new }
|
|
23
7
|
let(:ci_runner) { double("ci_runner") }
|
|
24
8
|
|
|
25
9
|
before do
|
|
10
|
+
allow(cli).to receive(:config).and_return(OpenStruct.new(ci: nil))
|
|
11
|
+
allow(cli).to receive(:project_name).and_return("test_project")
|
|
26
12
|
allow(cli).to receive(:puts)
|
|
27
13
|
allow(cli).to receive(:print)
|
|
28
14
|
allow(cli).to receive(:exit)
|
|
@@ -101,12 +87,9 @@ describe Bard::CLI::CI do
|
|
|
101
87
|
it "shows error message and exits" do
|
|
102
88
|
allow(cli).to receive(:options).and_return({})
|
|
103
89
|
allow(ci_runner).to receive(:exists?).and_return(false)
|
|
90
|
+
allow(cli).to receive(:exit).with(1).and_raise(SystemExit)
|
|
104
91
|
|
|
105
|
-
expect
|
|
106
|
-
expect(cli).to receive(:puts) # "Re-run with --skip-ci to bypass CI..."
|
|
107
|
-
expect(cli).to receive(:exit).with(1)
|
|
108
|
-
|
|
109
|
-
cli.ci
|
|
92
|
+
expect { cli.ci }.to raise_error(SystemExit)
|
|
110
93
|
end
|
|
111
94
|
end
|
|
112
95
|
|
|
@@ -116,7 +99,7 @@ describe Bard::CLI::CI do
|
|
|
116
99
|
allow(ci_runner).to receive(:exists?).and_return(true)
|
|
117
100
|
allow(ci_runner).to receive(:run).and_return(true)
|
|
118
101
|
|
|
119
|
-
expect(Bard::CI).to receive(:new).with("test_project", "develop",
|
|
102
|
+
expect(Bard::CI).to receive(:new).with("test_project", "develop", runner_name: nil)
|
|
120
103
|
expect(cli).to receive(:puts).with("Continuous integration: starting build on develop...")
|
|
121
104
|
|
|
122
105
|
cli.ci("develop")
|
|
@@ -124,12 +107,36 @@ describe Bard::CLI::CI do
|
|
|
124
107
|
end
|
|
125
108
|
|
|
126
109
|
context "with local-ci option" do
|
|
127
|
-
it "passes local
|
|
110
|
+
it "passes local runner_name to CI" do
|
|
128
111
|
allow(cli).to receive(:options).and_return({ "local-ci" => true })
|
|
129
112
|
allow(ci_runner).to receive(:exists?).and_return(true)
|
|
130
113
|
allow(ci_runner).to receive(:run).and_return(true)
|
|
131
114
|
|
|
132
|
-
expect(Bard::CI).to receive(:new).with("test_project", "feature-branch",
|
|
115
|
+
expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :local)
|
|
116
|
+
|
|
117
|
+
cli.ci
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context "with ci option" do
|
|
122
|
+
it "passes specified runner_name to CI" do
|
|
123
|
+
allow(cli).to receive(:options).and_return({ "ci" => "jenkins" })
|
|
124
|
+
allow(ci_runner).to receive(:exists?).and_return(true)
|
|
125
|
+
allow(ci_runner).to receive(:run).and_return(true)
|
|
126
|
+
|
|
127
|
+
expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :jenkins)
|
|
128
|
+
|
|
129
|
+
cli.ci
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context "with both local-ci and ci options" do
|
|
134
|
+
it "local-ci takes precedence" do
|
|
135
|
+
allow(cli).to receive(:options).and_return({ "local-ci" => true, "ci" => "jenkins" })
|
|
136
|
+
allow(ci_runner).to receive(:exists?).and_return(true)
|
|
137
|
+
allow(ci_runner).to receive(:run).and_return(true)
|
|
138
|
+
|
|
139
|
+
expect(Bard::CI).to receive(:new).with("test_project", "feature-branch", runner_name: :local)
|
|
133
140
|
|
|
134
141
|
cli.ci
|
|
135
142
|
end
|
data/spec/bard/cli/data_spec.rb
CHANGED
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
require "bard/cli"
|
|
3
|
-
require "bard/cli/data"
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
require "term/ansicolor"
|
|
8
|
-
|
|
9
|
-
class TestCLI < Thor
|
|
10
|
-
include Bard::CLI::Data
|
|
11
|
-
include Term::ANSIColor
|
|
12
|
-
|
|
13
|
-
attr_reader :config
|
|
14
|
-
|
|
15
|
-
def initialize
|
|
16
|
-
@config = {}
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def options
|
|
20
|
-
{}
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
describe Bard::CLI::Data do
|
|
25
|
-
let(:cli) { TestCLI.new }
|
|
4
|
+
describe "bard data" do
|
|
5
|
+
let(:cli) { Bard::CLI.new }
|
|
26
6
|
|
|
27
7
|
it "should have a data command" do
|
|
28
8
|
expect(cli).to respond_to(:data)
|
|
29
9
|
end
|
|
30
10
|
|
|
31
11
|
context "data" do
|
|
32
|
-
let(:from) { double("from", key: :production, run!: nil,
|
|
12
|
+
let(:from) { double("from", key: :production, run!: nil, require_capability!: nil) }
|
|
33
13
|
let(:to) { double("to", key: :local, run!: nil) }
|
|
34
14
|
|
|
35
15
|
let(:config) do
|
|
@@ -42,17 +22,18 @@ describe Bard::CLI::Data do
|
|
|
42
22
|
before do
|
|
43
23
|
allow(cli).to receive(:config).and_return(config)
|
|
44
24
|
allow(cli).to receive(:options).and_return({from: "production", to: "local"})
|
|
25
|
+
allow(cli).to receive(:puts)
|
|
45
26
|
end
|
|
46
27
|
|
|
47
28
|
it "should run the data command" do
|
|
48
29
|
expect(from).to receive(:run!).with("bin/rake db:dump")
|
|
49
|
-
expect(
|
|
30
|
+
expect(Bard::Copy).to receive(:file).with("db/data.sql.gz", from: from, to: to, verbose: true)
|
|
50
31
|
expect(to).to receive(:run!).with("bin/rake db:load")
|
|
51
32
|
cli.data
|
|
52
33
|
end
|
|
53
34
|
|
|
54
35
|
context "pushing to production" do
|
|
55
|
-
let(:to) { double("to", key: :production,
|
|
36
|
+
let(:to) { double("to", key: :production, url: "https://example.com", run!: nil, require_capability!: nil) }
|
|
56
37
|
|
|
57
38
|
before do
|
|
58
39
|
allow(cli).to receive(:options).and_return({from: "local", to: "production"})
|
|
@@ -68,7 +49,7 @@ describe Bard::CLI::Data do
|
|
|
68
49
|
it "should allow pushing to production if the user confirms" do
|
|
69
50
|
expect(cli).to receive(:ask).and_return("https://example.com")
|
|
70
51
|
expect(from).to receive(:run!).with("bin/rake db:dump")
|
|
71
|
-
expect(
|
|
52
|
+
expect(Bard::Copy).to receive(:file).with("db/data.sql.gz", from: from, to: to, verbose: true)
|
|
72
53
|
expect(to).to receive(:run!).with("bin/rake db:load")
|
|
73
54
|
cli.data
|
|
74
55
|
end
|