bard 1.9.6 → 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/README.md +6 -14
- data/README.rdoc +15 -0
- 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 -14
- 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/ci/github_actions.rb +2 -1
- data/lib/bard/ci/jenkins.rb +11 -82
- data/lib/bard/ci/local.rb +6 -6
- data/lib/bard/ci/runner.rb +1 -35
- data/lib/bard/ci.rb +23 -11
- data/lib/bard/cli/ci.rb +38 -45
- data/lib/bard/cli/deploy.rb +9 -40
- data/lib/bard/cli/hurt.rb +15 -10
- data/lib/bard/cli/install.rb +12 -7
- data/lib/bard/cli/open.rb +16 -12
- data/lib/bard/cli/ping.rb +14 -8
- data/lib/bard/cli/provision.rb +1 -1
- data/lib/bard/cli/run.rb +3 -5
- data/lib/bard/cli/stage.rb +1 -9
- data/lib/bard/cli/vim.rb +10 -5
- data/lib/bard/cli.rb +11 -13
- data/lib/bard/command.rb +13 -33
- data/lib/bard/config.rb +14 -10
- data/lib/bard/copy.rb +33 -12
- data/lib/bard/deploy_strategy/github_pages.rb +2 -10
- data/lib/bard/deploy_strategy.rb +0 -3
- data/lib/bard/github.rb +4 -2
- data/lib/bard/provision/apt.rb +1 -1
- data/lib/bard/provision/logrotation.rb +1 -1
- data/lib/bard/provision/mysql.rb +2 -2
- data/lib/bard/provision/passenger.rb +2 -2
- data/lib/bard/provision/repo.rb +2 -2
- data/lib/bard/provision/ssh.rb +2 -9
- data/lib/bard/provision/swapfile.rb +1 -3
- data/lib/bard/server.rb +3 -46
- data/lib/bard/ssh_server.rb +2 -9
- data/lib/bard/target.rb +16 -67
- data/lib/bard/version.rb +1 -1
- data/spec/acceptance/docker/Dockerfile +1 -2
- data/spec/bard/ci/github_actions_spec.rb +13 -116
- data/spec/bard/cli/ci_spec.rb +8 -34
- data/spec/bard/cli/deploy_spec.rb +8 -46
- data/spec/bard/cli/hurt_spec.rb +2 -2
- data/spec/bard/cli/install_spec.rb +4 -4
- data/spec/bard/cli/open_spec.rb +8 -10
- data/spec/bard/cli/ping_spec.rb +5 -5
- data/spec/bard/cli/run_spec.rb +1 -20
- data/spec/bard/cli/stage_spec.rb +0 -20
- data/spec/bard/cli/vim_spec.rb +5 -5
- data/spec/bard/command_spec.rb +1 -3
- data/spec/bard/config_spec.rb +0 -28
- data/spec/bard/copy_spec.rb +3 -3
- data/spec/bard/github_spec.rb +1 -1
- data/spec/bard/provision/apt_spec.rb +1 -1
- data/spec/bard/provision/logrotation_spec.rb +1 -1
- data/spec/bard/provision/passenger_spec.rb +1 -1
- data/spec/bard/provision/repo_spec.rb +1 -1
- data/spec/bard/provision/ssh_spec.rb +4 -17
- data/spec/bard/provision/swapfile_spec.rb +1 -2
- data/spec/bard/ssh_server_spec.rb +8 -12
- data/spec/bard/target_spec.rb +5 -9
- data/spec/spec_helper.rb +1 -6
- metadata +31 -41
- data/CLAUDE.md +0 -76
- data/PLUGINS.md +0 -114
- data/cucumber.yml +0 -1
- data/features/ci.feature +0 -62
- data/features/data.feature +0 -12
- data/features/deploy.feature +0 -13
- data/features/deploy_git_workflow.feature +0 -88
- data/features/run.feature +0 -13
- data/features/step_definitions/bard_steps.rb +0 -135
- data/features/support/bard-coverage +0 -16
- data/features/support/test_server.rb +0 -216
- data/lib/bard/deprecation.rb +0 -19
- data/lib/bard/plugin.rb +0 -100
- data/lib/bard/plugins/backup.rb +0 -19
- data/lib/bard/plugins/github_pages.rb +0 -34
- data/lib/bard/plugins/hurt.rb +0 -5
- data/lib/bard/plugins/install.rb +0 -5
- data/lib/bard/plugins/jenkins.rb +0 -6
- data/lib/bard/plugins/new.rb +0 -5
- data/lib/bard/plugins/ping.rb +0 -6
- data/lib/bard/plugins/provision.rb +0 -5
- data/lib/bard/plugins/vim.rb +0 -5
- data/lib/bard/secrets.rb +0 -10
- data/spec/bard/ci/jenkins_spec.rb +0 -139
- data/spec/bard/ci/runner_spec.rb +0 -61
- data/spec/bard/deprecation_spec.rb +0 -281
- data/spec/bard/plugin_spec.rb +0 -79
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Given /^a shared rails project$/ do
|
|
2
|
+
# TEARDOWN
|
|
3
|
+
Dir.foreach "#{ROOT}/tmp" do |file|
|
|
4
|
+
FileUtils.rm_rf("#{ROOT}/tmp/#{file}") unless %w(fixtures . ..).include? file
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# SETUP
|
|
8
|
+
Dir.chdir ROOT
|
|
9
|
+
`cp -r tmp/fixtures/* tmp/`
|
|
10
|
+
|
|
11
|
+
Dir.chdir 'tmp'
|
|
12
|
+
@repos = {}
|
|
13
|
+
%w(development_a development_b staging production).each do |env|
|
|
14
|
+
@repos[env] = Grit::Repo.new env
|
|
15
|
+
end
|
|
16
|
+
Dir.chdir 'development_a'
|
|
17
|
+
@repo = @repos['development_a']
|
|
18
|
+
@env = { 'RAILS_ENV' => 'development', 'TESTING' => true }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Given /^I am in a subdirectory$/ do
|
|
22
|
+
FileUtils.mkdir "test_subdirectory"
|
|
23
|
+
Dir.chdir "test_subdirectory"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
When /^I type "([^\"]*)"$/ do |command|
|
|
27
|
+
type command.sub /\b(bard)\b/, "#{ROOT}/bin/bard"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
When /^on (\w+), (.*$)/ do |env, step|
|
|
31
|
+
old_env = @env['RAILS_ENV']
|
|
32
|
+
@env['RAILS_ENV'] = env if %w(staging production).include? env
|
|
33
|
+
Dir.chdir "#{ROOT}/tmp/#{env}" do
|
|
34
|
+
old_repo = @repo
|
|
35
|
+
@repo = @repos[env]
|
|
36
|
+
When step
|
|
37
|
+
@repo = old_repo
|
|
38
|
+
end
|
|
39
|
+
@env['RAILS_ENV'] = old_env
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Then /^I should see the fatal error "([^\"]*)"$/ do |error_message|
|
|
43
|
+
@stderr.should include(error_message)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Then /^I should see the warning "([^\"]*)"$/ do |warning_message|
|
|
47
|
+
@stderr.should include(warning_message)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Then /^I should see "([^\"]*)"$/ do |message|
|
|
51
|
+
@stdout.should include(message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Then /^debug$/ do
|
|
55
|
+
debugger
|
|
56
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Given /^a podman testcontainer is ready for bard$/ do
|
|
2
|
+
raise "Podman testcontainer failed to start" unless @podman_container && @podman_ssh_port
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
Given /^a remote file "([^\"]+)" exists in the test container$/ do |filename|
|
|
6
|
+
run_ssh("touch testproject/#{filename}").should be_true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Given /^a remote file "([^\"]+)" containing "([^\"]+)" exists in the test container$/ do |filename, content|
|
|
10
|
+
run_ssh("echo #{Shellwords.escape(content)} > testproject/#{filename}").should be_true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
When /^I run bard "([^\"]+)" against the test container$/ do |command|
|
|
14
|
+
run_bard_against_container(command)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Then /^the bard command should succeed$/ do
|
|
18
|
+
@status.success?.should be_true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Then /^the bard output should include "([^\"]+)"$/ do |expected|
|
|
22
|
+
@stdout.should include(expected)
|
|
23
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Given /^a commit with a new migration$/ do
|
|
2
|
+
type "script/generate migration test_migration"
|
|
3
|
+
type "git add ."
|
|
4
|
+
type "git commit -am'added test migration.'"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
Given /^a (\w+) database$/ do |env|
|
|
8
|
+
type "rake db:create RAILS_ENV=#{env} && rake db:migrate RAILS_ENV=#{env}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Then /^the (\w+) database should include that migration$/ do |env|
|
|
12
|
+
db_version = type("rake db:version RAILS_ENV=#{env}")[/[0-9]{14}/]
|
|
13
|
+
migration_version = type("ls db/migrate/*_test_migration.rb")[/[0-9]{14}/]
|
|
14
|
+
db_version.should == migration_version
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Given /^the test gem is not installed$/ do
|
|
18
|
+
type "gem uninstall rake-dotnet -v=0.0.1 -x"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Given /^a commit that adds the test gem as a dependency$/ do
|
|
22
|
+
file_inject "config/environment.rb", "
|
|
23
|
+
Rails::Initializer.run do |config|", <<-RUBY
|
|
24
|
+
config.gem "rake-dotnet", :version => "0.0.1"
|
|
25
|
+
RUBY
|
|
26
|
+
type "git add ."
|
|
27
|
+
type "git commit -am'added test gem dependency.'"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Then /^the test gem should be installed$/ do
|
|
31
|
+
type("gem list rake-dotnet").should include "rake-dotnet (0.0.1)"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Then /^passenger should have been restarted$/ do
|
|
35
|
+
File.exist?("tmp/restart.txt").should be_true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Given /^the "([^\"]+)" file includes "([^\"]+)"$/ do |file, contents|
|
|
39
|
+
file_append file, contents
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Given /^the "([^\"]+)" file does not include "([^\"]+)"$/ do |file, contents|
|
|
43
|
+
gsub_file file, contents, ""
|
|
44
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Given /^a submodule$/ do
|
|
2
|
+
Given 'on development_b, a commit with a new submodule'
|
|
3
|
+
Given 'on development_b, I type "bard push"'
|
|
4
|
+
Given 'I type "bard pull"'
|
|
5
|
+
@submodule_url = File.read(".gitmodules").match(/url = (.*)$/)[1]
|
|
6
|
+
@submodule_commit = type "git submodule status"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Given /^the submodule working directory is dirty$/ do
|
|
10
|
+
Dir.chdir "submodule" do
|
|
11
|
+
type "git checkout master"
|
|
12
|
+
type "echo 'submodule_update' > submodule_update"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Given /^a commit to the submodule$/ do
|
|
17
|
+
Dir.chdir "submodule" do
|
|
18
|
+
type "echo 'submodule_update' > submodule_update"
|
|
19
|
+
type "git add ."
|
|
20
|
+
type "git commit -am 'update in submodule'"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Given /^a commit with a new submodule$/ do
|
|
25
|
+
type "git submodule add #{ROOT}/tmp/submodule_a.git submodule"
|
|
26
|
+
type "git submodule update --init"
|
|
27
|
+
Dir.chdir "submodule" do
|
|
28
|
+
type "git checkout master"
|
|
29
|
+
end
|
|
30
|
+
type "git add ."
|
|
31
|
+
type "git commit -m 'added submodule'"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Given /^a commit with a submodule update$/ do
|
|
35
|
+
type "git checkout integration"
|
|
36
|
+
Dir.chdir "submodule" do
|
|
37
|
+
type "git checkout master"
|
|
38
|
+
type "echo 'submodule_update' > submodule_update"
|
|
39
|
+
type "git add ."
|
|
40
|
+
type "git commit -m 'update in submodule'"
|
|
41
|
+
type "git push origin HEAD"
|
|
42
|
+
end
|
|
43
|
+
type "git add ."
|
|
44
|
+
type "git commit -m 'updated submodule'"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Given /^a commit with a submodule url change$/ do
|
|
48
|
+
gsub_file ".gitmodules", "submodule_a.git", "submodule_b.git"
|
|
49
|
+
type "git add ."
|
|
50
|
+
type "git commit -m 'updated submodule url'"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Given /^I a commit a with a submodule deletion$/ do
|
|
54
|
+
type "rm .gitmodules"
|
|
55
|
+
type "rm -rf --cached submodule"
|
|
56
|
+
type "git add ."
|
|
57
|
+
type "git commit -am'removed submodule'"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Then /^there should be one new submodule$/ do
|
|
61
|
+
status = type "git submodule status"
|
|
62
|
+
status.should match /.[a-z0-9]{40} submodule/
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Then /^the submodule branch should match the submodule origin branch$/ do
|
|
66
|
+
@submodule_url = File.read(".gitmodules").match(/url = (.*)$/)[1]
|
|
67
|
+
@submodule_commit = type "git submodule status"
|
|
68
|
+
@submodule_commit.should match %r( [a-z0-9]{40} submodule)
|
|
69
|
+
Dir.chdir "submodule" do
|
|
70
|
+
@submodule = Grit::Repo.new "."
|
|
71
|
+
branch = @submodule.head.name rescue nil
|
|
72
|
+
remote_branch = @submodule.remotes.find {|n| n.name == "origin/HEAD" }.commit.id[/\w+$/]
|
|
73
|
+
branch.should_not be_nil
|
|
74
|
+
remote_branch.should_not be_nil
|
|
75
|
+
branch.should == remote_branch
|
|
76
|
+
type("git rev-parse HEAD").should == type("git rev-parse origin/HEAD")
|
|
77
|
+
type("git name-rev --name-only HEAD").should == type("git name-rev --name-only origin/HEAD")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
Then /^the submodule should be checked out$/ do
|
|
82
|
+
@submodule_url = File.read(".gitmodules").match(/url = (.*)$/)[1]
|
|
83
|
+
@submodule_commit = type "git submodule status"
|
|
84
|
+
@submodule_commit.should match %r( [a-z0-9]{40} submodule)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Then /^the submodule should be updated$/ do
|
|
88
|
+
@submodule_commit[/[a-z0-9]{40}/].should_not == type("git submodule status")[/[a-z0-9]{40}/]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Then /^the submodule url should be changed$/ do
|
|
92
|
+
Dir.chdir "submodule" do
|
|
93
|
+
remote = type "git remote show origin"
|
|
94
|
+
remote.should_not match %r(Fetch URL: #{@submodule_url}$)
|
|
95
|
+
remote.should_not match %r(Push URL: #{@submodule_url}$)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Then /^the submodule should be deleted$/ do
|
|
100
|
+
Then 'the directory should not be dirty'
|
|
101
|
+
@submodule_commit = type "git submodule status"
|
|
102
|
+
@submodule_commit.should_not match /.[a-z0-9]{40} submodule/
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Then /^the submodule working directory should be clean$/ do
|
|
107
|
+
Dir.chdir "submodule" do
|
|
108
|
+
type("git status").should include "working directory clean"
|
|
109
|
+
end
|
|
110
|
+
end
|
data/features/support/env.rb
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
|
-
require "simplecov"
|
|
2
|
-
SimpleCov.start do
|
|
3
|
-
command_name "Cucumber"
|
|
4
|
-
track_files "lib/**/*.rb"
|
|
5
|
-
add_filter "spec/"
|
|
6
|
-
add_filter "features/"
|
|
7
|
-
end
|
|
8
|
-
|
|
9
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
|
10
|
-
require
|
|
11
|
-
require '
|
|
12
|
-
require '
|
|
2
|
+
require 'ruby-debug'
|
|
3
|
+
require 'grit'
|
|
4
|
+
require 'spec/expectations'
|
|
5
|
+
gem 'sqlite3-ruby'
|
|
13
6
|
|
|
14
|
-
ENV["PATH"]
|
|
7
|
+
ENV["PATH"] += ":#{File.dirname(File.expand_path(__FILE__))}/../../bin"
|
|
15
8
|
ENV["GIT_DIR"] = nil
|
|
16
9
|
ENV["GIT_WORK_TREE"] = nil
|
|
17
10
|
ENV["GIT_INDEX_FILE"] = nil
|
|
18
11
|
|
|
19
12
|
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
|
|
20
13
|
|
|
21
|
-
#
|
|
22
|
-
|
|
14
|
+
# setup fixtures
|
|
15
|
+
if File.exist?("/dev/shm")
|
|
16
|
+
FileUtils.rm_rf "tmp"
|
|
17
|
+
tmp_dir = "/dev/shm/bard_testing_tmp"
|
|
18
|
+
FileUtils.rm_rf tmp_dir
|
|
19
|
+
FileUtils.mkdir tmp_dir
|
|
20
|
+
`ln -s #{tmp_dir} tmp`
|
|
21
|
+
else
|
|
22
|
+
FileUtils.rm_rf "tmp"
|
|
23
|
+
FileUtils.mkdir "tmp"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Dir.chdir 'tmp' do
|
|
27
|
+
`git clone --mirror --recursive #{ROOT}/fixtures/repo origin.git`
|
|
28
|
+
|
|
29
|
+
`git clone --bare --recursive origin.git submodule_a.git`
|
|
30
|
+
`git clone --bare --recursive origin.git submodule_b.git`
|
|
31
|
+
%w(development_a development_b staging production).each do |env|
|
|
32
|
+
`git clone --recursive origin.git #{env}`
|
|
33
|
+
Dir.chdir env do
|
|
34
|
+
FileUtils.cp "config/database.sample.yml", "config/database.yml"
|
|
35
|
+
`grb track master`
|
|
36
|
+
`git checkout master`
|
|
37
|
+
unless env == "production"
|
|
38
|
+
`grb track integration`
|
|
39
|
+
`git checkout integration`
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
FileUtils.mkdir "fixtures"
|
|
44
|
+
Dir.foreach "." do |file|
|
|
45
|
+
FileUtils.mv(file, "fixtures/") unless %w(fixtures . ..).include? file
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Grit::Repo.class_eval do
|
|
2
|
+
def remote_branches(remote = "origin")
|
|
3
|
+
branches = self.remotes
|
|
4
|
+
branches.reject! { |r| r.name !~ %r(^#{remote}/) }
|
|
5
|
+
branches.collect! { |r| r.name.split('/')[1] }
|
|
6
|
+
branches.reject! { |b| b == "HEAD" }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def find_common_ancestor(head1, head2)
|
|
10
|
+
`git merge-base #{head1} #{head2}`.chomp
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
def type(command)
|
|
3
|
+
@stdout, @stderr, @status = Open3.capture3(@env, command)
|
|
4
|
+
if ENV['DEBUG']
|
|
5
|
+
puts '-' * 20
|
|
6
|
+
puts "Executing command: #{command}"
|
|
7
|
+
puts " Status: #{@status}"
|
|
8
|
+
puts " Stdout:\n #{@stdout}"
|
|
9
|
+
puts " Stderr:\n #{@stderr}"
|
|
10
|
+
puts '-' * 20
|
|
11
|
+
end
|
|
12
|
+
@stdout || @stderr
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def file_append(file_name, contents)
|
|
16
|
+
File.open(file_name, 'ab') { |file| file.puts("\n#{contents}") }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def file_inject(file_name, sentinel, string, before_after=:after)
|
|
20
|
+
gsub_file file_name, /(#{Regexp.escape(sentinel)})/mi do |match|
|
|
21
|
+
if before_after == :after
|
|
22
|
+
"#{match}\n#{string}"
|
|
23
|
+
else
|
|
24
|
+
"#{string}\n#{match}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def gsub_file(file_name, regexp, *args, &block)
|
|
30
|
+
content = File.read(file_name).gsub(regexp, *args, &block)
|
|
31
|
+
File.open(file_name, 'wb') { |file| file.write(content) }
|
|
32
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "open3"
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require "testcontainers"
|
|
6
|
+
|
|
7
|
+
module PodmanWorld
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :podman_available, :podman_image_built
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class PrerequisiteError < StandardError; end
|
|
13
|
+
|
|
14
|
+
def ensure_podman_available
|
|
15
|
+
return if @podman_available || PodmanWorld.podman_available
|
|
16
|
+
|
|
17
|
+
raise PrerequisiteError, "podman is not installed or not on PATH" unless system("command -v podman >/dev/null 2>&1")
|
|
18
|
+
|
|
19
|
+
configure_podman_socket
|
|
20
|
+
ensure_bard_test_image
|
|
21
|
+
FileUtils.chmod(0o600, podman_ssh_key_path)
|
|
22
|
+
|
|
23
|
+
PodmanWorld.podman_available = true
|
|
24
|
+
@podman_available = true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def configure_podman_socket
|
|
28
|
+
return if ENV["DOCKER_HOST"]
|
|
29
|
+
|
|
30
|
+
podman_socket = "/run/user/#{Process.uid}/podman/podman.sock"
|
|
31
|
+
unless File.exist?(podman_socket)
|
|
32
|
+
system("systemctl --user start podman.socket 2>/dev/null || podman system service --time=0 unix://#{podman_socket} &")
|
|
33
|
+
sleep 2
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
raise PrerequisiteError, "Podman socket not available at #{podman_socket}" unless File.exist?(podman_socket)
|
|
37
|
+
|
|
38
|
+
ENV["DOCKER_HOST"] = "unix://#{podman_socket}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def ensure_bard_test_image
|
|
42
|
+
return if @podman_image_built || PodmanWorld.podman_image_built
|
|
43
|
+
|
|
44
|
+
raise PrerequisiteError, "Unable to pull ubuntu:22.04 image" unless system("podman pull ubuntu:22.04 >/dev/null 2>&1")
|
|
45
|
+
|
|
46
|
+
docker_dir = File.join(ROOT, "spec/acceptance/docker")
|
|
47
|
+
dockerfile = File.join(docker_dir, "Dockerfile")
|
|
48
|
+
unless system("podman build -t bard-test-server -f #{dockerfile} #{docker_dir} 2>&1")
|
|
49
|
+
raise PrerequisiteError, "Failed to build bard test image"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
PodmanWorld.podman_image_built = true
|
|
53
|
+
@podman_image_built = true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def start_podman_container
|
|
57
|
+
ensure_podman_available
|
|
58
|
+
|
|
59
|
+
@podman_container = Testcontainers::DockerContainer
|
|
60
|
+
.new("localhost/bard-test-server:latest")
|
|
61
|
+
.with_exposed_port(22)
|
|
62
|
+
.with_name("bard-test-#{SecureRandom.hex(4)}")
|
|
63
|
+
.start
|
|
64
|
+
|
|
65
|
+
@podman_ssh_port = @podman_container.mapped_port(22)
|
|
66
|
+
wait_for_ssh
|
|
67
|
+
run_ssh("mkdir -p testproject")
|
|
68
|
+
write_bard_config
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def wait_for_ssh
|
|
72
|
+
30.times do
|
|
73
|
+
return if run_ssh("echo ready", quiet: true)
|
|
74
|
+
sleep 0.5
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
raise PrerequisiteError, "SSH in podman container did not become ready"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def write_bard_config
|
|
81
|
+
FileUtils.mkdir_p(File.join(ROOT, "tmp"))
|
|
82
|
+
@bard_config_path = File.join(ROOT, "tmp", "test_bard_#{SecureRandom.hex(4)}.rb")
|
|
83
|
+
|
|
84
|
+
File.write(@bard_config_path, <<~RUBY)
|
|
85
|
+
server :production do
|
|
86
|
+
ssh "deploy@localhost:#{@podman_ssh_port}"
|
|
87
|
+
path "testproject"
|
|
88
|
+
ssh_key "#{podman_ssh_key_path}"
|
|
89
|
+
ping false
|
|
90
|
+
end
|
|
91
|
+
RUBY
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def run_ssh(command, quiet: false)
|
|
95
|
+
escaped = Shellwords.escape(command)
|
|
96
|
+
ssh_command = [
|
|
97
|
+
"ssh",
|
|
98
|
+
"-o", "StrictHostKeyChecking=no",
|
|
99
|
+
"-o", "ConnectTimeout=1",
|
|
100
|
+
"-p", @podman_ssh_port.to_s,
|
|
101
|
+
"-i", podman_ssh_key_path,
|
|
102
|
+
"deploy@localhost",
|
|
103
|
+
"--",
|
|
104
|
+
"bash",
|
|
105
|
+
"-lc",
|
|
106
|
+
escaped
|
|
107
|
+
].join(" ")
|
|
108
|
+
|
|
109
|
+
quiet ? system("#{ssh_command} >/dev/null 2>&1") : system(ssh_command)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def run_bard_against_container(command)
|
|
113
|
+
Dir.chdir(File.join(ROOT, "tmp")) do
|
|
114
|
+
FileUtils.cp(@bard_config_path, "bard.rb")
|
|
115
|
+
@stdout, @status = Open3.capture2e(@env || {}, "bard run #{command}")
|
|
116
|
+
@stderr = ""
|
|
117
|
+
FileUtils.rm_f("bard.rb")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def podman_ssh_key_path
|
|
122
|
+
@podman_ssh_key_path ||= File.expand_path(File.join(ROOT, "spec/acceptance/docker/test_key"))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def stop_podman_container
|
|
126
|
+
FileUtils.rm_f(@bard_config_path) if @bard_config_path
|
|
127
|
+
return unless @podman_container
|
|
128
|
+
|
|
129
|
+
@podman_container.stop
|
|
130
|
+
@podman_container.remove
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
warn "Failed to cleanup podman container: #{e.message}"
|
|
133
|
+
ensure
|
|
134
|
+
@podman_container = nil
|
|
135
|
+
@podman_ssh_port = nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
World(PodmanWorld)
|
|
140
|
+
|
|
141
|
+
Before("@podman") do
|
|
142
|
+
@env ||= {}
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
start_podman_container
|
|
146
|
+
rescue PodmanWorld::PrerequisiteError => e
|
|
147
|
+
pending(e.message)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
After("@podman") do
|
|
152
|
+
stop_podman_container
|
|
153
|
+
end
|
data/lib/bard/ci/jenkins.rb
CHANGED
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "bard/ci/runner"
|
|
3
|
-
require "bard/secrets"
|
|
4
3
|
|
|
5
4
|
module Bard
|
|
6
5
|
class CI
|
|
7
6
|
class Jenkins < Runner
|
|
8
|
-
def exists?
|
|
9
|
-
`curl -s -I #{ci_host}/` =~ /\b200 OK\b/ or create!
|
|
10
|
-
end
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
config = JOB_CONFIG_XML.sub("GIT_URL", git_url)
|
|
15
|
-
if File.exist?("config/master.key")
|
|
16
|
-
master_key = File.read("config/master.key").strip
|
|
17
|
-
master_key_step = <<~XML.chomp
|
|
18
|
-
<hudson.tasks.Shell>
|
|
19
|
-
<command>echo #{master_key} > config/master.key</command>
|
|
20
|
-
</hudson.tasks.Shell>
|
|
21
|
-
|
|
22
|
-
XML
|
|
23
|
-
config = config.sub("<builders>\n", "<builders>\n #{master_key_step}")
|
|
24
|
-
end
|
|
25
|
-
`curl -s -X POST "http://#{auth}@ci.botandrose.com/createItem?name=#{project_name}" -H "Content-Type: application/xml" -d '#{config}'`
|
|
8
|
+
def exists?
|
|
9
|
+
`curl -s -I #{ci_host}/` =~ /\b200 OK\b/
|
|
26
10
|
end
|
|
27
11
|
|
|
28
12
|
def console
|
|
@@ -39,7 +23,7 @@ module Bard
|
|
|
39
23
|
end
|
|
40
24
|
|
|
41
25
|
def start
|
|
42
|
-
command = "curl -s -I -X POST -L '#{ci_host}/buildWithParameters?GIT_REF=#{
|
|
26
|
+
command = "curl -s -I -X POST -L '#{ci_host}/buildWithParameters?GIT_REF=#{sha}'"
|
|
43
27
|
output = `#{command}`
|
|
44
28
|
@queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
|
|
45
29
|
end
|
|
@@ -47,7 +31,7 @@ module Bard
|
|
|
47
31
|
def building?
|
|
48
32
|
retry_with_backoff do
|
|
49
33
|
self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
|
|
50
|
-
raise "Blank response from CI" if last_response.
|
|
34
|
+
raise "Blank response from CI" if last_response.blank?
|
|
51
35
|
end
|
|
52
36
|
last_response.include? '"building":true'
|
|
53
37
|
end
|
|
@@ -77,9 +61,9 @@ module Bard
|
|
|
77
61
|
private
|
|
78
62
|
|
|
79
63
|
def get_last_time_elapsed
|
|
80
|
-
|
|
64
|
+
retry_with_backoff do
|
|
81
65
|
response = `curl -s #{ci_host}/lastStableBuild/api/xml`
|
|
82
|
-
raise "Blank response from CI" if response.
|
|
66
|
+
raise "Blank response from CI" if response.blank?
|
|
83
67
|
response
|
|
84
68
|
end
|
|
85
69
|
response.match(/<duration>(\d+)<\/duration>/)
|
|
@@ -90,7 +74,7 @@ module Bard
|
|
|
90
74
|
end
|
|
91
75
|
|
|
92
76
|
def auth
|
|
93
|
-
|
|
77
|
+
"botandrose:11cc2ba6ef2e43fbfbedc1f466724f6290"
|
|
94
78
|
end
|
|
95
79
|
|
|
96
80
|
def ci_host
|
|
@@ -101,10 +85,8 @@ module Bard
|
|
|
101
85
|
retry_with_backoff do
|
|
102
86
|
command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
|
|
103
87
|
output = `#{command}`
|
|
104
|
-
raise "Blank response from CI" if output.
|
|
105
|
-
|
|
106
|
-
raise "Build not found in builds list" if builds.empty?
|
|
107
|
-
builds.first["queueId"] == @queueId
|
|
88
|
+
raise "Blank response from CI" if output.blank?
|
|
89
|
+
JSON.parse(output)["builds"][0]["queueId"] == @queueId
|
|
108
90
|
end
|
|
109
91
|
end
|
|
110
92
|
|
|
@@ -112,64 +94,11 @@ module Bard
|
|
|
112
94
|
@job_id ||= begin
|
|
113
95
|
retry_with_backoff do
|
|
114
96
|
output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
|
|
115
|
-
raise "Blank response from CI" if output.
|
|
116
|
-
|
|
117
|
-
build = builds.find { |b| b["queueId"] == @queueId }
|
|
118
|
-
build["number"]
|
|
97
|
+
raise "Blank response from CI" if output.blank?
|
|
98
|
+
output[/"number":(\d+),"queueId":#{@queueId}\b/, 1].to_i
|
|
119
99
|
end
|
|
120
100
|
end
|
|
121
101
|
end
|
|
122
|
-
JOB_CONFIG_XML = <<~XML
|
|
123
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
124
|
-
<project>
|
|
125
|
-
<actions/>
|
|
126
|
-
<description></description>
|
|
127
|
-
<keepDependencies>false</keepDependencies>
|
|
128
|
-
<properties>
|
|
129
|
-
<hudson.model.ParametersDefinitionProperty>
|
|
130
|
-
<parameterDefinitions>
|
|
131
|
-
<hudson.model.StringParameterDefinition>
|
|
132
|
-
<name>GIT_REF</name>
|
|
133
|
-
<description></description>
|
|
134
|
-
<defaultValue>master</defaultValue>
|
|
135
|
-
</hudson.model.StringParameterDefinition>
|
|
136
|
-
</parameterDefinitions>
|
|
137
|
-
</hudson.model.ParametersDefinitionProperty>
|
|
138
|
-
</properties>
|
|
139
|
-
<scm class="hudson.plugins.git.GitSCM" plugin="git@3.3.0">
|
|
140
|
-
<configVersion>2</configVersion>
|
|
141
|
-
<userRemoteConfigs>
|
|
142
|
-
<hudson.plugins.git.UserRemoteConfig>
|
|
143
|
-
<url>GIT_URL</url>
|
|
144
|
-
</hudson.plugins.git.UserRemoteConfig>
|
|
145
|
-
</userRemoteConfigs>
|
|
146
|
-
<branches>
|
|
147
|
-
<hudson.plugins.git.BranchSpec>
|
|
148
|
-
<name>$GIT_REF</name>
|
|
149
|
-
</hudson.plugins.git.BranchSpec>
|
|
150
|
-
</branches>
|
|
151
|
-
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
|
|
152
|
-
<submoduleCfg class="list"/>
|
|
153
|
-
<extensions/>
|
|
154
|
-
</scm>
|
|
155
|
-
<canRoam>true</canRoam>
|
|
156
|
-
<disabled>false</disabled>
|
|
157
|
-
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
|
158
|
-
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
|
159
|
-
<triggers/>
|
|
160
|
-
<concurrentBuild>false</concurrentBuild>
|
|
161
|
-
<builders>
|
|
162
|
-
<hudson.tasks.Shell>
|
|
163
|
-
<command>bash -l -c bin/setup</command>
|
|
164
|
-
</hudson.tasks.Shell>
|
|
165
|
-
<hudson.tasks.Shell>
|
|
166
|
-
<command>bash -l -c bin/ci</command>
|
|
167
|
-
</hudson.tasks.Shell>
|
|
168
|
-
</builders>
|
|
169
|
-
<publishers/>
|
|
170
|
-
<buildWrappers/>
|
|
171
|
-
</project>
|
|
172
|
-
XML
|
|
173
102
|
end
|
|
174
103
|
end
|
|
175
104
|
end
|