bard 1.8.0 → 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/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 -5
- 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/command.rb +10 -29
- data/lib/bard/config.rb +0 -2
- data/lib/bard/copy.rb +33 -12
- data/lib/bard/server.rb +1 -43
- data/lib/bard/ssh_server.rb +1 -1
- data/lib/bard/target.rb +15 -65
- data/lib/bard/version.rb +1 -1
- data/spec/acceptance/docker/Dockerfile +1 -2
- data/spec/bard/command_spec.rb +1 -1
- data/spec/bard/copy_spec.rb +3 -3
- data/spec/bard/ssh_server_spec.rb +3 -7
- data/spec/bard/target_spec.rb +5 -9
- metadata +30 -16
- data/cucumber.yml +0 -1
- data/features/data.feature +0 -12
- data/features/deploy.feature +0 -13
- data/features/run.feature +0 -13
- data/features/step_definitions/bard_steps.rb +0 -39
- data/features/support/test_server.rb +0 -215
- data/lib/bard/deprecation.rb +0 -19
- data/spec/bard/deprecation_spec.rb +0 -281
|
@@ -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,13 +1,47 @@
|
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
|
2
|
-
require '
|
|
3
|
-
require '
|
|
2
|
+
require 'ruby-debug'
|
|
3
|
+
require 'grit'
|
|
4
|
+
require 'spec/expectations'
|
|
5
|
+
gem 'sqlite3-ruby'
|
|
4
6
|
|
|
5
|
-
ENV["PATH"]
|
|
7
|
+
ENV["PATH"] += ":#{File.dirname(File.expand_path(__FILE__))}/../../bin"
|
|
6
8
|
ENV["GIT_DIR"] = nil
|
|
7
9
|
ENV["GIT_WORK_TREE"] = nil
|
|
8
10
|
ENV["GIT_INDEX_FILE"] = nil
|
|
9
11
|
|
|
10
12
|
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
|
|
11
13
|
|
|
12
|
-
#
|
|
13
|
-
|
|
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/command.rb
CHANGED
|
@@ -64,43 +64,24 @@ module Bard
|
|
|
64
64
|
# Support both new Target (with server attribute) and old Server architecture
|
|
65
65
|
ssh_server = on.respond_to?(:server) ? on.server : on
|
|
66
66
|
|
|
67
|
-
# Get options from Target first (for deprecated separate method calls), fall back to SSHServer
|
|
68
|
-
env_value = on.respond_to?(:env) ? on.env : nil
|
|
69
|
-
env_value ||= ssh_server.env if ssh_server.respond_to?(:env)
|
|
70
|
-
|
|
71
|
-
ssh_key = on.respond_to?(:ssh_key) ? on.ssh_key : nil
|
|
72
|
-
ssh_key ||= ssh_server.ssh_key if ssh_server.respond_to?(:ssh_key)
|
|
73
|
-
|
|
74
|
-
gateway = on.respond_to?(:gateway) ? on.gateway : nil
|
|
75
|
-
gateway ||= ssh_server.gateway if ssh_server.respond_to?(:gateway)
|
|
76
|
-
|
|
77
67
|
cmd = command
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
if ssh_server.env
|
|
69
|
+
cmd = "#{ssh_server.env} #{command}"
|
|
70
|
+
end
|
|
80
71
|
unless home
|
|
81
72
|
path = on.respond_to?(:path) ? on.path : ssh_server.path
|
|
82
73
|
cmd = "cd #{path} && #{cmd}" if path
|
|
83
74
|
end
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key} " : ""
|
|
77
|
+
ssh_uri = ssh_server.respond_to?(:ssh_uri) ? ssh_server.ssh_uri : ssh_server.ssh_uri(:ssh)
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
if ssh_server.respond_to?(:host)
|
|
90
|
-
# New SSHServer - has separate host/port/user
|
|
91
|
-
ssh_opts << "-p #{ssh_server.port}" if ssh_server.port && ssh_server.port != "22"
|
|
92
|
-
ssh_opts << "-o ProxyJump=#{gateway}" if gateway
|
|
93
|
-
ssh_target = "#{ssh_server.user}@#{ssh_server.host}"
|
|
94
|
-
else
|
|
95
|
-
# Old Server - uses URI-based ssh_uri
|
|
96
|
-
ssh_target = ssh_server.ssh_uri
|
|
97
|
-
if gateway
|
|
98
|
-
gateway_uri = ssh_server.ssh_uri(:gateway)
|
|
99
|
-
ssh_opts << "-o ProxyJump=#{gateway_uri}"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
79
|
+
cmd = "ssh -tt #{ssh_key} #{ssh_uri} '#{cmd}'"
|
|
102
80
|
|
|
103
|
-
|
|
81
|
+
if ssh_server.gateway
|
|
82
|
+
gateway_uri = ssh_server.respond_to?(:ssh_uri) ? ssh_server.gateway : ssh_server.ssh_uri(:gateway)
|
|
83
|
+
cmd = "ssh -tt #{gateway_uri} \"#{cmd}\""
|
|
84
|
+
end
|
|
104
85
|
|
|
105
86
|
cmd += " 2>&1" if quiet
|
|
106
87
|
cmd
|
data/lib/bard/config.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "bard/server"
|
|
2
2
|
require "bard/target"
|
|
3
|
-
require "bard/deprecation"
|
|
4
3
|
|
|
5
4
|
module Bard
|
|
6
5
|
class Config
|
|
@@ -44,7 +43,6 @@ module Bard
|
|
|
44
43
|
|
|
45
44
|
# Old v1.x API - creates Server instances
|
|
46
45
|
def server(key, &block)
|
|
47
|
-
Deprecation.warn "`server` is deprecated; use `target` instead (will be removed in v2.0)"
|
|
48
46
|
key = key.to_sym
|
|
49
47
|
@servers[key] = Server.define(project_name, key, &block)
|
|
50
48
|
end
|
data/lib/bard/copy.rb
CHANGED
|
@@ -29,16 +29,10 @@ module Bard
|
|
|
29
29
|
|
|
30
30
|
ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# scp uses -P for port (uppercase, unlike ssh's -p)
|
|
35
|
-
port = ssh_server.port
|
|
36
|
-
port_opt = port && port.to_s != "22" ? "-P #{port}" : ""
|
|
37
|
-
|
|
38
|
-
from_and_to = [path, target_or_server.scp_uri(path).to_s]
|
|
32
|
+
from_and_to = [path, target_or_server.scp_uri(path)]
|
|
39
33
|
from_and_to.reverse! if direction == :from
|
|
40
34
|
|
|
41
|
-
command = ["scp",
|
|
35
|
+
command = ["scp", gateway, ssh_key, *from_and_to].join(" ")
|
|
42
36
|
Bard::Command.run! command, verbose: verbose
|
|
43
37
|
end
|
|
44
38
|
|
|
@@ -65,7 +59,18 @@ module Bard
|
|
|
65
59
|
# Support both new Target (with server attribute) and old Server
|
|
66
60
|
ssh_server = target_or_server.respond_to?(:server) ? target_or_server.server : target_or_server
|
|
67
61
|
|
|
68
|
-
ssh_uri
|
|
62
|
+
# Get ssh_uri - it might be a URI object (old Server), string (new SSHServer), or mock
|
|
63
|
+
ssh_uri_value = ssh_server.respond_to?(:ssh_uri) ? ssh_server.ssh_uri : nil
|
|
64
|
+
if ssh_uri_value.respond_to?(:port)
|
|
65
|
+
# Already a URI-like object (old Server or mock)
|
|
66
|
+
ssh_uri = ssh_uri_value
|
|
67
|
+
elsif ssh_uri_value.is_a?(String)
|
|
68
|
+
# String from new SSHServer
|
|
69
|
+
ssh_uri = URI("ssh://#{ssh_uri_value}")
|
|
70
|
+
else
|
|
71
|
+
# Fallback
|
|
72
|
+
ssh_uri = ssh_uri_value
|
|
73
|
+
end
|
|
69
74
|
|
|
70
75
|
gateway = ssh_server.gateway ? "-oProxyCommand=\"ssh #{ssh_server.gateway} -W %h:%p\"" : ""
|
|
71
76
|
|
|
@@ -86,13 +91,29 @@ module Bard
|
|
|
86
91
|
|
|
87
92
|
raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
# Get ssh_uri - it might be a URI object (old Server), string (new SSHServer), or mock
|
|
95
|
+
from_uri_value = from_server.respond_to?(:ssh_uri) ? from_server.ssh_uri : nil
|
|
96
|
+
if from_uri_value.respond_to?(:port)
|
|
97
|
+
from_uri = from_uri_value
|
|
98
|
+
elsif from_uri_value.is_a?(String)
|
|
99
|
+
from_uri = URI("ssh://#{from_uri_value}")
|
|
100
|
+
else
|
|
101
|
+
from_uri = from_uri_value
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
to_uri_value = to_server.respond_to?(:ssh_uri) ? to_server.ssh_uri : nil
|
|
105
|
+
if to_uri_value.respond_to?(:port)
|
|
106
|
+
to_uri = to_uri_value
|
|
107
|
+
elsif to_uri_value.is_a?(String)
|
|
108
|
+
to_uri = URI("ssh://#{to_uri_value}")
|
|
109
|
+
else
|
|
110
|
+
to_uri = to_uri_value
|
|
111
|
+
end
|
|
91
112
|
|
|
92
113
|
from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
|
|
93
114
|
to_str = to.rsync_uri(path).sub(%r(/[^/]+$), '/')
|
|
94
115
|
|
|
95
|
-
command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no
|
|
116
|
+
command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no\" --delete --info=progress2 -az #{from.path}/#{path} #{to_str}')
|
|
96
117
|
Bard::Command.run! command, verbose: verbose
|
|
97
118
|
end
|
|
98
119
|
end
|
data/lib/bard/server.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require "uri"
|
|
2
2
|
require "bard/command"
|
|
3
3
|
require "bard/copy"
|
|
4
|
-
require "bard/deprecation"
|
|
5
4
|
|
|
6
5
|
module Bard
|
|
7
6
|
class Server < Struct.new(:project_name, :key, :ssh, :path, :ping, :gateway, :ssh_key, :env, :github_pages)
|
|
@@ -25,24 +24,7 @@ module Bard
|
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
fields.each do |field|
|
|
30
|
-
define_method field do |*args|
|
|
31
|
-
if args.length == 1
|
|
32
|
-
Deprecation.warn message
|
|
33
|
-
send :"#{field}=", args.first
|
|
34
|
-
elsif args.length == 0
|
|
35
|
-
super()
|
|
36
|
-
else
|
|
37
|
-
raise ArgumentError
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
setting :ssh, :ping, :github_pages
|
|
44
|
-
setting_with_deprecation :gateway, :ssh_key, :env,
|
|
45
|
-
message: "Separate SSH options are deprecated; pass as keyword arguments to `ssh` instead, e.g., `ssh \"user@host\", path: \"/app\"` (will be removed in v2.0)"
|
|
27
|
+
setting :ssh, :path, :ping, :gateway, :ssh_key, :env, :github_pages
|
|
46
28
|
|
|
47
29
|
def ping(*args)
|
|
48
30
|
if args.length == 0
|
|
@@ -68,7 +50,6 @@ module Bard
|
|
|
68
50
|
|
|
69
51
|
def path(*args)
|
|
70
52
|
if args.length == 1
|
|
71
|
-
Deprecation.warn "Separate SSH options are deprecated; pass as keyword arguments to `ssh` instead, e.g., `ssh \"user@host\", path: \"/app\"` (will be removed in v2.0)"
|
|
72
53
|
self.path = args.first
|
|
73
54
|
elsif args.length == 0
|
|
74
55
|
super() || project_name
|
|
@@ -77,34 +58,11 @@ module Bard
|
|
|
77
58
|
end
|
|
78
59
|
end
|
|
79
60
|
|
|
80
|
-
def strategy(name)
|
|
81
|
-
Deprecation.warn "`strategy` is deprecated; use the strategy method directly, e.g., `jets \"url\"` instead of `strategy :jets` (will be removed in v2.0)"
|
|
82
|
-
@strategy = name
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def option(key, value)
|
|
86
|
-
Deprecation.warn "`option` is deprecated; pass options as keyword arguments to the strategy method, e.g., `jets \"url\", run_tests: true` (will be removed in v2.0)"
|
|
87
|
-
@options ||= {}
|
|
88
|
-
@options[key] = value
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def strategy_name
|
|
92
|
-
@strategy
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def strategy_options
|
|
96
|
-
@options || {}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
61
|
def ssh_uri which=:ssh
|
|
100
62
|
value = send(which)
|
|
101
63
|
URI("ssh://#{value}")
|
|
102
64
|
end
|
|
103
65
|
|
|
104
|
-
def port
|
|
105
|
-
ssh_uri.port || 22
|
|
106
|
-
end
|
|
107
|
-
|
|
108
66
|
def scp_uri file_path=nil
|
|
109
67
|
ssh_uri.dup.tap do |uri|
|
|
110
68
|
uri.scheme = "scp"
|