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
|
@@ -1,17 +1,48 @@
|
|
|
1
|
-
require "bard/ci/state"
|
|
2
|
-
require "bard/
|
|
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
|
+
|
|
9
|
+
@runners = {}
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
attr_reader :runners
|
|
13
|
+
|
|
14
|
+
def inherited(subclass)
|
|
15
|
+
super
|
|
16
|
+
name = extract_runner_name(subclass)
|
|
17
|
+
runners[name] = subclass if name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](name)
|
|
21
|
+
runners[name.to_sym]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns the last registered runner (most recently loaded wins)
|
|
25
|
+
def default
|
|
26
|
+
runners.values.last
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def extract_runner_name(klass)
|
|
32
|
+
klass.name&.split("::")&.last
|
|
33
|
+
&.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
34
|
+
&.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
35
|
+
&.downcase
|
|
36
|
+
&.to_sym
|
|
37
|
+
end
|
|
38
|
+
end
|
|
8
39
|
|
|
9
40
|
def run
|
|
10
41
|
start
|
|
11
42
|
@start_time = Time.new.to_i
|
|
12
43
|
@last_time_elapsed = get_last_time_elapsed
|
|
13
44
|
save_state
|
|
14
|
-
wait_until_started
|
|
45
|
+
wait_until_started
|
|
15
46
|
|
|
16
47
|
poll_until_complete { |elapsed, last_time| yield elapsed, last_time }
|
|
17
48
|
|
|
@@ -62,6 +93,9 @@ module Bard
|
|
|
62
93
|
raise NotImplementedError, "#{self.class}#success? not implemented"
|
|
63
94
|
end
|
|
64
95
|
|
|
96
|
+
def wait_until_started
|
|
97
|
+
end
|
|
98
|
+
|
|
65
99
|
def get_last_time_elapsed
|
|
66
100
|
nil
|
|
67
101
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "bard/plugins/deploy/ci/runner"
|
|
3
|
+
|
|
4
|
+
module Bard
|
|
5
|
+
class CI
|
|
6
|
+
def initialize(project_name, branch, runner_name: nil)
|
|
7
|
+
@project_name = project_name
|
|
8
|
+
@branch = branch
|
|
9
|
+
@runner_name = runner_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
extend Forwardable
|
|
13
|
+
delegate [:run, :resume, :exists?, :console, :status] => :runner
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def runner
|
|
18
|
+
@runner ||= choose_runner_class.new(@project_name, @branch, sha)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sha
|
|
22
|
+
@sha ||= `git rev-parse #{@branch}`.chomp
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def choose_runner_class
|
|
26
|
+
if @runner_name
|
|
27
|
+
runner_class = Runner[@runner_name]
|
|
28
|
+
raise "Unknown CI runner: #{@runner_name}" unless runner_class
|
|
29
|
+
runner_class
|
|
30
|
+
else
|
|
31
|
+
runner_class = Runner.default
|
|
32
|
+
raise "No CI runner available" unless runner_class
|
|
33
|
+
runner_class
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
@@ -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
|
|
@@ -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
|
|
24
|
+
return sha if $?.success?
|
|
25
25
|
nil # Branch doesn't exist
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def
|
|
29
|
-
|
|
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,11 +2,12 @@ require "net/http"
|
|
|
2
2
|
require "json"
|
|
3
3
|
require "base64"
|
|
4
4
|
require "rbnacl"
|
|
5
|
-
require "bard/
|
|
5
|
+
require "bard/retryable"
|
|
6
|
+
require "bard/secrets"
|
|
6
7
|
|
|
7
8
|
module Bard
|
|
8
9
|
class Github < Struct.new(:project_name)
|
|
9
|
-
include
|
|
10
|
+
include Retryable
|
|
10
11
|
|
|
11
12
|
def initialize(project_name, api_key: nil)
|
|
12
13
|
super(project_name)
|
|
@@ -107,10 +108,7 @@ module Bard
|
|
|
107
108
|
private
|
|
108
109
|
|
|
109
110
|
def api_key
|
|
110
|
-
@api_key ||=
|
|
111
|
-
raw = `git ls-remote -t git@github.com:botandrosedesign/secrets`
|
|
112
|
-
raw[/github-apikey\|(.+)$/, 1]
|
|
113
|
-
end
|
|
111
|
+
@api_key ||= Bard::Secrets.fetch("github-apikey")
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
def request path, &block
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require "bard/
|
|
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
|
|
|
@@ -8,11 +8,10 @@ module Bard
|
|
|
8
8
|
class GithubPages < DeployStrategy
|
|
9
9
|
def initialize(target, url = nil, **options)
|
|
10
10
|
super(target)
|
|
11
|
-
@url = url
|
|
11
|
+
@url = url || target.github_pages
|
|
12
12
|
@options = options
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
target.ping(url) if url
|
|
14
|
+
target.url(url) if url
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def deploy
|
|
@@ -65,7 +64,15 @@ module Bard
|
|
|
65
64
|
ensure
|
|
66
65
|
# cleanup
|
|
67
66
|
run! <<~SH
|
|
68
|
-
cat tmp/pids/server.pid
|
|
67
|
+
PID=$(cat tmp/pids/server.pid 2>/dev/null)
|
|
68
|
+
if [ -n "$PID" ]; then
|
|
69
|
+
kill $PID 2>/dev/null
|
|
70
|
+
for i in 1 2 3 4 5; do
|
|
71
|
+
kill -0 $PID 2>/dev/null || break
|
|
72
|
+
sleep 1
|
|
73
|
+
done
|
|
74
|
+
kill -9 $PID 2>/dev/null || true
|
|
75
|
+
fi
|
|
69
76
|
rm -rf public/assets
|
|
70
77
|
SH
|
|
71
78
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "bard/plugins/github_pages/strategy"
|
|
2
|
+
require "bard/config"
|
|
3
|
+
require "bard/target"
|
|
4
|
+
|
|
5
|
+
class Bard::Config
|
|
6
|
+
def github_pages(url)
|
|
7
|
+
uri = url.start_with?("http") ? URI.parse(url) : URI.parse("https://#{url}")
|
|
8
|
+
hostname = uri.hostname.sub(/^www\./, "")
|
|
9
|
+
|
|
10
|
+
remove_target :production
|
|
11
|
+
target :production do
|
|
12
|
+
github_pages url
|
|
13
|
+
url(hostname) if hostname
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
backup(false) if respond_to?(:backup)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Bard::Target
|
|
21
|
+
def github_pages(url = nil)
|
|
22
|
+
if url.nil?
|
|
23
|
+
@github_pages_url
|
|
24
|
+
else
|
|
25
|
+
@deploy_strategy = :github_pages
|
|
26
|
+
@github_pages_url = url
|
|
27
|
+
enable_capability(:github_pages)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
13
|
+
end
|
|
@@ -12,7 +12,7 @@ permissions:
|
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
14
|
test:
|
|
15
|
-
runs-on: ubuntu-
|
|
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-
|
|
50
|
+
runs-on: ubuntu-24.04
|
|
51
51
|
timeout-minutes: 30
|
|
52
52
|
if: github.actor == 'dependabot[bot]'
|
|
53
53
|
steps:
|
|
@@ -0,0 +1,9 @@
|
|
|
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__)
|
|
5
|
+
|
|
6
|
+
system "cp -R #{install_files_path}/* bin/"
|
|
7
|
+
system "cp -R #{install_files_path}/.github ./"
|
|
8
|
+
end
|
|
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(:
|
|
6
|
-
def self.call
|
|
7
|
-
new(
|
|
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
|
-
|
|
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
|
|
@@ -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
|