bard 2.0.0.beta → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -1
- data/CLAUDE.md +76 -0
- data/MIGRATION_GUIDE.md +24 -9
- data/PLUGINS.md +99 -0
- data/README.md +14 -6
- data/Rakefile +3 -1
- data/bard.gemspec +2 -1
- data/cucumber.yml +1 -0
- data/features/ci.feature +63 -0
- data/features/data.feature +13 -0
- data/features/deploy.feature +14 -0
- data/features/deploy_git_workflow.feature +89 -0
- data/features/run.feature +14 -0
- data/features/step_definitions/bard_steps.rb +136 -0
- data/features/support/bard-coverage +16 -0
- data/features/support/env.rb +14 -39
- data/features/support/test_server.rb +216 -0
- data/lib/bard/cli.rb +14 -31
- data/lib/bard/command.rb +10 -69
- data/lib/bard/config.rb +40 -183
- data/lib/bard/copy.rb +28 -103
- data/lib/bard/plugins/data.rb +56 -0
- data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
- data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
- data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
- data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
- data/lib/bard/plugins/deploy/ci.rb +38 -0
- data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
- data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
- data/lib/bard/plugins/deploy.rb +240 -0
- data/lib/bard/{git.rb → plugins/git.rb} +6 -3
- data/lib/bard/{github.rb → plugins/github.rb} +4 -6
- data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +13 -6
- data/lib/bard/plugins/github_pages.rb +30 -0
- data/lib/bard/plugins/hurt.rb +13 -0
- data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
- data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
- data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
- data/lib/bard/plugins/install.rb +9 -0
- data/lib/bard/plugins/open.rb +20 -0
- data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
- data/lib/bard/plugins/ping/target_methods.rb +23 -0
- data/lib/bard/plugins/ping.rb +10 -0
- data/lib/bard/plugins/run.rb +19 -0
- data/lib/bard/plugins/setup.rb +54 -0
- data/lib/bard/plugins/ssh/connection.rb +75 -0
- data/lib/bard/plugins/ssh/copy.rb +95 -0
- data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
- data/lib/bard/plugins/ssh/target_methods.rb +20 -0
- data/lib/bard/plugins/ssh.rb +10 -0
- data/lib/bard/plugins/url/target_methods.rb +23 -0
- data/lib/bard/plugins/url.rb +1 -0
- data/lib/bard/plugins/vim.rb +6 -0
- data/lib/bard/retryable.rb +25 -0
- data/lib/bard/secrets.rb +10 -0
- data/lib/bard/target.rb +27 -185
- data/lib/bard/version.rb +1 -1
- data/lib/bard.rb +1 -3
- data/spec/acceptance/docker/Dockerfile +3 -2
- data/spec/bard/capability_spec.rb +8 -50
- data/spec/bard/ci/github_actions_spec.rb +117 -14
- data/spec/bard/ci/jenkins_spec.rb +139 -0
- data/spec/bard/ci/runner_spec.rb +61 -0
- data/spec/bard/ci_spec.rb +1 -1
- data/spec/bard/cli/ci_spec.rb +34 -27
- data/spec/bard/cli/data_spec.rb +7 -26
- data/spec/bard/cli/deploy_spec.rb +87 -46
- data/spec/bard/cli/hurt_spec.rb +3 -9
- data/spec/bard/cli/install_spec.rb +5 -11
- data/spec/bard/cli/master_key_spec.rb +5 -19
- data/spec/bard/cli/open_spec.rb +14 -30
- data/spec/bard/cli/ping_spec.rb +8 -23
- data/spec/bard/cli/run_spec.rb +27 -21
- data/spec/bard/cli/setup_spec.rb +10 -27
- data/spec/bard/cli/ssh_spec.rb +10 -25
- data/spec/bard/cli/stage_spec.rb +28 -23
- data/spec/bard/cli/vim_spec.rb +3 -9
- data/spec/bard/command_spec.rb +1 -8
- data/spec/bard/config_spec.rb +78 -98
- data/spec/bard/copy_spec.rb +54 -18
- data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
- data/spec/bard/deploy_strategy_spec.rb +1 -1
- data/spec/bard/dynamic_dsl_spec.rb +18 -98
- data/spec/bard/git_spec.rb +9 -5
- data/spec/bard/github_spec.rb +2 -2
- data/spec/bard/ping_spec.rb +5 -5
- data/spec/bard/ssh_copy_spec.rb +44 -0
- data/spec/bard/ssh_server_spec.rb +8 -101
- data/spec/bard/target_spec.rb +66 -109
- data/spec/spec_helper.rb +6 -1
- metadata +79 -143
- data/README.rdoc +0 -15
- data/features/bard_check.feature +0 -94
- data/features/bard_deploy.feature +0 -18
- data/features/bard_pull.feature +0 -112
- data/features/bard_push.feature +0 -112
- data/features/podman_testcontainers.feature +0 -16
- data/features/step_definitions/check_steps.rb +0 -47
- data/features/step_definitions/git_steps.rb +0 -73
- data/features/step_definitions/global_steps.rb +0 -56
- data/features/step_definitions/podman_steps.rb +0 -23
- data/features/step_definitions/rails_steps.rb +0 -44
- data/features/step_definitions/submodule_steps.rb +0 -110
- data/features/support/grit_ext.rb +0 -13
- data/features/support/io.rb +0 -32
- data/features/support/podman.rb +0 -153
- data/lib/bard/ci/jenkins.rb +0 -105
- data/lib/bard/ci/retryable.rb +0 -27
- data/lib/bard/ci.rb +0 -50
- data/lib/bard/cli/ci.rb +0 -66
- data/lib/bard/cli/command.rb +0 -26
- data/lib/bard/cli/data.rb +0 -45
- data/lib/bard/cli/deploy.rb +0 -85
- data/lib/bard/cli/hurt.rb +0 -20
- data/lib/bard/cli/install.rb +0 -16
- data/lib/bard/cli/master_key.rb +0 -17
- data/lib/bard/cli/new.rb +0 -101
- data/lib/bard/cli/new_rails_template.rb +0 -197
- data/lib/bard/cli/open.rb +0 -22
- data/lib/bard/cli/ping.rb +0 -18
- data/lib/bard/cli/provision.rb +0 -34
- data/lib/bard/cli/run.rb +0 -24
- data/lib/bard/cli/setup.rb +0 -56
- data/lib/bard/cli/ssh.rb +0 -14
- data/lib/bard/cli/stage.rb +0 -27
- data/lib/bard/cli/vim.rb +0 -13
- data/lib/bard/default_config.rb +0 -35
- data/lib/bard/deploy_strategy/ssh.rb +0 -19
- data/lib/bard/github_pages.rb +0 -134
- data/lib/bard/provision/app.rb +0 -10
- data/lib/bard/provision/apt.rb +0 -16
- data/lib/bard/provision/authorizedkeys.rb +0 -25
- data/lib/bard/provision/data.rb +0 -27
- data/lib/bard/provision/deploy.rb +0 -10
- data/lib/bard/provision/http.rb +0 -16
- data/lib/bard/provision/logrotation.rb +0 -30
- data/lib/bard/provision/masterkey.rb +0 -18
- data/lib/bard/provision/mysql.rb +0 -22
- data/lib/bard/provision/passenger.rb +0 -37
- data/lib/bard/provision/repo.rb +0 -72
- data/lib/bard/provision/rvm.rb +0 -22
- data/lib/bard/provision/ssh.rb +0 -72
- data/lib/bard/provision/swapfile.rb +0 -21
- data/lib/bard/provision/user.rb +0 -42
- data/lib/bard/provision.rb +0 -16
- data/lib/bard/server.rb +0 -117
- data/spec/bard/cli/command_spec.rb +0 -50
- data/spec/bard/cli/new_spec.rb +0 -73
- data/spec/bard/cli/provision_spec.rb +0 -42
- data/spec/bard/github_pages_spec.rb +0 -143
- data/spec/bard/provision/app_spec.rb +0 -33
- data/spec/bard/provision/apt_spec.rb +0 -39
- data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
- data/spec/bard/provision/data_spec.rb +0 -54
- data/spec/bard/provision/deploy_spec.rb +0 -33
- data/spec/bard/provision/http_spec.rb +0 -57
- data/spec/bard/provision/logrotation_spec.rb +0 -34
- data/spec/bard/provision/masterkey_spec.rb +0 -63
- data/spec/bard/provision/mysql_spec.rb +0 -55
- data/spec/bard/provision/passenger_spec.rb +0 -81
- data/spec/bard/provision/repo_spec.rb +0 -208
- data/spec/bard/provision/rvm_spec.rb +0 -49
- data/spec/bard/provision/ssh_spec.rb +0 -229
- data/spec/bard/provision/swapfile_spec.rb +0 -32
- data/spec/bard/provision/user_spec.rb +0 -103
- data/spec/bard/provision_spec.rb +0 -28
- data/spec/bard/server_spec.rb +0 -127
- /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/ci +0 -0
- /data/{install_files → lib/bard/plugins/install}/setup +0 -0
- /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
- /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "bard/plugins/url"
|
|
3
|
+
|
|
4
|
+
class Bard::CLI
|
|
5
|
+
desc "setup", "installs app in nginx"
|
|
6
|
+
def setup
|
|
7
|
+
path = "/etc/nginx/sites-available/#{project_name}"
|
|
8
|
+
system "sudo tee #{path} >/dev/null <<-'EOF'
|
|
9
|
+
upstream puma {
|
|
10
|
+
server 127.0.0.1:3000 fail_timeout=5;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
server {
|
|
14
|
+
listen 80;
|
|
15
|
+
server_name #{nginx_server_name};
|
|
16
|
+
root #{Dir.pwd}/public;
|
|
17
|
+
|
|
18
|
+
try_files $uri @app;
|
|
19
|
+
|
|
20
|
+
location @app {
|
|
21
|
+
proxy_pass http://puma;
|
|
22
|
+
proxy_set_header Host $host;
|
|
23
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
24
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
25
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
location ~* \\-[0-9a-f]\\{64\\}\\.(ico|css|js|gif|jpe?g|png|webp)$ {
|
|
29
|
+
access_log off;
|
|
30
|
+
expires max;
|
|
31
|
+
add_header Cache-Control public;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
gzip_static on;
|
|
35
|
+
}
|
|
36
|
+
EOF"
|
|
37
|
+
|
|
38
|
+
dest_path = path.sub("sites-available", "sites-enabled")
|
|
39
|
+
system "sudo ln -sf #{path} #{dest_path}" if !File.exist?(dest_path)
|
|
40
|
+
|
|
41
|
+
system "sudo service nginx restart"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
no_commands do
|
|
45
|
+
def nginx_server_name
|
|
46
|
+
case ENV["RAILS_ENV"]
|
|
47
|
+
when "production"
|
|
48
|
+
"*.#{URI.parse(config[:production].url).host} _"
|
|
49
|
+
when "staging" then "#{project_name}.botandrose.com"
|
|
50
|
+
else "#{project_name}.localhost"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "shellwords"
|
|
3
|
+
require "bard/plugins/ssh/server"
|
|
4
|
+
require "bard/command"
|
|
5
|
+
|
|
6
|
+
module Bard
|
|
7
|
+
module SSH
|
|
8
|
+
def server
|
|
9
|
+
@server
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def gateway
|
|
13
|
+
server.gateway
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ssh_key
|
|
17
|
+
server.ssh_key
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def env
|
|
21
|
+
server.env
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ssh_uri
|
|
25
|
+
server.ssh_uri
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def scp_uri(file_path = nil)
|
|
29
|
+
full_path = "/#{path}"
|
|
30
|
+
full_path += "/#{file_path}" if file_path
|
|
31
|
+
URI::Generic.build(scheme: "scp", userinfo: server.user, host: server.host, port: server.port.to_i, path: full_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def rsync_uri(file_path = nil)
|
|
35
|
+
uri = ssh_uri
|
|
36
|
+
str = "#{uri.user}@#{uri.host}"
|
|
37
|
+
str += ":#{path}"
|
|
38
|
+
str += "/#{file_path}" if file_path
|
|
39
|
+
str
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run!(command, home: false, verbose: false, quiet: false, capture: false)
|
|
43
|
+
result = Command.run!(ssh_command(command, home:), verbose:, quiet:)
|
|
44
|
+
result if capture
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run(command, home: false, verbose: false, quiet: false)
|
|
48
|
+
Command.run(ssh_command(command, home:), verbose:, quiet:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def exec!(command, home: false)
|
|
52
|
+
Command.exec!(ssh_command(command, home:))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def ssh_command(command, home: false)
|
|
58
|
+
cmd = command
|
|
59
|
+
cmd = "#{env} #{command}" if env
|
|
60
|
+
|
|
61
|
+
unless home
|
|
62
|
+
cmd = "cd #{path} && #{cmd}" if path
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ssh_opts = ["-tt", "-o StrictHostKeyChecking=no", "-o UserKnownHostsFile=/dev/null", "-o LogLevel=ERROR"]
|
|
66
|
+
ssh_opts << "-i #{ssh_key}" if ssh_key
|
|
67
|
+
ssh_opts << "-p #{server.port}" if server.port && server.port != "22"
|
|
68
|
+
ssh_opts << "-o ProxyJump=#{gateway}" if gateway
|
|
69
|
+
|
|
70
|
+
ssh_target = "#{server.user}@#{server.host}"
|
|
71
|
+
|
|
72
|
+
"ssh #{ssh_opts.join(" ")} #{ssh_target} #{Shellwords.shellescape(cmd)}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "bard/copy"
|
|
3
|
+
require "bard/command"
|
|
4
|
+
|
|
5
|
+
module Bard
|
|
6
|
+
module SSH
|
|
7
|
+
class Copy < Bard::Copy
|
|
8
|
+
def self.can_handle?(from, to)
|
|
9
|
+
from.has_capability?(:ssh) || to.has_capability?(:ssh)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def file
|
|
13
|
+
if from.key == :local
|
|
14
|
+
scp_using_local :to, to
|
|
15
|
+
elsif to.key == :local
|
|
16
|
+
scp_using_local :from, from
|
|
17
|
+
else
|
|
18
|
+
scp_as_mediator
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def scp_using_local direction, target
|
|
23
|
+
ssh_server = target.server
|
|
24
|
+
|
|
25
|
+
gateway = ssh_server.gateway ? "-oProxyCommand='ssh #{ssh_server.gateway} -W %h:%p'" : ""
|
|
26
|
+
|
|
27
|
+
ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
|
|
28
|
+
|
|
29
|
+
ssh_opts = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
|
|
30
|
+
|
|
31
|
+
port = ssh_server.port
|
|
32
|
+
port_opt = port && port.to_s != "22" ? "-P #{port}" : ""
|
|
33
|
+
|
|
34
|
+
from_and_to = [path, target.scp_uri(path).to_s]
|
|
35
|
+
from_and_to.reverse! if direction == :from
|
|
36
|
+
|
|
37
|
+
command = ["scp", ssh_opts, gateway, ssh_key, port_opt, *from_and_to].reject(&:empty?).join(" ")
|
|
38
|
+
Bard::Command.run! command, verbose: verbose
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def scp_as_mediator
|
|
42
|
+
from_server = from.server
|
|
43
|
+
to_server = to.server
|
|
44
|
+
|
|
45
|
+
raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
|
|
46
|
+
command = "scp -o ForwardAgent=yes #{from.scp_uri(path)} #{to.scp_uri(path)}"
|
|
47
|
+
Bard::Command.run! command, verbose: verbose
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def dir
|
|
51
|
+
if from.key == :local
|
|
52
|
+
rsync_using_local :to, to
|
|
53
|
+
elsif to.key == :local
|
|
54
|
+
rsync_using_local :from, from
|
|
55
|
+
else
|
|
56
|
+
rsync_as_mediator
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def rsync_using_local direction, target
|
|
61
|
+
ssh_server = target.server
|
|
62
|
+
|
|
63
|
+
ssh_uri = ssh_server.ssh_uri
|
|
64
|
+
|
|
65
|
+
gateway = ssh_server.gateway ? "-oProxyCommand=\"ssh #{ssh_server.gateway} -W %h:%p\"" : ""
|
|
66
|
+
|
|
67
|
+
ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
|
|
68
|
+
ssh = "-e'ssh #{gateway} -p#{ssh_uri.port || 22}'"
|
|
69
|
+
|
|
70
|
+
from_and_to = ["./#{path}", target.rsync_uri(path)]
|
|
71
|
+
from_and_to.reverse! if direction == :from
|
|
72
|
+
from_and_to[-1].sub! %r(/[^/]+$), '/'
|
|
73
|
+
|
|
74
|
+
command = "rsync #{ssh} --delete --info=progress2 -az #{from_and_to.join(" ")}"
|
|
75
|
+
Bard::Command.run! command, verbose: verbose
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rsync_as_mediator
|
|
79
|
+
from_server = from.server
|
|
80
|
+
to_server = to.server
|
|
81
|
+
|
|
82
|
+
raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
|
|
83
|
+
|
|
84
|
+
from_uri = from_server.ssh_uri
|
|
85
|
+
to_uri = to_server.ssh_uri
|
|
86
|
+
|
|
87
|
+
from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
|
|
88
|
+
to_str = to.rsync_uri(path).sub(%r(/[^/]+$), '/')
|
|
89
|
+
|
|
90
|
+
command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no -o LogLevel=ERROR\" --delete --info=progress2 -az #{from.path}/#{path} #{to_str}')
|
|
91
|
+
Bard::Command.run! command, verbose: verbose
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require "uri"
|
|
2
|
-
require "bard/command"
|
|
3
2
|
|
|
4
3
|
module Bard
|
|
5
4
|
class SSHServer
|
|
@@ -9,13 +8,11 @@ module Bard
|
|
|
9
8
|
@uri_string = uri_string
|
|
10
9
|
@options = options
|
|
11
10
|
|
|
12
|
-
# Parse URI
|
|
13
11
|
uri = parse_uri(uri_string)
|
|
14
12
|
@user = uri.user || ENV['USER']
|
|
15
13
|
@host = uri.host
|
|
16
14
|
@port = uri.port ? uri.port.to_s : "22"
|
|
17
15
|
|
|
18
|
-
# Store options
|
|
19
16
|
@path = options[:path]
|
|
20
17
|
@gateway = options[:gateway]
|
|
21
18
|
@ssh_key = options[:ssh_key]
|
|
@@ -23,39 +20,42 @@ module Bard
|
|
|
23
20
|
end
|
|
24
21
|
|
|
25
22
|
def ssh_uri
|
|
26
|
-
"
|
|
23
|
+
URI("ssh://#{user}@#{host}:#{port}")
|
|
27
24
|
end
|
|
28
25
|
|
|
29
26
|
def hostname
|
|
30
27
|
host
|
|
31
28
|
end
|
|
32
29
|
|
|
30
|
+
def to_s
|
|
31
|
+
str = "#{user}@#{host}"
|
|
32
|
+
str += ":#{port}" if port && port != "22"
|
|
33
|
+
str
|
|
34
|
+
end
|
|
35
|
+
|
|
33
36
|
def connection_string
|
|
34
37
|
"#{user}@#{host}"
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
def ==(other)
|
|
41
|
+
return false unless other.is_a?(Bard::SSHServer)
|
|
42
|
+
state == other.state
|
|
40
43
|
end
|
|
44
|
+
alias_method :eql?, :==
|
|
41
45
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
if status.to_i.nonzero?
|
|
45
|
-
raise Command::Error, "Command failed: #{command}\n#{error}"
|
|
46
|
-
end
|
|
47
|
-
output
|
|
46
|
+
def hash
|
|
47
|
+
state.hash
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def state
|
|
53
|
+
[user, host, port, path, gateway, ssh_key, env]
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
private
|
|
56
57
|
|
|
57
58
|
def parse_uri(uri_string)
|
|
58
|
-
# Handle user@host:port format
|
|
59
59
|
if uri_string =~ /^([^@]+@)?([^:]+)(?::(\d+))?$/
|
|
60
60
|
user_part = $1&.chomp('@')
|
|
61
61
|
host_part = $2
|
|
@@ -71,30 +71,5 @@ module Bard
|
|
|
71
71
|
URI.parse("ssh://#{uri_string}")
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
-
|
|
75
|
-
def build_command(command)
|
|
76
|
-
cmd = "ssh -tt"
|
|
77
|
-
|
|
78
|
-
# Add port
|
|
79
|
-
cmd += " -p #{port}" if port != "22"
|
|
80
|
-
|
|
81
|
-
# Add gateway
|
|
82
|
-
cmd += " -o ProxyJump=#{gateway}" if gateway
|
|
83
|
-
|
|
84
|
-
# Add SSH key
|
|
85
|
-
cmd += " -i #{ssh_key}" if ssh_key
|
|
86
|
-
|
|
87
|
-
# Add user@host
|
|
88
|
-
cmd += " #{user}@#{host}"
|
|
89
|
-
|
|
90
|
-
# Add command with path and env
|
|
91
|
-
remote_cmd = ""
|
|
92
|
-
remote_cmd += "#{env} " if env
|
|
93
|
-
remote_cmd += "cd #{path} && " if path
|
|
94
|
-
remote_cmd += command
|
|
95
|
-
|
|
96
|
-
cmd += " '#{remote_cmd}'"
|
|
97
|
-
cmd
|
|
98
|
-
end
|
|
99
74
|
end
|
|
100
75
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "bard/target"
|
|
2
|
+
require "bard/plugins/url/target_methods"
|
|
3
|
+
require "bard/plugins/ssh/connection"
|
|
4
|
+
|
|
5
|
+
class Bard::Target
|
|
6
|
+
def ssh(uri = nil, **options)
|
|
7
|
+
if uri.nil?
|
|
8
|
+
return @server
|
|
9
|
+
else
|
|
10
|
+
extend Bard::SSH
|
|
11
|
+
|
|
12
|
+
@server = Bard::SSHServer.new(uri, **options)
|
|
13
|
+
@path = options[:path] if options[:path]
|
|
14
|
+
enable_capability(:ssh)
|
|
15
|
+
|
|
16
|
+
hostname = @server.hostname
|
|
17
|
+
url("https://#{hostname}") if hostname
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require "bard/plugins/ssh/target_methods"
|
|
2
|
+
require "bard/plugins/ssh/copy"
|
|
3
|
+
|
|
4
|
+
class Bard::CLI
|
|
5
|
+
option :home, type: :boolean
|
|
6
|
+
desc "ssh [to=production]", "logs into the specified server via SSH"
|
|
7
|
+
def ssh(to = :production)
|
|
8
|
+
config[to].exec! "exec $SHELL -l", home: options[:home]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "bard/target"
|
|
2
|
+
|
|
3
|
+
class Bard::Target
|
|
4
|
+
def url(value = nil)
|
|
5
|
+
if value.nil?
|
|
6
|
+
@url
|
|
7
|
+
elsif value == false
|
|
8
|
+
@url = nil
|
|
9
|
+
@capabilities.delete(:url)
|
|
10
|
+
else
|
|
11
|
+
@url = normalize_url(value)
|
|
12
|
+
enable_capability(:url)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def normalize_url(value)
|
|
19
|
+
normalized = value.to_s
|
|
20
|
+
normalized = "https://#{normalized}" unless normalized.start_with?("http")
|
|
21
|
+
normalized
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bard/plugins/url/target_methods"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
class Bard::CLI
|
|
2
|
+
desc "vim [branch=master]", "open all files that have changed since master"
|
|
3
|
+
def vim(branch = "master")
|
|
4
|
+
exec "vim -p `(git diff #{branch} --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`"
|
|
5
|
+
end
|
|
6
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Bard
|
|
2
|
+
module Retryable
|
|
3
|
+
MAX_RETRIES = 5
|
|
4
|
+
INITIAL_DELAY = 1
|
|
5
|
+
|
|
6
|
+
def retry_with_backoff(max_retries: MAX_RETRIES)
|
|
7
|
+
retries = 0
|
|
8
|
+
delay = INITIAL_DELAY
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
yield
|
|
12
|
+
rescue => e
|
|
13
|
+
if retries < max_retries
|
|
14
|
+
retries += 1
|
|
15
|
+
puts " Network error (attempt #{retries}/#{max_retries}): #{e.message}. Retrying in #{delay}s..."
|
|
16
|
+
sleep(delay)
|
|
17
|
+
delay *= 2
|
|
18
|
+
retry
|
|
19
|
+
else
|
|
20
|
+
raise "Network error after #{max_retries} attempts: #{e.message}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/bard/secrets.rb
ADDED
data/lib/bard/target.rb
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
require "uri"
|
|
2
1
|
require "bard/command"
|
|
3
|
-
require "bard/copy"
|
|
4
|
-
require "bard/deploy_strategy"
|
|
5
2
|
|
|
6
3
|
module Bard
|
|
7
4
|
class Target
|
|
8
|
-
attr_reader :key, :config
|
|
9
|
-
attr_accessor :server, :gateway, :ssh_key, :env
|
|
5
|
+
attr_reader :key, :config
|
|
10
6
|
|
|
11
7
|
def initialize(key, config)
|
|
12
8
|
@key = key
|
|
13
9
|
@config = config
|
|
14
10
|
@capabilities = []
|
|
15
|
-
@ping_urls = []
|
|
16
|
-
@strategy_options_hash = {}
|
|
17
|
-
@deploy_strategy = nil
|
|
18
11
|
@path = nil
|
|
19
|
-
@server = nil
|
|
20
12
|
end
|
|
21
13
|
|
|
22
14
|
# Capability tracking
|
|
@@ -30,193 +22,25 @@ module Bard
|
|
|
30
22
|
|
|
31
23
|
def require_capability!(capability)
|
|
32
24
|
unless has_capability?(capability)
|
|
33
|
-
|
|
34
|
-
when :ssh
|
|
35
|
-
"SSH not configured for this target"
|
|
36
|
-
when :ping
|
|
37
|
-
"Ping URL not configured for this target"
|
|
38
|
-
else
|
|
39
|
-
"#{capability} capability not configured for this target"
|
|
40
|
-
end
|
|
41
|
-
raise error_message
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# SSH configuration
|
|
46
|
-
def ssh(uri_or_false = nil, **options)
|
|
47
|
-
if uri_or_false.nil?
|
|
48
|
-
# Getter - return false if explicitly disabled, otherwise return server
|
|
49
|
-
return @ssh_disabled ? false : @server
|
|
50
|
-
elsif uri_or_false == false
|
|
51
|
-
# Disable SSH
|
|
52
|
-
@server = nil
|
|
53
|
-
@ssh_disabled = true
|
|
54
|
-
@capabilities.delete(:ssh)
|
|
55
|
-
else
|
|
56
|
-
# Enable SSH
|
|
57
|
-
require "bard/ssh_server"
|
|
58
|
-
@server = SSHServer.new(uri_or_false, **options)
|
|
59
|
-
@path = options[:path] if options[:path]
|
|
60
|
-
@gateway = options[:gateway] if options[:gateway]
|
|
61
|
-
@ssh_key = options[:ssh_key] if options[:ssh_key]
|
|
62
|
-
@env = options[:env] if options[:env]
|
|
63
|
-
enable_capability(:ssh)
|
|
64
|
-
|
|
65
|
-
# Set SSH as default deployment strategy if none set
|
|
66
|
-
@deploy_strategy ||= :ssh
|
|
67
|
-
|
|
68
|
-
# Auto-configure ping from hostname
|
|
69
|
-
hostname = @server.hostname
|
|
70
|
-
ping(hostname) if hostname
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def ssh_uri
|
|
75
|
-
server&.ssh_uri
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Path configuration
|
|
79
|
-
def path(new_path = nil)
|
|
80
|
-
if new_path
|
|
81
|
-
@path = new_path
|
|
82
|
-
else
|
|
83
|
-
@path || config.project_name
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Ping configuration
|
|
88
|
-
def ping(*urls)
|
|
89
|
-
if urls.empty?
|
|
90
|
-
# Getter
|
|
91
|
-
@ping_urls
|
|
92
|
-
elsif urls.first == false
|
|
93
|
-
# Disable ping
|
|
94
|
-
@ping_urls = []
|
|
95
|
-
@capabilities.delete(:ping)
|
|
96
|
-
else
|
|
97
|
-
# Enable ping
|
|
98
|
-
@ping_urls = urls.flatten
|
|
99
|
-
enable_capability(:ping)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def ping_urls
|
|
104
|
-
@ping_urls
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def ping!
|
|
108
|
-
require_capability!(:ping)
|
|
109
|
-
require "bard/ping"
|
|
110
|
-
failed_urls = Bard::Ping.call(self)
|
|
111
|
-
if failed_urls.any?
|
|
112
|
-
raise "Ping failed for: #{failed_urls.join(', ')}"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def open
|
|
117
|
-
require_capability!(:ping)
|
|
118
|
-
system "open #{ping_urls.first}"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Deploy strategy
|
|
122
|
-
attr_reader :deploy_strategy
|
|
123
|
-
|
|
124
|
-
# GitHub Pages deployment configuration
|
|
125
|
-
def github_pages(url = nil)
|
|
126
|
-
if url.nil?
|
|
127
|
-
# Getter
|
|
128
|
-
@github_pages_url
|
|
129
|
-
else
|
|
130
|
-
# Setter
|
|
131
|
-
@deploy_strategy = :github_pages
|
|
132
|
-
@github_pages_url = url
|
|
133
|
-
enable_capability(:github_pages)
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def strategy_options(strategy_name)
|
|
138
|
-
@strategy_options_hash[strategy_name] || {}
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def deploy_strategy_instance
|
|
142
|
-
raise "No deployment strategy configured for target #{key}" unless @deploy_strategy
|
|
143
|
-
|
|
144
|
-
strategy_class = DeployStrategy[@deploy_strategy]
|
|
145
|
-
raise "Unknown deployment strategy: #{@deploy_strategy}" unless strategy_class
|
|
146
|
-
|
|
147
|
-
strategy_class.new(self)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Dynamic strategy DSL via method_missing
|
|
151
|
-
def method_missing(method, *args, **kwargs, &block)
|
|
152
|
-
strategy_class = DeployStrategy[method]
|
|
153
|
-
|
|
154
|
-
if strategy_class
|
|
155
|
-
# This is a deployment strategy
|
|
156
|
-
@deploy_strategy = method
|
|
157
|
-
|
|
158
|
-
# Store options
|
|
159
|
-
@strategy_options_hash[method] = kwargs
|
|
160
|
-
|
|
161
|
-
# Auto-configure ping if first arg is a URL
|
|
162
|
-
if args.first && args.first.to_s =~ /^https?:\/\//
|
|
163
|
-
ping(args.first)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Call the strategy's initializer if it wants to configure the target
|
|
167
|
-
# (This will be handled by the strategy class)
|
|
168
|
-
else
|
|
169
|
-
super
|
|
25
|
+
raise "#{capability} capability not configured for this target"
|
|
170
26
|
end
|
|
171
27
|
end
|
|
172
28
|
|
|
173
|
-
def
|
|
174
|
-
|
|
29
|
+
def path
|
|
30
|
+
@path || config.project_name
|
|
175
31
|
end
|
|
176
32
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Command.run!(command, on: server, home: home, verbose: verbose, quiet: quiet)
|
|
33
|
+
def run!(command, home: false, verbose: false, quiet: false, capture: false)
|
|
34
|
+
result = Command.run!(command, verbose:, quiet:)
|
|
35
|
+
result if capture
|
|
181
36
|
end
|
|
182
37
|
|
|
183
38
|
def run(command, home: false, verbose: false, quiet: false)
|
|
184
|
-
|
|
185
|
-
Command.run(command, on: server, home: home, verbose: verbose, quiet: quiet)
|
|
39
|
+
Command.run(command, verbose:, quiet:)
|
|
186
40
|
end
|
|
187
41
|
|
|
188
42
|
def exec!(command, home: false)
|
|
189
|
-
|
|
190
|
-
Command.exec!(command, on: server, home: home)
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# File transfer
|
|
194
|
-
def copy_file(path, to:, verbose: false)
|
|
195
|
-
require_capability!(:ssh)
|
|
196
|
-
to.require_capability!(:ssh)
|
|
197
|
-
Copy.file(path, from: self, to: to, verbose: verbose)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def copy_dir(path, to:, verbose: false)
|
|
201
|
-
require_capability!(:ssh)
|
|
202
|
-
to.require_capability!(:ssh)
|
|
203
|
-
Copy.dir(path, from: self, to: to, verbose: verbose)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# URI methods for compatibility
|
|
207
|
-
def scp_uri(file_path = nil)
|
|
208
|
-
uri = URI("scp://#{ssh_uri}")
|
|
209
|
-
uri.path = "/#{path}"
|
|
210
|
-
uri.path += "/#{file_path}" if file_path
|
|
211
|
-
uri
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def rsync_uri(file_path = nil)
|
|
215
|
-
uri = URI("ssh://#{ssh_uri}")
|
|
216
|
-
str = "#{uri.user}@#{uri.host}"
|
|
217
|
-
str += ":#{path}"
|
|
218
|
-
str += "/#{file_path}" if file_path
|
|
219
|
-
str
|
|
43
|
+
Command.exec!(command)
|
|
220
44
|
end
|
|
221
45
|
|
|
222
46
|
# Utility methods
|
|
@@ -235,5 +59,23 @@ module Bard
|
|
|
235
59
|
end
|
|
236
60
|
end
|
|
237
61
|
end
|
|
62
|
+
|
|
63
|
+
def ==(other)
|
|
64
|
+
return false unless other.is_a?(Bard::Target)
|
|
65
|
+
comparable_state == other.comparable_state
|
|
66
|
+
end
|
|
67
|
+
alias_method :eql?, :==
|
|
68
|
+
|
|
69
|
+
def hash
|
|
70
|
+
comparable_state.hash
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
def comparable_state
|
|
76
|
+
(instance_variables - [:@key, :@config]).sort.map do |ivar|
|
|
77
|
+
[ivar, instance_variable_get(ivar)]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
238
80
|
end
|
|
239
81
|
end
|
data/lib/bard/version.rb
CHANGED
data/lib/bard.rb
CHANGED