bard 1.9.6 → 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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/CLAUDE.md +1 -1
  4. data/PLUGINS.md +31 -46
  5. data/bard.gemspec +2 -1
  6. data/features/ci.feature +1 -0
  7. data/features/data.feature +1 -0
  8. data/features/deploy.feature +1 -0
  9. data/features/deploy_git_workflow.feature +1 -0
  10. data/features/run.feature +1 -0
  11. data/features/step_definitions/bard_steps.rb +1 -0
  12. data/features/support/test_server.rb +3 -3
  13. data/lib/bard/cli.rb +9 -28
  14. data/lib/bard/command.rb +9 -88
  15. data/lib/bard/config.rb +38 -177
  16. data/lib/bard/copy.rb +28 -82
  17. data/lib/bard/plugins/data.rb +56 -0
  18. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +2 -2
  19. data/lib/bard/{ci → plugins/deploy/ci}/jenkins.rb +1 -1
  20. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +1 -1
  21. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +3 -3
  22. data/lib/bard/{ci.rb → plugins/deploy/ci.rb} +1 -1
  23. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  24. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -4
  25. data/lib/bard/plugins/deploy.rb +240 -0
  26. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  27. data/lib/bard/{github.rb → plugins/github.rb} +2 -2
  28. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +3 -4
  29. data/lib/bard/plugins/github_pages.rb +11 -15
  30. data/lib/bard/plugins/hurt.rb +12 -4
  31. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  32. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  33. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  34. data/lib/bard/plugins/install.rb +7 -3
  35. data/lib/bard/plugins/open.rb +20 -0
  36. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  37. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  38. data/lib/bard/plugins/ping.rb +8 -4
  39. data/lib/bard/plugins/run.rb +19 -0
  40. data/lib/bard/plugins/setup.rb +54 -0
  41. data/lib/bard/plugins/ssh/connection.rb +75 -0
  42. data/lib/bard/plugins/ssh/copy.rb +95 -0
  43. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +10 -42
  44. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  45. data/lib/bard/plugins/ssh.rb +10 -0
  46. data/lib/bard/plugins/url/target_methods.rb +23 -0
  47. data/lib/bard/plugins/url.rb +1 -0
  48. data/lib/bard/plugins/vim.rb +5 -4
  49. data/lib/bard/retryable.rb +25 -0
  50. data/lib/bard/target.rb +21 -230
  51. data/lib/bard/version.rb +1 -1
  52. data/lib/bard.rb +1 -3
  53. data/spec/acceptance/docker/Dockerfile +1 -1
  54. data/spec/bard/capability_spec.rb +8 -50
  55. data/spec/bard/ci/github_actions_spec.rb +1 -1
  56. data/spec/bard/ci/jenkins_spec.rb +1 -1
  57. data/spec/bard/ci/runner_spec.rb +3 -3
  58. data/spec/bard/ci_spec.rb +1 -1
  59. data/spec/bard/cli/ci_spec.rb +4 -23
  60. data/spec/bard/cli/data_spec.rb +7 -26
  61. data/spec/bard/cli/deploy_spec.rb +43 -40
  62. data/spec/bard/cli/hurt_spec.rb +2 -8
  63. data/spec/bard/cli/install_spec.rb +4 -10
  64. data/spec/bard/cli/master_key_spec.rb +5 -19
  65. data/spec/bard/cli/open_spec.rb +17 -35
  66. data/spec/bard/cli/ping_spec.rb +10 -25
  67. data/spec/bard/cli/run_spec.rb +10 -23
  68. data/spec/bard/cli/setup_spec.rb +10 -27
  69. data/spec/bard/cli/ssh_spec.rb +10 -25
  70. data/spec/bard/cli/stage_spec.rb +17 -32
  71. data/spec/bard/cli/vim_spec.rb +5 -11
  72. data/spec/bard/command_spec.rb +1 -10
  73. data/spec/bard/config_spec.rb +68 -116
  74. data/spec/bard/copy_spec.rb +54 -18
  75. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  76. data/spec/bard/deploy_strategy_spec.rb +1 -1
  77. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  78. data/spec/bard/git_spec.rb +9 -5
  79. data/spec/bard/github_spec.rb +1 -1
  80. data/spec/bard/ping_spec.rb +5 -5
  81. data/spec/bard/ssh_copy_spec.rb +44 -0
  82. data/spec/bard/ssh_server_spec.rb +1 -98
  83. data/spec/bard/target_spec.rb +61 -108
  84. metadata +50 -124
  85. data/lib/bard/ci/retryable.rb +0 -27
  86. data/lib/bard/cli/ci.rb +0 -73
  87. data/lib/bard/cli/command.rb +0 -26
  88. data/lib/bard/cli/data.rb +0 -45
  89. data/lib/bard/cli/deploy.rb +0 -116
  90. data/lib/bard/cli/hurt.rb +0 -15
  91. data/lib/bard/cli/install.rb +0 -11
  92. data/lib/bard/cli/master_key.rb +0 -17
  93. data/lib/bard/cli/new.rb +0 -101
  94. data/lib/bard/cli/new_rails_template.rb +0 -197
  95. data/lib/bard/cli/open.rb +0 -18
  96. data/lib/bard/cli/ping.rb +0 -12
  97. data/lib/bard/cli/provision.rb +0 -34
  98. data/lib/bard/cli/run.rb +0 -26
  99. data/lib/bard/cli/setup.rb +0 -56
  100. data/lib/bard/cli/ssh.rb +0 -14
  101. data/lib/bard/cli/stage.rb +0 -35
  102. data/lib/bard/cli/vim.rb +0 -8
  103. data/lib/bard/default_config.rb +0 -35
  104. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  105. data/lib/bard/deprecation.rb +0 -19
  106. data/lib/bard/github_pages.rb +0 -134
  107. data/lib/bard/plugin.rb +0 -100
  108. data/lib/bard/plugins/backup.rb +0 -19
  109. data/lib/bard/plugins/jenkins.rb +0 -6
  110. data/lib/bard/plugins/new.rb +0 -5
  111. data/lib/bard/plugins/provision.rb +0 -5
  112. data/lib/bard/provision/app.rb +0 -10
  113. data/lib/bard/provision/apt.rb +0 -16
  114. data/lib/bard/provision/authorizedkeys.rb +0 -25
  115. data/lib/bard/provision/data.rb +0 -27
  116. data/lib/bard/provision/deploy.rb +0 -10
  117. data/lib/bard/provision/http.rb +0 -16
  118. data/lib/bard/provision/logrotation.rb +0 -30
  119. data/lib/bard/provision/masterkey.rb +0 -18
  120. data/lib/bard/provision/mysql.rb +0 -22
  121. data/lib/bard/provision/passenger.rb +0 -37
  122. data/lib/bard/provision/repo.rb +0 -72
  123. data/lib/bard/provision/rvm.rb +0 -22
  124. data/lib/bard/provision/ssh.rb +0 -79
  125. data/lib/bard/provision/swapfile.rb +0 -23
  126. data/lib/bard/provision/user.rb +0 -42
  127. data/lib/bard/provision.rb +0 -16
  128. data/lib/bard/server.rb +0 -160
  129. data/spec/bard/cli/command_spec.rb +0 -50
  130. data/spec/bard/cli/new_spec.rb +0 -73
  131. data/spec/bard/cli/provision_spec.rb +0 -42
  132. data/spec/bard/deprecation_spec.rb +0 -281
  133. data/spec/bard/github_pages_spec.rb +0 -143
  134. data/spec/bard/plugin_spec.rb +0 -79
  135. data/spec/bard/provision/app_spec.rb +0 -33
  136. data/spec/bard/provision/apt_spec.rb +0 -39
  137. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  138. data/spec/bard/provision/data_spec.rb +0 -54
  139. data/spec/bard/provision/deploy_spec.rb +0 -33
  140. data/spec/bard/provision/http_spec.rb +0 -57
  141. data/spec/bard/provision/logrotation_spec.rb +0 -34
  142. data/spec/bard/provision/masterkey_spec.rb +0 -63
  143. data/spec/bard/provision/mysql_spec.rb +0 -55
  144. data/spec/bard/provision/passenger_spec.rb +0 -81
  145. data/spec/bard/provision/repo_spec.rb +0 -208
  146. data/spec/bard/provision/rvm_spec.rb +0 -49
  147. data/spec/bard/provision/ssh_spec.rb +0 -242
  148. data/spec/bard/provision/swapfile_spec.rb +0 -33
  149. data/spec/bard/provision/user_spec.rb +0 -103
  150. data/spec/bard/provision_spec.rb +0 -28
  151. data/spec/bard/server_spec.rb +0 -127
  152. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  153. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  154. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  155. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  156. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  157. /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
