bard 2.0.0.beta → 2.0.1
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} +41 -13
- 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,110 +0,0 @@
|
|
|
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,13 +0,0 @@
|
|
|
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
|
-
|
data/features/support/io.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
data/features/support/podman.rb
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "bard/ci/runner"
|
|
3
|
-
|
|
4
|
-
module Bard
|
|
5
|
-
class CI
|
|
6
|
-
class Jenkins < Runner
|
|
7
|
-
|
|
8
|
-
def exists?
|
|
9
|
-
`curl -s -I #{ci_host}/` =~ /\b200 OK\b/
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def console
|
|
13
|
-
raw = `curl -s #{ci_host}/lastBuild/console`
|
|
14
|
-
raw[%r{<pre.*?>(.+)</pre>}m, 1]
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
attr_accessor :last_response
|
|
18
|
-
|
|
19
|
-
protected
|
|
20
|
-
|
|
21
|
-
def wait_until_started
|
|
22
|
-
sleep(2) until started?
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def start
|
|
26
|
-
command = "curl -s -I -X POST -L '#{ci_host}/buildWithParameters?GIT_REF=#{sha}'"
|
|
27
|
-
output = `#{command}`
|
|
28
|
-
@queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def building?
|
|
32
|
-
retry_with_backoff do
|
|
33
|
-
self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
|
|
34
|
-
raise "Blank response from CI" if last_response.blank?
|
|
35
|
-
end
|
|
36
|
-
last_response.include? '"building":true'
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def success?
|
|
40
|
-
last_response.include? '"result":"SUCCESS"'
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def get_state_data
|
|
44
|
-
{
|
|
45
|
-
"project_name" => project_name,
|
|
46
|
-
"branch" => branch,
|
|
47
|
-
"queue_id" => @queueId,
|
|
48
|
-
"job_id" => @job_id,
|
|
49
|
-
"start_time" => @start_time,
|
|
50
|
-
"last_time_elapsed" => @last_time_elapsed
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def restore_state(data)
|
|
55
|
-
@queueId = data["queue_id"]
|
|
56
|
-
@job_id = data["job_id"]
|
|
57
|
-
@start_time = data["start_time"]
|
|
58
|
-
@last_time_elapsed = data["last_time_elapsed"]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def get_last_time_elapsed
|
|
64
|
-
retry_with_backoff do
|
|
65
|
-
response = `curl -s #{ci_host}/lastStableBuild/api/xml`
|
|
66
|
-
raise "Blank response from CI" if response.blank?
|
|
67
|
-
response
|
|
68
|
-
end
|
|
69
|
-
response.match(/<duration>(\d+)<\/duration>/)
|
|
70
|
-
$1 ? $1.to_i / 1000 : nil
|
|
71
|
-
rescue => e
|
|
72
|
-
puts " Warning: Could not get last build duration: #{e.message}"
|
|
73
|
-
nil
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def auth
|
|
77
|
-
"botandrose:11cc2ba6ef2e43fbfbedc1f466724f6290"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def ci_host
|
|
81
|
-
"http://#{auth}@ci.botandrose.com/job/#{project_name}"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def started?
|
|
85
|
-
retry_with_backoff do
|
|
86
|
-
command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
|
|
87
|
-
output = `#{command}`
|
|
88
|
-
raise "Blank response from CI" if output.blank?
|
|
89
|
-
JSON.parse(output)["builds"][0]["queueId"] == @queueId
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def job_id
|
|
94
|
-
@job_id ||= begin
|
|
95
|
-
retry_with_backoff do
|
|
96
|
-
output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
|
|
97
|
-
raise "Blank response from CI" if output.blank?
|
|
98
|
-
output[/"number":(\d+),"queueId":#{@queueId}\b/, 1].to_i
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
data/lib/bard/ci/retryable.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Bard
|
|
2
|
-
class CI
|
|
3
|
-
module Retryable
|
|
4
|
-
MAX_RETRIES = 5
|
|
5
|
-
INITIAL_DELAY = 1
|
|
6
|
-
|
|
7
|
-
def retry_with_backoff(max_retries: MAX_RETRIES)
|
|
8
|
-
retries = 0
|
|
9
|
-
delay = INITIAL_DELAY
|
|
10
|
-
|
|
11
|
-
begin
|
|
12
|
-
yield
|
|
13
|
-
rescue => e
|
|
14
|
-
if retries < max_retries
|
|
15
|
-
retries += 1
|
|
16
|
-
puts " Network error (attempt #{retries}/#{max_retries}): #{e.message}. Retrying in #{delay}s..."
|
|
17
|
-
sleep(delay)
|
|
18
|
-
delay *= 2
|
|
19
|
-
retry
|
|
20
|
-
else
|
|
21
|
-
raise "Network error after #{max_retries} attempts: #{e.message}"
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
data/lib/bard/ci.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
require "forwardable"
|
|
2
|
-
|
|
3
|
-
module Bard
|
|
4
|
-
class CI
|
|
5
|
-
def initialize project_name, branch, local: false
|
|
6
|
-
@project_name = project_name
|
|
7
|
-
@branch = branch
|
|
8
|
-
@local = !!local
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
extend Forwardable
|
|
12
|
-
delegate [:run, :resume, :exists?, :console, :status] => :runner
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def local?
|
|
17
|
-
@local
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def github_actions?
|
|
21
|
-
File.exist?(".github/workflows/ci.yml")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def jenkins?
|
|
25
|
-
!local? && !github_actions?
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def runner
|
|
29
|
-
@runner ||= choose_runner_class.new(@project_name, @branch, sha)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def sha
|
|
33
|
-
@sha ||= `git rev-parse #{@branch}`.chomp
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def choose_runner_class
|
|
37
|
-
if local?
|
|
38
|
-
require_relative "./ci/local"
|
|
39
|
-
Local
|
|
40
|
-
elsif github_actions?
|
|
41
|
-
require_relative "./ci/github_actions"
|
|
42
|
-
GithubActions
|
|
43
|
-
elsif jenkins?
|
|
44
|
-
require_relative "./ci/jenkins"
|
|
45
|
-
Jenkins
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
data/lib/bard/cli/ci.rb
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
require "bard/ci"
|
|
2
|
-
require "bard/git"
|
|
3
|
-
|
|
4
|
-
module Bard::CLI::CI
|
|
5
|
-
def self.included mod
|
|
6
|
-
mod.class_eval do
|
|
7
|
-
|
|
8
|
-
option :"local-ci", type: :boolean
|
|
9
|
-
option :status, type: :boolean
|
|
10
|
-
option :resume, type: :boolean
|
|
11
|
-
desc "ci [branch=HEAD]", "runs ci against BRANCH"
|
|
12
|
-
def ci branch=Bard::Git.current_branch
|
|
13
|
-
ci = Bard::CI.new(project_name, branch, local: options["local-ci"])
|
|
14
|
-
if ci.exists?
|
|
15
|
-
return puts ci.status if options["status"]
|
|
16
|
-
|
|
17
|
-
if options["resume"]
|
|
18
|
-
puts "Continuous integration: resuming build..."
|
|
19
|
-
success = ci.resume do |elapsed_time, last_time|
|
|
20
|
-
if last_time
|
|
21
|
-
percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
|
|
22
|
-
output = " Estimated completion: #{percentage}%"
|
|
23
|
-
else
|
|
24
|
-
output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
|
|
25
|
-
end
|
|
26
|
-
print "\x08" * output.length
|
|
27
|
-
print output
|
|
28
|
-
$stdout.flush
|
|
29
|
-
end
|
|
30
|
-
else
|
|
31
|
-
puts "Continuous integration: starting build on #{branch}..."
|
|
32
|
-
|
|
33
|
-
success = ci.run do |elapsed_time, last_time|
|
|
34
|
-
if last_time
|
|
35
|
-
percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
|
|
36
|
-
output = " Estimated completion: #{percentage}%"
|
|
37
|
-
else
|
|
38
|
-
output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
|
|
39
|
-
end
|
|
40
|
-
print "\x08" * output.length
|
|
41
|
-
print output
|
|
42
|
-
$stdout.flush
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
if success
|
|
47
|
-
puts
|
|
48
|
-
puts "Continuous integration: success!"
|
|
49
|
-
else
|
|
50
|
-
puts
|
|
51
|
-
puts ci.console
|
|
52
|
-
puts red("Automated tests failed!")
|
|
53
|
-
exit 1
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
else
|
|
57
|
-
puts red("No CI found for #{project_name}!")
|
|
58
|
-
puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
|
|
59
|
-
exit 1
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
data/lib/bard/cli/command.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require "delegate"
|
|
2
|
-
|
|
3
|
-
class Bard::CLI::Command < SimpleDelegator
|
|
4
|
-
def self.desc command, description
|
|
5
|
-
@command = command
|
|
6
|
-
@method = command.split(" ").first.to_sym
|
|
7
|
-
@description = description
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.option *args, **kwargs
|
|
11
|
-
@option_args = args
|
|
12
|
-
@option_kwargs = kwargs
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.setup cli
|
|
16
|
-
cli.desc @command, @description
|
|
17
|
-
cli.option *@option_args, **@option_kwargs if @option_args || @option_kwargs
|
|
18
|
-
# put in local variables so next block can capture it
|
|
19
|
-
command_class = self
|
|
20
|
-
method = @method
|
|
21
|
-
cli.define_method method do |*args|
|
|
22
|
-
command = command_class.new(self)
|
|
23
|
-
command.send method, *args
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
data/lib/bard/cli/data.rb
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
require "bard/command"
|
|
2
|
-
|
|
3
|
-
module Bard::CLI::Data
|
|
4
|
-
def self.included mod
|
|
5
|
-
mod.class_eval do
|
|
6
|
-
|
|
7
|
-
desc "data --from=production --to=local", "copy database and assets from from to to"
|
|
8
|
-
option :from, default: "production"
|
|
9
|
-
option :to, default: "local"
|
|
10
|
-
def data
|
|
11
|
-
from = config[options[:from]]
|
|
12
|
-
to = config[options[:to]]
|
|
13
|
-
|
|
14
|
-
if to.key == :production
|
|
15
|
-
url = to.ping.first
|
|
16
|
-
puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
|
|
17
|
-
answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
|
|
18
|
-
if answer != url
|
|
19
|
-
puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
|
|
20
|
-
exit 1
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
puts "Dumping #{from.key} database to file..."
|
|
25
|
-
from.run! "bin/rake db:dump"
|
|
26
|
-
|
|
27
|
-
puts "Transfering file from #{from.key} to #{to.key}..."
|
|
28
|
-
from.copy_file "db/data.sql.gz", to: to, verbose: true
|
|
29
|
-
|
|
30
|
-
puts "Loading file into #{to.key} database..."
|
|
31
|
-
to.run! "bin/rake db:load"
|
|
32
|
-
|
|
33
|
-
config.data.each do |path|
|
|
34
|
-
puts "Synchronizing files in #{path}..."
|
|
35
|
-
from.copy_dir path, to: to, verbose: true
|
|
36
|
-
end
|
|
37
|
-
rescue Bard::Command::Error => e
|
|
38
|
-
puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
|
|
39
|
-
exit 1
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|