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
data/lib/bard/copy.rb CHANGED
@@ -1,99 +1,45 @@
1
- require "uri"
2
- require "bard/command"
3
-
4
1
  module Bard
5
- class Copy < Struct.new(:path, :from, :to, :verbose)
6
- def self.file path, from:, to:, verbose: false
7
- new(path, from, to, verbose).scp
8
- end
9
-
10
- def self.dir path, from:, to:, verbose: false
11
- new(path, from, to, verbose).rsync
12
- end
2
+ class Copy
3
+ @handlers = []
13
4
 
14
- def scp
15
- if from.key == :local
16
- scp_using_local :to, to
17
- elsif to.key == :local
18
- scp_using_local :from, from
19
- else
20
- scp_as_mediator
5
+ class << self
6
+ def inherited(subclass)
7
+ super
8
+ @handlers.unshift(subclass)
21
9
  end
22
- end
23
-
24
- def scp_using_local direction, target_or_server
25
- # Support both new Target (with server attribute) and old Server
26
- ssh_server = target_or_server.respond_to?(:server) ? target_or_server.server : target_or_server
27
10
 
28
- gateway = ssh_server.gateway ? "-oProxyCommand='ssh #{ssh_server.gateway} -W %h:%p'" : ""
29
-
30
- ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
31
-
32
- ssh_opts = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
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]
39
- from_and_to.reverse! if direction == :from
40
-
41
- command = ["scp", ssh_opts, gateway, ssh_key, port_opt, *from_and_to].reject(&:empty?).join(" ")
42
- Bard::Command.run! command, verbose: verbose
43
- end
11
+ def file(path, from:, to:, verbose: false)
12
+ handler_for!(from, to).new(path, from, to, verbose).file
13
+ end
44
14
 
45
- def scp_as_mediator
46
- from_server = from.respond_to?(:server) ? from.server : from
47
- to_server = to.respond_to?(:server) ? to.server : to
15
+ def dir(path, from:, to:, verbose: false)
16
+ handler_for!(from, to).new(path, from, to, verbose).dir
17
+ end
48
18
 
49
- raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
50
- command = "scp -o ForwardAgent=yes #{from.scp_uri(path)} #{to.scp_uri(path)}"
51
- Bard::Command.run! command, verbose: verbose
52
- end
19
+ private
53
20
 
54
- def rsync
55
- if from.key == :local
56
- rsync_using_local :to, to
57
- elsif to.key == :local
58
- rsync_using_local :from, from
59
- else
60
- rsync_as_mediator
21
+ def handler_for!(from, to)
22
+ handler = @handlers.find { |h| h.can_handle?(from, to) }
23
+ raise "No copy handler for #{from.key} -> #{to.key}" unless handler
24
+ handler
61
25
  end
62
26
  end
63
27
 
64
- def rsync_using_local direction, target_or_server
65
- # Support both new Target (with server attribute) and old Server
66
- ssh_server = target_or_server.respond_to?(:server) ? target_or_server.server : target_or_server
67
-
68
- ssh_uri = ssh_server.ssh_uri
69
-
70
- gateway = ssh_server.gateway ? "-oProxyCommand=\"ssh #{ssh_server.gateway} -W %h:%p\"" : ""
71
-
72
- ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
73
- ssh = "-e'ssh #{gateway} -p#{ssh_uri.port || 22}'"
28
+ attr_reader :path, :from, :to, :verbose
74
29
 
75
- from_and_to = ["./#{path}", target_or_server.rsync_uri(path)]
76
- from_and_to.reverse! if direction == :from
77
- from_and_to[-1].sub! %r(/[^/]+$), '/'
78
-
79
- command = "rsync #{ssh} --delete --info=progress2 -az #{from_and_to.join(" ")}"
80
- Bard::Command.run! command, verbose: verbose
30
+ def initialize(path, from, to, verbose)
31
+ @path = path
32
+ @from = from
33
+ @to = to
34
+ @verbose = verbose
81
35
  end