@@ -1,5 +1,9 @@
1
- require "bard/plugin"
1
+ class Bard::CLI
2
+ desc "install", "copies bin/setup and bin/ci scripts into current project."
3
+ def install
4
+ install_files_path = File.expand_path("install", __dir__)
2
5
 
3
- Bard::Plugin.register :install do
4
- cli "Bard::CLI::Install", require: "bard/cli/install"
6
+ system "cp -R #{install_files_path}/* bin/"
7
+ system "cp -R #{install_files_path}/.github ./"
8
+ end
5
9
  end
@@ -0,0 +1,20 @@
1
+ require "bard/plugins/url"
2
+
3
+ class Bard::CLI
4
+ desc "open [target=production]", "opens the url in the web browser."
5
+ def open(target = :production)
6
+ exec "xdg-open #{open_url target}"
7
+ end
8
+
9
+ no_commands do
10
+ def open_url(target)
11
+ if target.to_sym == :ci
12
+ "https://github.com/botandrosedesign/#{project_name}/actions/workflows/ci.yml"
13
+ else
14
+ t = config[target]
15
+ t.require_capability!(:url)
16
+ t.url
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,13 +2,13 @@ require "net/http"
2
2
  require "uri"
3
3
 
4
4
  module Bard
5
- class Ping < Struct.new(:server)
6
- def self.call server
7
- new(server).call
5
+ class Ping < Struct.new(:target)
6
+ def self.call target
7
+ new(target).call
8
8
  end
