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.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -1
  3. data/CLAUDE.md +76 -0
  4. data/MIGRATION_GUIDE.md +24 -9
  5. data/PLUGINS.md +99 -0
  6. data/README.md +14 -6
  7. data/Rakefile +3 -1
  8. data/bard.gemspec +2 -1
  9. data/cucumber.yml +1 -0
  10. data/features/ci.feature +63 -0
  11. data/features/data.feature +13 -0
  12. data/features/deploy.feature +14 -0
  13. data/features/deploy_git_workflow.feature +89 -0
  14. data/features/run.feature +14 -0
  15. data/features/step_definitions/bard_steps.rb +136 -0
  16. data/features/support/bard-coverage +16 -0
  17. data/features/support/env.rb +14 -39
  18. data/features/support/test_server.rb +216 -0
  19. data/lib/bard/cli.rb +14 -31
  20. data/lib/bard/command.rb +10 -69
  21. data/lib/bard/config.rb +40 -183
  22. data/lib/bard/copy.rb +28 -103
  23. data/lib/bard/plugins/data.rb +56 -0
  24. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
  25. data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
  26. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
  27. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
  28. data/lib/bard/plugins/deploy/ci.rb +38 -0
  29. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  30. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
  31. data/lib/bard/plugins/deploy.rb +240 -0
  32. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  33. data/lib/bard/{github.rb → plugins/github.rb} +4 -6
  34. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +41 -13
  35. data/lib/bard/plugins/github_pages.rb +30 -0
  36. data/lib/bard/plugins/hurt.rb +13 -0
  37. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  38. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  39. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  40. data/lib/bard/plugins/install.rb +9 -0
  41. data/lib/bard/plugins/open.rb +20 -0
  42. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  43. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  44. data/lib/bard/plugins/ping.rb +10 -0
  45. data/lib/bard/plugins/run.rb +19 -0
  46. data/lib/bard/plugins/setup.rb +54 -0
  47. data/lib/bard/plugins/ssh/connection.rb +75 -0
  48. data/lib/bard/plugins/ssh/copy.rb +95 -0
  49. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
  50. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  51. data/lib/bard/plugins/ssh.rb +10 -0
  52. data/lib/bard/plugins/url/target_methods.rb +23 -0
  53. data/lib/bard/plugins/url.rb +1 -0
  54. data/lib/bard/plugins/vim.rb +6 -0
  55. data/lib/bard/retryable.rb +25 -0
  56. data/lib/bard/secrets.rb +10 -0
  57. data/lib/bard/target.rb +27 -185
  58. data/lib/bard/version.rb +1 -1
  59. data/lib/bard.rb +1 -3
  60. data/spec/acceptance/docker/Dockerfile +3 -2
  61. data/spec/bard/capability_spec.rb +8 -50
  62. data/spec/bard/ci/github_actions_spec.rb +117 -14
  63. data/spec/bard/ci/jenkins_spec.rb +139 -0
  64. data/spec/bard/ci/runner_spec.rb +61 -0
  65. data/spec/bard/ci_spec.rb +1 -1
  66. data/spec/bard/cli/ci_spec.rb +34 -27
  67. data/spec/bard/cli/data_spec.rb +7 -26
  68. data/spec/bard/cli/deploy_spec.rb +87 -46
  69. data/spec/bard/cli/hurt_spec.rb +3 -9
  70. data/spec/bard/cli/install_spec.rb +5 -11
  71. data/spec/bard/cli/master_key_spec.rb +5 -19
  72. data/spec/bard/cli/open_spec.rb +14 -30
  73. data/spec/bard/cli/ping_spec.rb +8 -23
  74. data/spec/bard/cli/run_spec.rb +27 -21
  75. data/spec/bard/cli/setup_spec.rb +10 -27
  76. data/spec/bard/cli/ssh_spec.rb +10 -25
  77. data/spec/bard/cli/stage_spec.rb +28 -23
  78. data/spec/bard/cli/vim_spec.rb +3 -9
  79. data/spec/bard/command_spec.rb +1 -8
  80. data/spec/bard/config_spec.rb +78 -98
  81. data/spec/bard/copy_spec.rb +54 -18
  82. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  83. data/spec/bard/deploy_strategy_spec.rb +1 -1
  84. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  85. data/spec/bard/git_spec.rb +9 -5
  86. data/spec/bard/github_spec.rb +2 -2
  87. data/spec/bard/ping_spec.rb +5 -5
  88. data/spec/bard/ssh_copy_spec.rb +44 -0
  89. data/spec/bard/ssh_server_spec.rb +8 -101
  90. data/spec/bard/target_spec.rb +66 -109
  91. data/spec/spec_helper.rb +6 -1
  92. metadata +79 -143
  93. data/README.rdoc +0 -15
  94. data/features/bard_check.feature +0 -94
  95. data/features/bard_deploy.feature +0 -18
  96. data/features/bard_pull.feature +0 -112
  97. data/features/bard_push.feature +0 -112
  98. data/features/podman_testcontainers.feature +0 -16
  99. data/features/step_definitions/check_steps.rb +0 -47
  100. data/features/step_definitions/git_steps.rb +0 -73
  101. data/features/step_definitions/global_steps.rb +0 -56
  102. data/features/step_definitions/podman_steps.rb +0 -23
  103. data/features/step_definitions/rails_steps.rb +0 -44
  104. data/features/step_definitions/submodule_steps.rb +0 -110
  105. data/features/support/grit_ext.rb +0 -13
  106. data/features/support/io.rb +0 -32
  107. data/features/support/podman.rb +0 -153
  108. data/lib/bard/ci/jenkins.rb +0 -105
  109. data/lib/bard/ci/retryable.rb +0 -27
  110. data/lib/bard/ci.rb +0 -50
  111. data/lib/bard/cli/ci.rb +0 -66
  112. data/lib/bard/cli/command.rb +0 -26
  113. data/lib/bard/cli/data.rb +0 -45
  114. data/lib/bard/cli/deploy.rb +0 -85
  115. data/lib/bard/cli/hurt.rb +0 -20
  116. data/lib/bard/cli/install.rb +0 -16
  117. data/lib/bard/cli/master_key.rb +0 -17
  118. data/lib/bard/cli/new.rb +0 -101
  119. data/lib/bard/cli/new_rails_template.rb +0 -197
  120. data/lib/bard/cli/open.rb +0 -22
  121. data/lib/bard/cli/ping.rb +0 -18
  122. data/lib/bard/cli/provision.rb +0 -34
  123. data/lib/bard/cli/run.rb +0 -24
  124. data/lib/bard/cli/setup.rb +0 -56
  125. data/lib/bard/cli/ssh.rb +0 -14
  126. data/lib/bard/cli/stage.rb +0 -27
  127. data/lib/bard/cli/vim.rb +0 -13
  128. data/lib/bard/default_config.rb +0 -35
  129. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  130. data/lib/bard/github_pages.rb +0 -134
  131. data/lib/bard/provision/app.rb +0 -10
  132. data/lib/bard/provision/apt.rb +0 -16
  133. data/lib/bard/provision/authorizedkeys.rb +0 -25
  134. data/lib/bard/provision/data.rb +0 -27
  135. data/lib/bard/provision/deploy.rb +0 -10
  136. data/lib/bard/provision/http.rb +0 -16
  137. data/lib/bard/provision/logrotation.rb +0 -30
  138. data/lib/bard/provision/masterkey.rb +0 -18
  139. data/lib/bard/provision/mysql.rb +0 -22
  140. data/lib/bard/provision/passenger.rb +0 -37
  141. data/lib/bard/provision/repo.rb +0 -72
  142. data/lib/bard/provision/rvm.rb +0 -22
  143. data/lib/bard/provision/ssh.rb +0 -72
  144. data/lib/bard/provision/swapfile.rb +0 -21
  145. data/lib/bard/provision/user.rb +0 -42
  146. data/lib/bard/provision.rb +0 -16
  147. data/lib/bard/server.rb +0 -117
  148. data/spec/bard/cli/command_spec.rb +0 -50
  149. data/spec/bard/cli/new_spec.rb +0 -73
  150. data/spec/bard/cli/provision_spec.rb +0 -42
  151. data/spec/bard/github_pages_spec.rb +0 -143
  152. data/spec/bard/provision/app_spec.rb +0 -33
  153. data/spec/bard/provision/apt_spec.rb +0 -39
  154. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  155. data/spec/bard/provision/data_spec.rb +0 -54
  156. data/spec/bard/provision/deploy_spec.rb +0 -33
  157. data/spec/bard/provision/http_spec.rb +0 -57
  158. data/spec/bard/provision/logrotation_spec.rb +0 -34
  159. data/spec/bard/provision/masterkey_spec.rb +0 -63
  160. data/spec/bard/provision/mysql_spec.rb +0 -55
  161. data/spec/bard/provision/passenger_spec.rb +0 -81
  162. data/spec/bard/provision/repo_spec.rb +0 -208
  163. data/spec/bard/provision/rvm_spec.rb +0 -49
  164. data/spec/bard/provision/ssh_spec.rb +0 -229
  165. data/spec/bard/provision/swapfile_spec.rb +0 -32
  166. data/spec/bard/provision/user_spec.rb +0 -103
  167. data/spec/bard/provision_spec.rb +0 -28
  168. data/spec/bard/server_spec.rb +0 -127
  169. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  170. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  171. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  172. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  173. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  174. /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
@@ -0,0 +1,10 @@
1
+ require "bard/plugins/ping/target_methods"
2
+
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
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,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
- "#{user}@#{host}:#{port}"
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 run(command)
38
- full_command = build_command(command)
39
- Open3.capture3(full_command)
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 run!(command)
43
- output, error, status = run(command)
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
- def exec!(command)
51
- full_command = build_command(command)
52
- exec(full_command)
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
@@ -0,0 +1,10 @@
1
+ module Bard
2
+ module Secrets
3
+ REPO = "git@github.com:botandrosedesign/secrets"
4
+
5
+ def self.fetch(key)
6
+ raw = `git ls-remote -t #{REPO}`
7
+ raw[/#{Regexp.escape(key)}\|(.+)$/, 1]
8
+ end
9
+ end
10
+ end