82
36
 
83
- def rsync_as_mediator
84
- from_server = from.respond_to?(:server) ? from.server : from
85
- to_server = to.respond_to?(:server) ? to.server : to
86
-
87
- raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
88
-
89
- from_uri = from_server.ssh_uri
90
- to_uri = to_server.ssh_uri
91
-
92
- from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
93
- to_str = to.rsync_uri(path).sub(%r(/[^/]+$), '/')
37
+ def file
38
+ raise NotImplementedError
39
+ end
94
40
 
95
- 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}')
96
- Bard::Command.run! command, verbose: verbose
41
+ def dir
42
+ raise NotImplementedError
97
43
  end
98
44
  end
99
45
  end
@@ -0,0 +1,56 @@
1
+ require "bard/command"
2
+ require "bard/copy"
3
+ require "bard/plugins/ssh"
4
+ require "bard/plugins/url"
5
+
6
+ class Bard::CLI
7
+ option :from, default: "production"
8
+ option :to, default: "local"
9
+ desc "data --from=production --to=local", "copy database and assets from from to to"
10
+ def data
11
+ from = config[options[:from]]
12
+ to = config[options[:to]]
13
+
14
+ from.require_capability!(:ssh) unless from.key == :local
15
+ to.require_capability!(:ssh) unless to.key == :local
16
+
17
+ if to.key == :production
18
+ url = to.url
19
+ puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
20
+ answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
21
+ if answer != url
22
+ puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
23
+ exit 1
24
+ end
25
+ end
26
+
27
+ puts "Dumping #{from.key} database to file..."
28
+ from.run! "bin/rake db:dump"
29
+
30
+ puts "Transfering file from #{from.key} to #{to.key}..."
31
+ Bard::Copy.file "db/data.sql.gz", from: from, to: to, verbose: true
32
+
33
+ puts "Loading file into #{to.key} database..."
34
+ to.run! "bin/rake db:load"
35
+
36
+ config.data.each do |path|
37
+ puts "Synchronizing files in #{path}..."
38
+ Bard::Copy.dir path, from: from, to: to, verbose: true
39
+ end
40
+ rescue Bard::Command::Error => e
41
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
42
+ exit 1
43
+ end
44
+ end
45
+
46
+ require "bard/config"
47
+
48
+ class Bard::Config
49
+ def data(*paths)
50
+ if paths.empty?
51
+ @data_paths ||= []
52
+ else
53
+ @data_paths = paths
54
+ end
55
+ end
56
+ end
@@ -1,6 +1,6 @@
1
1
  require "time"
2
- require "bard/github"
3
- require "bard/ci/runner"
2
+ require "bard/plugins/github"
3
+ require "bard/plugins/deploy/ci/runner"
4
4
 
5
5
  module Bard
6
6
  class CI
@@ -1,5 +1,5 @@
1
1
  require "json"
2
- require "bard/ci/runner"
2
+ require "bard/plugins/deploy/ci/runner"
3
3
  require "bard/secrets"
4
4
 
5
5
  module Bard
@@ -1,5 +1,5 @@
1
1
  require "tempfile"
2
- require "bard/ci/runner"
2
+ require "bard/plugins/deploy/ci/runner"
3
3
 
4
4
  module Bard
5
5
  class CI
@@ -1,10 +1,10 @@
1
- require "bard/ci/state"
2
- require "bard/ci/retryable"
1
+ require "bard/plugins/deploy/ci/state"
2
+ require "bard/retryable"
3
3
 
4
4
  module Bard
5
5
  class CI
6
6
  class Runner < Struct.new(:project_name, :branch, :sha)
7
- include Retryable
7
+ include Bard::Retryable
8
8
 
9
9
  @runners = {}
10
10
 
@@ -1,5 +1,5 @@
1
1
  require "forwardable"
2
- require "bard/ci/runner"
2
+ require "bard/plugins/deploy/ci/runner"
3
3
 
