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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +0 -5
  3. data/MIGRATION_GUIDE.md +9 -24
  4. data/README.md +6 -14
  5. data/README.rdoc +15 -0
  6. data/Rakefile +1 -3
  7. data/features/bard_check.feature +94 -0
  8. data/features/bard_deploy.feature +18 -0
  9. data/features/bard_pull.feature +112 -0
  10. data/features/bard_push.feature +112 -0
  11. data/features/podman_testcontainers.feature +16 -0
  12. data/features/step_definitions/check_steps.rb +47 -0
  13. data/features/step_definitions/git_steps.rb +73 -0
  14. data/features/step_definitions/global_steps.rb +56 -0
  15. data/features/step_definitions/podman_steps.rb +23 -0
  16. data/features/step_definitions/rails_steps.rb +44 -0
  17. data/features/step_definitions/submodule_steps.rb +110 -0
  18. data/features/support/env.rb +39 -14
  19. data/features/support/grit_ext.rb +13 -0
  20. data/features/support/io.rb +32 -0
  21. data/features/support/podman.rb +153 -0
  22. data/lib/bard/ci/github_actions.rb +2 -1
  23. data/lib/bard/ci/jenkins.rb +11 -82
  24. data/lib/bard/ci/local.rb +6 -6
  25. data/lib/bard/ci/runner.rb +1 -35
  26. data/lib/bard/ci.rb +23 -11
  27. data/lib/bard/cli/ci.rb +38 -45
  28. data/lib/bard/cli/deploy.rb +9 -40
  29. data/lib/bard/cli/hurt.rb +15 -10
  30. data/lib/bard/cli/install.rb +12 -7
  31. data/lib/bard/cli/open.rb +16 -12
  32. data/lib/bard/cli/ping.rb +14 -8
  33. data/lib/bard/cli/provision.rb +1 -1
  34. data/lib/bard/cli/run.rb +3 -5
  35. data/lib/bard/cli/stage.rb +1 -9
  36. data/lib/bard/cli/vim.rb +10 -5
  37. data/lib/bard/cli.rb +11 -13
  38. data/lib/bard/command.rb +13 -33
  39. data/lib/bard/config.rb +14 -10
  40. data/lib/bard/copy.rb +33 -12
  41. data/lib/bard/deploy_strategy/github_pages.rb +2 -10
  42. data/lib/bard/deploy_strategy.rb +0 -3
  43. data/lib/bard/github.rb +4 -2
  44. data/lib/bard/provision/apt.rb +1 -1
  45. data/lib/bard/provision/logrotation.rb +1 -1
  46. data/lib/bard/provision/mysql.rb +2 -2
  47. data/lib/bard/provision/passenger.rb +2 -2
  48. data/lib/bard/provision/repo.rb +2 -2
  49. data/lib/bard/provision/ssh.rb +2 -9
  50. data/lib/bard/provision/swapfile.rb +1 -3
  51. data/lib/bard/server.rb +3 -46
  52. data/lib/bard/ssh_server.rb +2 -9
  53. data/lib/bard/target.rb +16 -67
  54. data/lib/bard/version.rb +1 -1
  55. data/spec/acceptance/docker/Dockerfile +1 -2
  56. data/spec/bard/ci/github_actions_spec.rb +13 -116
  57. data/spec/bard/cli/ci_spec.rb +8 -34
  58. data/spec/bard/cli/deploy_spec.rb +8 -46
  59. data/spec/bard/cli/hurt_spec.rb +2 -2
  60. data/spec/bard/cli/install_spec.rb +4 -4
  61. data/spec/bard/cli/open_spec.rb +8 -10
  62. data/spec/bard/cli/ping_spec.rb +5 -5
  63. data/spec/bard/cli/run_spec.rb +1 -20
  64. data/spec/bard/cli/stage_spec.rb +0 -20
  65. data/spec/bard/cli/vim_spec.rb +5 -5
  66. data/spec/bard/command_spec.rb +1 -3
  67. data/spec/bard/config_spec.rb +0 -28
  68. data/spec/bard/copy_spec.rb +3 -3
  69. data/spec/bard/github_spec.rb +1 -1
  70. data/spec/bard/provision/apt_spec.rb +1 -1
  71. data/spec/bard/provision/logrotation_spec.rb +1 -1
  72. data/spec/bard/provision/passenger_spec.rb +1 -1
  73. data/spec/bard/provision/repo_spec.rb +1 -1
  74. data/spec/bard/provision/ssh_spec.rb +4 -17
  75. data/spec/bard/provision/swapfile_spec.rb +1 -2
  76. data/spec/bard/ssh_server_spec.rb +8 -12
  77. data/spec/bard/target_spec.rb +5 -9
  78. data/spec/spec_helper.rb +1 -6
  79. metadata +31 -41
  80. data/CLAUDE.md +0 -76
  81. data/PLUGINS.md +0 -114
  82. data/cucumber.yml +0 -1
  83. data/features/ci.feature +0 -62
  84. data/features/data.feature +0 -12
  85. data/features/deploy.feature +0 -13
  86. data/features/deploy_git_workflow.feature +0 -88
  87. data/features/run.feature +0 -13
  88. data/features/step_definitions/bard_steps.rb +0 -135
  89. data/features/support/bard-coverage +0 -16
  90. data/features/support/test_server.rb +0 -216
  91. data/lib/bard/deprecation.rb +0 -19
  92. data/lib/bard/plugin.rb +0 -100
  93. data/lib/bard/plugins/backup.rb +0 -19
  94. data/lib/bard/plugins/github_pages.rb +0 -34
  95. data/lib/bard/plugins/hurt.rb +0 -5
  96. data/lib/bard/plugins/install.rb +0 -5
  97. data/lib/bard/plugins/jenkins.rb +0 -6
  98. data/lib/bard/plugins/new.rb +0 -5
  99. data/lib/bard/plugins/ping.rb +0 -6
  100. data/lib/bard/plugins/provision.rb +0 -5
  101. data/lib/bard/plugins/vim.rb +0 -5
  102. data/lib/bard/secrets.rb +0 -10
  103. data/spec/bard/ci/jenkins_spec.rb +0 -139
  104. data/spec/bard/ci/runner_spec.rb +0 -61
  105. data/spec/bard/deprecation_spec.rb +0 -281
  106. 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