9
9
 
10
10
  def call
11
- server.ping.reject { |url| reachable?(url) }
11
+ target.ping.reject { |url| reachable?(url) }
12
12
  end
13
13
 
14
14
  private
@@ -0,0 +1,23 @@
1
+ require "bard/target"
2
+ require "bard/plugins/url/target_methods"
3
+ require "bard/plugins/ping/check"
4
+
5
+ class Bard::Target
6
+ def ping(*urls)
7
+ if urls.empty?
8
+ @ping_urls || [url].compact
9
+ elsif urls.first == false
10
+ @ping_urls = []
11
+ else
12
+ @ping_urls = urls.flatten.map { |u| normalize_url(u) }
13
+ end
14
+ end
15
+
16
+ def ping!
17
+ require_capability!(:url)
18
+ failed_urls = Bard::Ping.call(self)
19
+ if failed_urls.any?
20
+ raise "Ping failed for: #{failed_urls.join(", ")}"
21
+ end
22
+ end
23
+ end
@@ -1,6 +1,10 @@
1
- require "bard/plugin"
1
+ require "bard/plugins/ping/target_methods"
2
2
 
3
- Bard::Plugin.register :ping do
4
- cli "Bard::CLI::Ping", require: "bard/cli/ping"
5
- cli "Bard::CLI::Open", require: "bard/cli/open"
3
+ class Bard::CLI
4
+ desc "ping [target=production]", "hits the target over http to verify that its up."
5
+ def ping(target = :production)
6
+ down_urls = Bard::Ping.call(config[target])
7
+ down_urls.each { |url| puts "#{url} is down!" }
8
+ exit 1 if down_urls.any?
9
+ end
6
10
  end