4
4
  module Bard
5
5
  class CI
@@ -0,0 +1,27 @@
1
+ require "bard/plugins/deploy/strategy"
2
+ require "bard/copy"
3
+ require "bard/plugins/ssh"
4
+
5
+ module Bard
6
+ class DeployStrategy
7
+ class SSH < DeployStrategy
8
+ def deploy(clone: nil, branch: nil, force: false)
9
+ target.require_capability!(:ssh)
10
+
11
+ if clone
12
+ target.run! "git clone git@github.com:botandrosedesign/#{clone} #{target.path}", home: true
13
+ Bard::Copy.file "config/master.key", from: target.config[:local], to: target
14
+ elsif force
15
+ target.run! "git fetch origin #{branch}"
16
+ target.run! "git checkout -f origin/#{branch}"
17
+ else
18
+ branch ||= target.instance_variable_get(:@branch) || "master"
19
+ target.run! "git pull --ff-only origin #{branch}"
20
+ end
21
+
22
+ target.run! "bin/setup"
23
+ target.run! "bard setup" if clone
24
+ end
25
+ end
26
+ end
27
+ end
@@ -39,7 +39,7 @@ module Bard
39
39
  @target = target
40
40
  end
41
41
 
42
- def deploy
42
+ def deploy(clone: nil, branch: nil, force: false)
43
43
  raise NotImplementedError, "Subclasses must implement #deploy"
44
44
  end
45
45
 
@@ -58,6 +58,3 @@ module Bard
58
58
  end
59
59
  end
60
60
  end
