bard 1.0.1 → 1.0.3
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/Rakefile +5 -0
- data/features/bard_check.feature +1 -1
- data/features/bard_push.feature +1 -1
- data/features/step_definitions/global_steps.rb +4 -4
- data/features/support/env.rb +1 -1
- data/features/support/io.rb +1 -1
- data/lib/bard/ci/github_actions.rb +3 -6
- data/lib/bard/ci/local.rb +0 -2
- data/lib/bard/ci.rb +10 -12
- data/lib/bard/cli/ci.rb +51 -0
- data/lib/bard/cli/data.rb +45 -0
- data/lib/bard/cli/deploy.rb +64 -0
- data/lib/bard/cli/hurt.rb +20 -0
- data/lib/bard/cli/install.rb +16 -0
- data/lib/bard/cli/master_key.rb +17 -0
- data/lib/bard/cli/open.rb +13 -0
- data/lib/bard/cli/ping.rb +18 -0
- data/lib/bard/cli/provision.rb +15 -0
- data/lib/bard/cli/run.rb +24 -0
- data/lib/bard/cli/setup.rb +45 -0
- data/lib/bard/cli/ssh.rb +14 -0
- data/lib/bard/cli/stage.rb +27 -0
- data/lib/bard/cli/vim.rb +13 -0
- data/lib/bard/cli.rb +21 -246
- data/lib/bard/command.rb +8 -6
- data/lib/bard/config.rb +2 -11
- data/lib/bard/copy.rb +15 -40
- data/lib/bard/git.rb +0 -4
- data/lib/bard/ping.rb +0 -1
- data/lib/bard/provision/ssh.rb +1 -1
- data/lib/bard/provision.rb +4 -23
- data/lib/bard/server.rb +29 -7
- data/lib/bard/version.rb +1 -1
- data/spec/bard/ci_spec.rb +10 -0
- data/spec/bard/config_spec.rb +83 -0
- data/spec/bard/server_spec.rb +127 -0
- metadata +23 -3
- /data/lib/bard/provision/{master_key.rb → masterkey.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 911b2ae8bf5420aa572a4154235d208377016c5567909fb1dc23d57d20ac276e
|
4
|
+
data.tar.gz: c2cb50a1b72ef2c4a3efcd7a82c52dd7b42f5085ab4d1d40a24ca941c30df451
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1c7625f9762e9ca2b2ab19de7362405a46f12c8cbe4c8489cef98c0d4c256850faae9ce9a98df72ecf4dc31c090d02f1186ee34b410929450d304a61ab789d1
|
7
|
+
data.tar.gz: 3a7520d91ebe6a6f771dbdac08661c20109ca713328c9acb5f0fa1c86e136718d99c997a64232e8f7e1e3681b9ccec22c9fe737387eb70fabc8c63c71c87722d
|
data/Rakefile
CHANGED
data/features/bard_check.feature
CHANGED
@@ -17,7 +17,7 @@ Feature: Bard can check its environment for missing dependencies and potential p
|
|
17
17
|
And the database is missing
|
18
18
|
When I type "bard check"
|
19
19
|
Then I should see the fatal error "missing database"
|
20
|
-
|
20
|
+
|
21
21
|
Scenario: Bard check detects pending migrations
|
22
22
|
Given a shared rails project
|
23
23
|
And a commit with a new migration
|
data/features/bard_push.feature
CHANGED
@@ -42,7 +42,7 @@ Feature: bard push
|
|
42
42
|
Then on staging, there should be one new submodule
|
43
43
|
And the submodule branch should match the submodule origin branch
|
44
44
|
And on staging, the submodule working directory should be clean
|
45
|
-
|
45
|
+
|
46
46
|
Scenario: Pushing a change that includes a submodule update
|
47
47
|
Given a submodule
|
48
48
|
And a commit with a submodule update
|
@@ -3,11 +3,11 @@ Given /^a shared rails project$/ do
|
|
3
3
|
Dir.foreach "#{ROOT}/tmp" do |file|
|
4
4
|
FileUtils.rm_rf("#{ROOT}/tmp/#{file}") unless %w(fixtures . ..).include? file
|
5
5
|
end
|
6
|
-
|
6
|
+
|
7
7
|
# SETUP
|
8
8
|
Dir.chdir ROOT
|
9
9
|
`cp -r tmp/fixtures/* tmp/`
|
10
|
-
|
10
|
+
|
11
11
|
Dir.chdir 'tmp'
|
12
12
|
@repos = {}
|
13
13
|
%w(development_a development_b staging production).each do |env|
|
@@ -44,11 +44,11 @@ Then /^I should see the fatal error "([^\"]*)"$/ do |error_message|
|
|
44
44
|
end
|
45
45
|
|
46
46
|
Then /^I should see the warning "([^\"]*)"$/ do |warning_message|
|
47
|
-
@stderr.should include(warning_message)
|
47
|
+
@stderr.should include(warning_message)
|
48
48
|
end
|
49
49
|
|
50
50
|
Then /^I should see "([^\"]*)"$/ do |message|
|
51
|
-
@stdout.should include(message)
|
51
|
+
@stdout.should include(message)
|
52
52
|
end
|
53
53
|
|
54
54
|
Then /^debug$/ do
|
data/features/support/env.rb
CHANGED
data/features/support/io.rb
CHANGED
@@ -27,17 +27,14 @@ module Bard
|
|
27
27
|
@run.console
|
28
28
|
end
|
29
29
|
|
30
|
-
def last_response
|
31
|
-
end
|
32
|
-
|
33
30
|
def status
|
34
31
|
last_run = api.last_run
|
35
32
|
if last_run.building?
|
36
|
-
|
33
|
+
"Building..."
|
37
34
|
elsif last_run.success?
|
38
|
-
|
35
|
+
"Succeeded!"
|
39
36
|
elsif last_run.failure?
|
40
|
-
|
37
|
+
"Failed!\n\n#{last_run.console}"
|
41
38
|
else
|
42
39
|
raise "Unknown job status: #{last_run.inspect}"
|
43
40
|
end
|
data/lib/bard/ci/local.rb
CHANGED
data/lib/bard/ci.rb
CHANGED
@@ -8,18 +8,10 @@ module Bard
|
|
8
8
|
@local = !!local
|
9
9
|
end
|
10
10
|
|
11
|
-
attr_reader :project_name, :branch, :runner
|
12
|
-
|
13
|
-
def sha
|
14
|
-
@sha ||= `git rev-parse #{branch}`.chomp
|
15
|
-
end
|
16
|
-
|
17
|
-
def runner
|
18
|
-
@runner ||= choose_runner_class.new(project_name, branch, sha)
|
19
|
-
end
|
20
|
-
|
21
11
|
extend Forwardable
|
22
|
-
delegate [:run, :exists?, :console, :
|
12
|
+
delegate [:run, :exists?, :console, :status] => :runner
|
13
|
+
|
14
|
+
private
|
23
15
|
|
24
16
|
def local?
|
25
17
|
@local
|
@@ -33,7 +25,13 @@ module Bard
|
|
33
25
|
!local? && !github_actions?
|
34
26
|
end
|
35
27
|
|
36
|
-
|
28
|
+
def runner
|
29
|
+
@runner ||= choose_runner_class.new(@project_name, @branch, sha)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sha
|
33
|
+
@sha ||= `git rev-parse #{@branch}`.chomp
|
34
|
+
end
|
37
35
|
|
38
36
|
def choose_runner_class
|
39
37
|
if local?
|
data/lib/bard/cli/ci.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "bard/ci"
|
2
|
+
require "bard/git"
|
3
|
+
|
4
|
+
module Bard::CLI::CI
|
5
|
+
def self.included mod
|
6
|
+
mod.class_eval do
|
7
|
+
|
8
|
+
option :"local-ci", type: :boolean
|
9
|
+
option :status, type: :boolean
|
10
|
+
desc "ci [branch=HEAD]", "runs ci against BRANCH"
|
11
|
+
def ci branch=Bard::Git.current_branch
|
12
|
+
ci = Bard::CI.new(project_name, branch, local: options["local-ci"])
|
13
|
+
if ci.exists?
|
14
|
+
return puts ci.status if options["status"]
|
15
|
+
|
16
|
+
puts "Continuous integration: starting build on #{branch}..."
|
17
|
+
|
18
|
+
success = ci.run do |elapsed_time, last_time|
|
19
|
+
if last_time
|
20
|
+
percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
|
21
|
+
output = " Estimated completion: #{percentage}%"
|
22
|
+
else
|
23
|
+
output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
|
24
|
+
end
|
25
|
+
print "\x08" * output.length
|
26
|
+
print output
|
27
|
+
$stdout.flush
|
28
|
+
end
|
29
|
+
|
30
|
+
if success
|
31
|
+
puts
|
32
|
+
puts "Continuous integration: success!"
|
33
|
+
puts "Deploying..."
|
34
|
+
else
|
35
|
+
puts
|
36
|
+
puts ci.console
|
37
|
+
puts red("Automated tests failed!")
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
else
|
42
|
+
puts red("No CI found for #{project_name}!")
|
43
|
+
puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "bard/command"
|
2
|
+
|
3
|
+
module Bard::CLI::Data
|
4
|
+
def self.included mod
|
5
|
+
mod.class_eval do
|
6
|
+
|
7
|
+
desc "data --from=production --to=local", "copy database and assets from from to to"
|
8
|
+
option :from, default: "production"
|
9
|
+
option :to, default: "local"
|
10
|
+
def data
|
11
|
+
from = config[options[:from]]
|
12
|
+
to = config[options[:to]]
|
13
|
+
|
14
|
+
if to.key == :production
|
15
|
+
url = to.ping.first
|
16
|
+
puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
|
17
|
+
answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
|
18
|
+
if answer != url
|
19
|
+
puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Dumping #{from.key} database to file..."
|
25
|
+
from.run! "bin/rake db:dump"
|
26
|
+
|
27
|
+
puts "Transfering file from #{from.key} to #{to.key}..."
|
28
|
+
from.copy_file "db/data.sql.gz", to: to, verbose: true
|
29
|
+
|
30
|
+
puts "Loading file into #{to.key} database..."
|
31
|
+
to.run! "bin/rake db:load"
|
32
|
+
|
33
|
+
config.data.each do |path|
|
34
|
+
puts "Synchronizing files in #{path}..."
|
35
|
+
from.copy_dir path, to: to, verbose: true
|
36
|
+
end
|
37
|
+
rescue Bard::Command::Error => e
|
38
|
+
puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "bard/git"
|
2
|
+
require "bard/command"
|
3
|
+
|
4
|
+
module Bard::CLI::Deploy
|
5
|
+
def self.included mod
|
6
|
+
mod.class_eval do
|
7
|
+
|
8
|
+
option :"skip-ci", type: :boolean
|
9
|
+
option :"local-ci", type: :boolean
|
10
|
+
desc "deploy [TO=production]", "checks that current branch is a ff with master, checks with ci, merges into master, deploys to target, and then deletes branch."
|
11
|
+
def deploy to=:production
|
12
|
+
branch = Bard::Git.current_branch
|
13
|
+
|
14
|
+
if branch == "master"
|
15
|
+
if !Bard::Git.up_to_date_with_remote?(branch)
|
16
|
+
run! "git push origin #{branch}:#{branch}"
|
17
|
+
end
|
18
|
+
invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
|
19
|
+
|
20
|
+
else
|
21
|
+
run! "git fetch origin master:master"
|
22
|
+
|
23
|
+
unless Bard::Git.fast_forward_merge?("origin/master", branch)
|
24
|
+
puts "The master branch has advanced. Attempting rebase..."
|
25
|
+
run! "git rebase origin/master"
|
26
|
+
end
|
27
|
+
|
28
|
+
run! "git push -f origin #{branch}:#{branch}"
|
29
|
+
|
30
|
+
invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
|
31
|
+
|
32
|
+
run! "git push origin #{branch}:master"
|
33
|
+
run! "git fetch origin master:master"
|
34
|
+
end
|
35
|
+
|
36
|
+
if `git remote` =~ /\bgithub\b/
|
37
|
+
run! "git push github"
|
38
|
+
end
|
39
|
+
|
40
|
+
config[to].run! "git pull origin master && bin/setup"
|
41
|
+
|
42
|
+
puts green("Deploy Succeeded")
|
43
|
+
|
44
|
+
if branch != "master"
|
45
|
+
puts "Deleting branch: #{branch}"
|
46
|
+
run! "git push --delete origin #{branch}"
|
47
|
+
|
48
|
+
if branch == Bard::Git.current_branch
|
49
|
+
run! "git checkout master"
|
50
|
+
end
|
51
|
+
|
52
|
+
run! "git branch -D #{branch}"
|
53
|
+
end
|
54
|
+
|
55
|
+
ping to
|
56
|
+
rescue Bard::Command::Error => e
|
57
|
+
puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Bard::CLI::Hurt
|
2
|
+
def self.included mod
|
3
|
+
mod.class_eval do
|
4
|
+
|
5
|
+
desc "hurt <command>", "reruns a command until it fails"
|
6
|
+
def hurt *args
|
7
|
+
(1..).each do |count|
|
8
|
+
puts "Running attempt #{count}"
|
9
|
+
system *args
|
10
|
+
unless $?.success?
|
11
|
+
puts "Ran #{count-1} times before failing"
|
12
|
+
break
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bard::CLI::Install
|
2
|
+
def self.included mod
|
3
|
+
mod.class_eval do
|
4
|
+
|
5
|
+
desc "install", "copies bin/setup and bin/ci scripts into current project."
|
6
|
+
def install
|
7
|
+
install_files_path = File.expand_path(File.join(__dir__, "../../install_files/*"))
|
8
|
+
system "cp -R #{install_files_path} bin/"
|
9
|
+
github_files_path = File.expand_path(File.join(__dir__, "../../install_files/.github"))
|
10
|
+
system "cp -R #{github_files_path} ./"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bard::CLI::MasterKey
|
2
|
+
def self.included mod
|
3
|
+
mod.class_eval do
|
4
|
+
|
5
|
+
desc "master_key --from=production --to=local", "copy master key from from to to"
|
6
|
+
option :from, default: "production"
|
7
|
+
option :to, default: "local"
|
8
|
+
def master_key
|
9
|
+
from = config[options[:from]]
|
10
|
+
to = config[options[:to]]
|
11
|
+
from.copy_file "config/master.key", to:
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bard/ping"
|
2
|
+
|
3
|
+
module Bard::CLI::Ping
|
4
|
+
def self.included mod
|
5
|
+
mod.class_eval do
|
6
|
+
|
7
|
+
desc "ping [server=production]", "hits the server over http to verify that its up."
|
8
|
+
def ping server=:production
|
9
|
+
server = config[server]
|
10
|
+
down_urls = Bard::Ping.call(config[server])
|
11
|
+
down_urls.each { |url| puts "#{url} is down!" }
|
12
|
+
exit 1 if down_urls.any?
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bard/provision"
|
2
|
+
|
3
|
+
module Bard::CLI::Provision
|
4
|
+
def self.included mod
|
5
|
+
mod.class_eval do
|
6
|
+
|
7
|
+
desc "provision [ssh_url]", "takes an ssh url to a raw ubuntu 22.04 install, and readies it in the shape of :production"
|
8
|
+
def provision ssh_url
|
9
|
+
Bard::Provision.call(config, ssh_url.dup) # dup unfreezes the string for later mutation
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
data/lib/bard/cli/run.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "bard/command"
|
2
|
+
|
3
|
+
module Bard::CLI::Run
|
4
|
+
def self.included mod
|
5
|
+
mod.class_eval do
|
6
|
+
|
7
|
+
# HACK: we don't use Thor::Base#run, so its okay to stomp on it here
|
8
|
+
original_verbose, $VERBOSE = $VERBOSE, nil
|
9
|
+
Thor::THOR_RESERVED_WORDS -= ["run"]
|
10
|
+
$VERBOSE = original_verbose
|
11
|
+
|
12
|
+
desc "run <command>", "run the given command on production"
|
13
|
+
def run *args
|
14
|
+
server = config[:production]
|
15
|
+
server.run! *args, verbose: true
|
16
|
+
rescue Bard::Command::Error => e
|
17
|
+
puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Bard::CLI::Setup
|
4
|
+
def self.included mod
|
5
|
+
mod.class_eval do
|
6
|
+
|
7
|
+
desc "setup", "installs app in nginx"
|
8
|
+
def setup
|
9
|
+
path = "/etc/nginx/sites-available/#{project_name}"
|
10
|
+
dest_path = path.sub("sites-available", "sites-enabled")
|
11
|
+
server_name = case ENV["RAILS_ENV"]
|
12
|
+
when "production"
|
13
|
+
(config[:production].ping.map do |str|
|
14
|
+
"*.#{URI.parse(str).host}"
|
15
|
+
end + ["_"]).join(" ")
|
16
|
+
when "staging" then "#{project_name}.botandrose.com"
|
17
|
+
else "#{project_name}.localhost"
|
18
|
+
end
|
19
|
+
|
20
|
+
system "sudo tee #{path} >/dev/null <<-EOF
|
21
|
+
server {
|
22
|
+
listen 80;
|
23
|
+
server_name #{server_name};
|
24
|
+
|
25
|
+
root #{Dir.pwd}/public;
|
26
|
+
passenger_enabled on;
|
27
|
+
|
28
|
+
location ~* \\.(ico|css|js|gif|jp?g|png|webp) {
|
29
|
+
access_log off;
|
30
|
+
if (\\$request_filename ~ \"-[0-9a-f]{32}\\.\") {
|
31
|
+
expires max;
|
32
|
+
add_header Cache-Control public;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
gzip_static on;
|
36
|
+
}
|
37
|
+
EOF"
|
38
|
+
system "sudo ln -sf #{path} #{dest_path}" if !File.exist?(dest_path)
|
39
|
+
system "sudo service nginx restart"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/lib/bard/cli/ssh.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Bard::CLI::SSH
|
2
|
+
def self.included mod
|
3
|
+
mod.class_eval do
|
4
|
+
|
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
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "bard/git"
|
2
|
+
require "bard/command"
|
3
|
+
|
4
|
+
module Bard::CLI::Stage
|
5
|
+
def self.included mod
|
6
|
+
mod.class_eval do
|
7
|
+
|
8
|
+
desc "stage [branch=HEAD]", "pushes current branch, and stages it"
|
9
|
+
def stage branch=Bard::Git.current_branch
|
10
|
+
unless config.servers.key?(:production)
|
11
|
+
raise Thor::Error.new("`bard stage` is disabled until a production server is defined. Until then, please use `bard deploy` to deploy to the staging server.")
|
12
|
+
end
|
13
|
+
|
14
|
+
run! "git push -u origin #{branch}", verbose: true
|
15
|
+
config[:staging].run! "git fetch && git checkout -f origin/#{branch} && bin/setup"
|
16
|
+
puts green("Stage Succeeded")
|
17
|
+
|
18
|
+
ping :staging
|
19
|
+
rescue Bard::Command::Error => e
|
20
|
+
puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/bard/cli/vim.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bard::CLI::Vim
|
2
|
+
def self.included mod
|
3
|
+
mod.class_eval do
|
4
|
+
|
5
|
+
desc "vim [branch=master]", "open all files that have changed since master"
|
6
|
+
def vim branch="master"
|
7
|
+
exec "vim -p `git diff #{branch} --name-only | grep -v sass$ | tac`"
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|