@@ -0,0 +1,19 @@
1
+ require "bard/command"
2
+
3
+ class Bard::CLI
4
+ # HACK: we don't use Thor::Base#run, so its okay to stomp on it here
5
+ original_verbose, $VERBOSE = $VERBOSE, nil
6
+ Thor::THOR_RESERVED_WORDS -= ["run"]
7
+ $VERBOSE = original_verbose
8
+
9
+ option :target, type: :string, default: "production"
10
+ option :home, type: :boolean
11
+ desc "run <command>", "run the given command on the specified target"
12
+ def run(*args)
13
+ target = config[options[:target].to_sym]
14
+ target.run!(*args.join(" "), verbose: true, home: options[:home])
15
+ rescue Bard::Command::Error => e
16
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
17
+ exit 1
18
+ end
19
+ end
@@ -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,6 +1,4 @@
1
1
  require "uri"
2
- require "shellwords"
3
- require "bard/command"
4
2
 
5
3
  module Bard
6
4
  class SSHServer
@@ -10,13 +8,11 @@ module Bard
10
8
  @uri_string = uri_string
11
9
  @options = options
12
10
 
13
- # Parse URI
14
11
  uri = parse_uri(uri_string)
15
12
  @user = uri.user || ENV['USER']
16
13
  @host = uri.host
17
14
  @port = uri.port ? uri.port.to_s : "22"
18
15
 
19
- # Store options
20
16
  @path = options[:path]
21
17
  @gateway = options[:gateway]
22
18
  @ssh_key = options[:ssh_key]
@@ -41,28 +37,25 @@ module Bard
41
37
  "#{user}@#{host}"
42
38
  end
43
39
 
44
- def run(command)
45
- full_command = build_command(command)
46
- Open3.capture3(full_command)
40
+ def ==(other)
41
+ return false unless other.is_a?(Bard::SSHServer)
42
+ state == other.state
47
43
  end
44
+ alias_method :eql?, :==
48
45
 
49
- def run!(command)
50
- output, error, status = run(command)
51
- if status.to_i.nonzero?
52
- raise Command::Error, "Command failed: #{command}\n#{error}"
53
- end
54
- output
46
+ def hash
47
+ state.hash
55
48
  end
56
49
 
57
- def exec!(command)
58
- full_command = build_command(command)
59
- exec(full_command)
50
+ protected
51
+
52
+ def state
53
+ [user, host, port, path, gateway, ssh_key, env]
60
54
  end
61
55
 
62
56
  private
63
57
 
64
58
  def parse_uri(uri_string)
65
- # Handle user@host:port format
66
59
  if uri_string =~ /^([^@]+@)?([^:]+)(?::(\d+))?$/
67
60
  user_part = $1&.chomp('@')
68
61
  host_part = $2
@@ -78,30 +71,5 @@ module Bard
78
71
  URI.parse("ssh://#{uri_string}")
79
72
  end
80
73
  end
81
-
82
- def build_command(command)
83
- cmd = "ssh -tt"
84
-
85
- # Add port
86
- cmd += " -p #{port}" if port != "22"
87
-
88
- # Add gateway
89
- cmd += " -o ProxyJump=#{gateway}" if gateway
90
-
91
- # Add SSH key
92
- cmd += " -i #{ssh_key}" if ssh_key
93
-
94
- # Add user@host
95
- cmd += " #{user}@#{host}"
96
-
97
- # Add command with path and env
98
- remote_cmd = ""
99
- remote_cmd += "#{env} " if env
100
- remote_cmd += "cd #{path} && " if path
101
- remote_cmd += command
102
-
103
- cmd += " #{Shellwords.shellescape(remote_cmd)}"
104
- cmd
105
- end
106
74
  end
107
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"
@@ -1,5 +1,6 @@
1
- require "bard/plugin"
2
-
3
- Bard::Plugin.register :vim do
4
- cli "Bard::CLI::Vim", require: "bard/cli/vim"
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
5
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