61
-
62
- # Auto-load all strategy files
63
- Dir[File.join(__dir__, "deploy_strategy", "*.rb")].each { |f| require f }
@@ -0,0 +1,240 @@
1
+ require "bard/plugins/ping"
2
+ require "bard/plugins/git"
3
+ require "bard/command"
4
+ require "bard/plugins/deploy/strategy"
5
+ require "bard/plugins/deploy/ssh_strategy"
6
+ require "bard/plugins/deploy/ci"
7
+ require "bard/plugins/deploy/ci/jenkins"
8
+ require "bard/plugins/deploy/ci/local"
9
+ require "bard/plugins/deploy/ci/github_actions"
10
+ require "tmpdir"
11
+
12
+ class Bard::CLI
13
+ option :"skip-ci", type: :boolean
14
+ option :"local-ci", type: :boolean
15
+ option :ci, type: :string
16
+ option :clone, type: :boolean
17
+ option :target, type: :string, default: "production"
18
+ desc "deploy [BRANCH]", "deploys branch to target (default: current branch to production)"
19
+ def deploy(branch = nil)
20
+ branch ||= Bard::Git.current_branch
21
+
22
+ if branch == "master"
23
+ if !Bard::Git.up_to_date_with_remote?(branch)
24
+ run! "git push origin #{branch}:#{branch}"
25
+ end
26
+ invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false
27
+
28
+ else
29
+ run! "git fetch origin"
30
+ if Bard::Git.current_branch != "master" && !Bard::Git.in_linked_worktree?
31
+ run! "git fetch origin master:master"
32
+ end
33
+
34
+ unless Bard::Git.fast_forward_merge?("origin/master", branch)
35
+ puts "The master branch has advanced. Attempting rebase..."
36
+ if branch == Bard::Git.current_branch
37
+ run! "git rebase origin/master"
38
+ else
39
+ tmpdir = Dir.mktmpdir("bard-rebase")
40
+ begin
41
+ run! "git worktree add --detach #{tmpdir} #{branch}"
42
+ success = Dir.chdir(tmpdir) { system("git rebase origin/master") }
43
+ rebased_sha = Dir.chdir(tmpdir) { `git rev-parse HEAD`.strip } if success
44
+ run! "git worktree remove #{tmpdir} --force"
45
+ unless success
46
+ puts red("!!! ") + "Rebase failed due to conflicts."
47
+ puts "Please rebase #{branch} manually:"
48
+ puts " git checkout #{branch}"
49
+ puts " git rebase origin/master"
50
+ exit 1
51
+ end
52
+ run! "git branch -f #{branch} #{rebased_sha}"
53
+ ensure
54
+ FileUtils.rm_rf(tmpdir) if Dir.exist?(tmpdir)
55
+ end
56
+ end
57
+ end
58
+
59
+ run! "git push -f origin #{branch}:#{branch}"
60
+
61
+ invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false
62
+
63
+ run! "git push origin #{branch}:master"
64
+ if Bard::Git.current_branch == "master"
65
+ run! "git pull --ff-only origin master"
66
+ elsif Bard::Git.in_linked_worktree?
67
+ run! "git fetch origin master"
68
+ else
69
+ run! "git fetch origin master:master"
70
+ end
71
+ end
72
+
73
+ if `git remote` =~ /\bgithub\b/
74
+ run! "git push github"
75
+ end
76
+
77
+ to = options[:target].to_sym
78
+
79
+ target = config[to]
80
+ strategy = target.deploy_strategy_instance
81
+ if options[:clone]
82
+ strategy.deploy(clone: project_name)
83
+ else
84
+ strategy.deploy
85
+ end
86
+
87
+ puts green("Deploy Succeeded")
88
+
89
+ if branch != "master"
90
+ puts "Deleting branch: #{branch}"
91
+ run! "git push --delete origin #{branch}"
92
+
93
+ if branch == Bard::Git.current_branch && Bard::Git.in_linked_worktree?
94
+ worktree_path = Dir.pwd
95
+ main_checkout = File.dirname(File.expand_path(`git rev-parse --git-common-dir`.chomp))
96
+ Dir.chdir(main_checkout)
97
+ run! "git worktree remove #{worktree_path}"
98
+ run! "git branch -D #{branch}"
99
+ puts "Worktree removed. Run: cd #{main_checkout}"
100
+ else
101
+ if branch == Bard::Git.current_branch
102
+ run! "git checkout master"
103
+ end
104
+ run! "git branch -D #{branch}"
105
+ end
106
+ end
107
+
108
+ ping to
109
+ rescue Bard::Command::Error => e
110
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
111
+ exit 1
112
+ end
113
+
114
+ desc "stage [branch=HEAD]", "pushes current branch, and stages it"
115
+ def stage(branch = Bard::Git.current_branch)
116
+ if config[:production] == config[:staging]
117
+ raise Thor::Error.new("`bard stage` is disabled until a production target is defined. Until then, please use `bard deploy` to deploy to the staging target.")
118
+ end
119
+
120
+ run! "git push -u origin #{branch}", verbose: true
121
+
122
+ target = config[:staging]
123
+ strategy = target.deploy_strategy_instance
124
+ strategy.deploy(branch: branch, force: true)
125
+
126
+ puts green("Stage Succeeded")
127
+
128
+ ping :staging
129
+ rescue Bard::Command::Error => e
130
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
131
+ exit 1
132
+ end
133
+
134
+ option :"local-ci", type: :boolean
135
+ option :ci, type: :string
136
+ option :status, type: :boolean
137
+ option :resume, type: :boolean
138
+ desc "ci [branch=HEAD]", "runs ci against BRANCH"
139
+ def ci(branch = Bard::Git.current_branch)
140
+ runner_name = if options["local-ci"]
141
+ :local
142
+ elsif options["ci"]
143
+ options["ci"].to_sym
144
+ else
145
+ config.ci
146
+ end
147
+ ci = Bard::CI.new(project_name, branch, runner_name: runner_name)
148
+ unless ci.exists?
149
+ puts red("No CI found for #{project_name}!")
150
+ puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
151
+ exit 1
152
+ end
153
+
154
+ return puts ci.status if options["status"]
155
+
156
+ if options["resume"]
157
+ puts "Continuous integration: resuming build..."
158
+ success = ci.resume do |elapsed_time, last_time|
159
+ if last_time
160
+ percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
161
+ output = " Estimated completion: #{percentage}%"
162
+ else
163
+ output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
164
+ end
165
+ print "\x08" * output.length
166
+ print output
167
+ $stdout.flush
168
+ end
169
+ else
170
+ puts "Continuous integration: starting build on #{branch}..."
171
+
172
+ success = ci.run do |elapsed_time, last_time|
173
+ if last_time
174
+ percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
175
+ output = " Estimated completion: #{percentage}%"
176
+ else
177
+ output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
178
+ end
179
+ print "\x08" * output.length
180
+ print output
181
+ $stdout.flush
182
+ end
183
+ end
184
+
185
+ if success
186
+ puts
187
+ puts "Continuous integration: success!"
188
+ else
189
+ puts
190
+ puts ci.console
191
+ puts red("Automated tests failed!")
192
+ exit 1
193
+ end
194
+ end
195
+
196
+ option :from, default: "production"
197
+ option :to, default: "local"
198
+ desc "master_key --from=production --to=local", "copy master key from from to to"
199
+ def master_key
200
+ from = config[options[:from]]
201
+ to = config[options[:to]]
202
+ Bard::Copy.file "config/master.key", from: from, to: to
203
+ end
204
+ end
205
+
206
+ require "bard/config"
207
+
208
+ class Bard::Config
209
+ def ci(system = nil)
210
+ if system.nil?
211
+ @ci_system
212
+ else
213
+ @ci_system = system
214
+ end
215
+ end
216
+ end
217
+
218
+ require "bard/target"
219
+
220
+ class Bard::Target
221
+ def deploy_strategy
222
+ @deploy_strategy
223
+ end
224
+
225
+ def deploy_strategy_instance
226
+ strategy = @deploy_strategy
227
+ strategy ||= :ssh if has_capability?(:ssh)
228
+ raise "No deployment strategy configured for target #{key}" unless strategy
229
+
230
+ strategy_class = Bard::DeployStrategy[strategy]
231
+ raise "Unknown deployment strategy: #{strategy}" unless strategy_class
232
+
233
+ strategy_class.new(self)
234
+ end
235
+
236
+ def strategy_options(strategy_name)
237
+ @strategy_options_hash ||= {}
238
+ @strategy_options_hash[strategy_name] || {}
239
+ end
240
+ end
@@ -21,12 +21,15 @@ module Bard
21
21
 