@@ -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 "bard"
11
- require 'rspec/expectations'
12
- require 'fileutils'
2
+ require 'ruby-debug'
3
+ require 'grit'
4
+ require 'spec/expectations'
5
+ gem 'sqlite3-ruby'
13
6
 
14
- ENV["PATH"] = "#{File.dirname(File.expand_path(__FILE__))}:#{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
- # Ensure tmp directory exists
22
- FileUtils.mkdir_p(File.join(ROOT, "tmp"))
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
@@ -5,8 +5,9 @@ require "bard/ci/runner"
5
5
  module Bard
6
6
  class CI
7
7
  class GithubActions < Runner
8
+
8
9
  def exists?
9
- File.exist?(".github/workflows/ci.yml")
10
+ true
10
11
  end
11
12
 
12
13
  def console
@@ -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
- private def create!
13
- git_url = `git remote get-url origin`.strip
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=#{branch}'"
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.empty?
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
- response = retry_with_backoff do
64
+ retry_with_backoff do
81
65
  response = `curl -s #{ci_host}/lastStableBuild/api/xml`
82
- raise "Blank response from CI" if response.empty?
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
- @auth ||= "#{Bard::Secrets.fetch("jenkins-user")}:#{Bard::Secrets.fetch("jenkins-token")}"
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.empty?
105
- builds = JSON.parse(output)["builds"]
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.empty?
116
- builds = JSON.parse(output)["builds"]
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