22
22
  def sha_of ref
23
23
  sha = `git rev-parse #{ref} 2>/dev/null`.chomp
24
- return sha if command_succeeded?
24
+ return sha if $?.success?
25
25
  nil # Branch doesn't exist
26
26
  end
27
27
 
28
- def command_succeeded?
29
- $?.success?
28
+ def in_linked_worktree?
29
+ git_dir = `git rev-parse --git-dir 2>/dev/null`.chomp
30
+ common_dir = `git rev-parse --git-common-dir 2>/dev/null`.chomp
31
+ return false if git_dir.empty? || common_dir.empty?
32
+ File.expand_path(git_dir) != File.expand_path(common_dir)
30
33
  end
31
34
  end
32
35
  end
@@ -2,12 +2,12 @@ require "net/http"
2
2
  require "json"
3
3
  require "base64"
4
4
  require "rbnacl"
5
- require "bard/ci/retryable"
5
+ require "bard/retryable"
6
6
  require "bard/secrets"
7
7
 
8
8
  module Bard
9
9
  class Github < Struct.new(:project_name)
10
- include CI::Retryable
10
+ include Retryable
11
11
 
12
12
  def initialize(project_name, api_key: nil)
13
13
  super(project_name)
@@ -1,5 +1,5 @@
1
- require "bard/deploy_strategy"
2
- require "bard/git"
1
+ require "bard/plugins/deploy/strategy"
2
+ require "bard/plugins/git"
3
3
  require "fileutils"
4
4
  require "uri"
5
5
 
@@ -11,8 +11,7 @@ module Bard
11
11
  @url = url || target.github_pages
12
12
  @options = options
13
13
 
14
- # Auto-configure ping URL if provided
15
- target.ping(url) if url
14
+ target.url(url) if url
16
15
  end
17
16
 
18
17
  def deploy
@@ -1,28 +1,24 @@
1
- require "bard/plugin"
1
+ require "bard/plugins/github_pages/strategy"
2
+ require "bard/config"
3
+ require "bard/target"
2
4
 
3
- # Load the deploy strategy (auto-registers via inherited hook)
4
- require "bard/deploy_strategy/github_pages"
5
-
6
- Bard::Plugin.register :github_pages do
7
- # Config DSL: github_pages "url" sets up a production target
8
- config_method :github_pages do |url|
9
- urls = []
5
+ class Bard::Config
6
+ def github_pages(url)
10
7
  uri = url.start_with?("http") ? URI.parse(url) : URI.parse("https://#{url}")
11
8
  hostname = uri.hostname.sub(/^www\./, "")
12
- urls = [hostname]
13
- urls << "www.#{hostname}" if hostname.count(".") < 2
14
9
 
10
+ remove_target :production
15
11
  target :production do
16
12
  github_pages url
17
- ssh false
18
- ping(*urls) if urls.any?
13
+ url(hostname) if hostname
19
14
  end
20
15
 
21
- backup false
16
+ backup(false) if respond_to?(:backup)
22
17
  end
18
+ end
23
19
 
24
- # Target DSL: github_pages sets deploy strategy
25
- target_method :github_pages do |url = nil|
20
+ class Bard::Target
21
+ def github_pages(url = nil)
26
22
  if url.nil?
27
23
  @github_pages_url
28
24
  else
@@ -1,5 +1,13 @@
1
- require "bard/plugin"
2
-
3
- Bard::Plugin.register :hurt do
4
- cli "Bard::CLI::Hurt", require: "bard/cli/hurt"
1
+ class Bard::CLI
2
+ desc "hurt <command>", "reruns a command until it fails"
3
+ def hurt(*args)
4
+ (1..).each do |count|
5
+ puts "Running attempt #{count}"
6
+ system *args
7
+ unless $?.success?
8
+ puts "Ran #{count-1} times before failing"
9
+ break
10
+ end
11
+ end
12
+ end
5
13
  end
@@ -4,6 +4,8 @@ updates:
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "weekly"
7
+ cooldown:
8
+ default-days: 7
7
9
  allow:
8
10
  - dependency-type: "all"
9
11
  rebase-strategy: "disabled"
@@ -14,4 +16,3 @@ updates:
14
16
  update-types:
15
17
  - "minor"
16
18
  - "patch"
17
-
@@ -6,7 +6,7 @@ on:
6
6
  - cron: '0 0 */3 * *'
7
7
  jobs:
8
8
  build_ruby_cache:
9
- runs-on: ubuntu-22.04
9
+ runs-on: ubuntu-24.04
10
10
  timeout-minutes: 30
11
11
  steps:
12
12
  - uses: actions/checkout@v3
@@ -12,7 +12,7 @@ permissions:
12
12
 
13
13
  jobs:
14
14
  test:
15
- runs-on: ubuntu-22.04${{ github.actor != 'dependabot[bot]' && '-16core' || '' }}
15
+ runs-on: ubuntu-24.04${{ github.actor != 'dependabot[bot]' && '-8core' || '' }}
16
16
  timeout-minutes: 30
17
17
  env:
18
18
  RAILS_ENV: test
@@ -47,7 +47,7 @@ jobs:
47
47
 
48
48
  autodeploy-dependabot-prs:
49
49
  needs: test
50
- runs-on: ubuntu-22.04
50
+ runs-on: ubuntu-24.04
51
51
  timeout-minutes: 30
52
52
  if: github.actor == 'dependabot[bot]'
53
53
  steps: