bard 0.41.1 → 0.44.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/bard.gemspec +0 -4
- data/features/support/env.rb +0 -1
- data/features/support/io.rb +2 -1
- data/install_files/specified_node.rb +13 -27
- data/install_files/specified_yarn.rb +6 -9
- data/install_files/yarn +5 -0
- data/lib/bard.rb +90 -43
- data/lib/bard/base.rb +59 -8
- data/lib/bard/ci.rb +122 -49
- data/lib/bard/config.rb +91 -0
- data/lib/bard/data.rb +49 -0
- data/lib/bard/version.rb +1 -1
- metadata +6 -62
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/lib/bard/capistrano.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ae411fccf104a6b049a476bfcc662b26d258687a304aa6a4115078a37aafa3f
|
4
|
+
data.tar.gz: ef5e3967d6569b1fb9c58b55ccc668a98fbda97b3f626cd31c3897106ecb04c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59a9e9b54fa1669571c4e61cdcb8ff12e19b511ab12a31ac79a9481cab6964ec53730daa789f685053ce658ba340efe5bfb7f1b031114436563a12c13a3d4ff1
|
7
|
+
data.tar.gz: 11036591830610869ab72ba374f47bd5d95b4de1f991366636dbc137fd394f75494c5a8ae192f7a089fddfd7e7b7f1756b2e1da6cc6f2ab3ada362ac93747130
|
data/.gitignore
CHANGED
data/bard.gemspec
CHANGED
@@ -19,11 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "thor", ">= 0.19.0"
|
22
|
-
spec.add_dependency "capistrano", "= 2.5.10"
|
23
|
-
spec.add_dependency "net-ssh", "~> 4.0"
|
24
22
|
spec.add_dependency "rvm"
|
25
|
-
spec.add_dependency "rvm-capistrano"
|
26
|
-
spec.add_dependency "systemu", ">= 1.2.0"
|
27
23
|
spec.add_dependency "term-ansicolor", ">= 1.0.3"
|
28
24
|
spec.add_dependency "bard-rake", ">= 0.1.1"
|
29
25
|
|
data/features/support/env.rb
CHANGED
data/features/support/io.rb
CHANGED
@@ -1,45 +1,31 @@
|
|
1
1
|
module SpecifiedNode
|
2
|
-
class NVMError < StandardError; end
|
3
|
-
|
4
|
-
NVM_PATH = File.expand_path("~/.nvm/nvm.sh")
|
5
|
-
|
6
2
|
extend self
|
7
3
|
|
4
|
+
NODE_VERSION = "v12.16.1"
|
5
|
+
NODE_PATH = "tmp/node-#{NODE_VERSION}-linux-x64/bin/node"
|
6
|
+
|
8
7
|
def ensure!
|
9
|
-
install_nvm unless nvm_installed?
|
10
|
-
restart unless nvm_active?
|
11
8
|
install_node unless node_installed?
|
12
|
-
|
9
|
+
install_binstub
|
10
|
+
"bin/node --version"
|
13
11
|
end
|
14
12
|
|
15
13
|
private
|
16
14
|
|
17
|
-
def install_nvm
|
18
|
-
system("curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash")\
|
19
|
-
or raise "Couldn't install nvm"
|
20
|
-
end
|
21
|
-
|
22
|
-
def nvm_installed?
|
23
|
-
File.exist?(NVM_PATH)
|
24
|
-
end
|
25
|
-
|
26
15
|
def install_node
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def node_installed?
|
31
|
-
nvm "use"
|
16
|
+
system("wget -cO- https://nodejs.org/dist/#{NODE_VERSION}/node-#{NODE_VERSION}-linux-x64.tar.xz | tar xJ -C tmp/")
|
32
17
|
end
|
33
18
|
|
34
|
-
def
|
35
|
-
|
19
|
+
def install_binstub
|
20
|
+
system("cd bin && ln -fs ../#{NODE_PATH}")
|
36
21
|
end
|
37
22
|
|
38
|
-
def
|
39
|
-
|
23
|
+
def node_installed?
|
24
|
+
File.exist?(NODE_PATH) && `#{NODE_PATH} --version`.chomp == NODE_VERSION
|
40
25
|
end
|
41
26
|
|
42
|
-
def
|
43
|
-
|
27
|
+
def binstub_installed?
|
28
|
+
File.exist?("bin/node")
|
44
29
|
end
|
45
30
|
end
|
31
|
+
|
@@ -1,34 +1,31 @@
|
|
1
1
|
module SpecifiedYarn
|
2
2
|
extend self
|
3
3
|
|
4
|
-
|
4
|
+
YARN_VERSION = "v1.22.0"
|
5
|
+
YARN_PATH = "tmp/yarn-#{YARN_VERSION}/bin/yarn.js"
|
5
6
|
|
6
7
|
def ensure!
|
7
8
|
install_yarn unless yarn_installed?
|
8
|
-
install_binstub
|
9
|
+
install_binstub
|
9
10
|
"bin/yarn install"
|
10
11
|
end
|
11
12
|
|
12
13
|
private
|
13
14
|
|
14
15
|
def install_yarn
|
15
|
-
system(".
|
16
|
+
system("wget -cO- https://github.com/yarnpkg/yarn/releases/download/#{YARN_VERSION}/yarn-#{YARN_VERSION}.tar.gz | tar -xz -C tmp/")
|
16
17
|
end
|
17
18
|
|
18
19
|
def install_binstub
|
19
|
-
system("cd bin && ln -
|
20
|
+
system("cd bin && ln -fs ../#{YARN_PATH}")
|
20
21
|
end
|
21
22
|
|
22
23
|
def yarn_installed?
|
23
|
-
File.exist?(YARN_PATH) &&
|
24
|
+
File.exist?(YARN_PATH) && `bin/node #{YARN_PATH} --version`.chomp == YARN_VERSION[1..-1]
|
24
25
|
end
|
25
26
|
|
26
27
|
def binstub_installed?
|
27
28
|
File.exist?("bin/yarn")
|
28
29
|
end
|
29
|
-
|
30
|
-
def version
|
31
|
-
File.read("package.json")[/"yarn": "([0-9\.]+)"/, 1]
|
32
|
-
end
|
33
30
|
end
|
34
31
|
|
data/install_files/yarn
ADDED
data/lib/bard.rb
CHANGED
@@ -1,60 +1,72 @@
|
|
1
|
-
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
-
|
3
1
|
module Bard; end
|
4
2
|
|
5
3
|
require "bard/base"
|
6
4
|
require "bard/git"
|
7
5
|
require "bard/ci"
|
6
|
+
require "bard/data"
|
7
|
+
|
8
|
+
require "bard/config"
|
8
9
|
|
9
10
|
class Bard::CLI < Thor
|
11
|
+
def initialize(*args, **kwargs, &block)
|
12
|
+
super
|
13
|
+
@config = Config.new(project_name, "bard.rb")
|
14
|
+
end
|
15
|
+
|
10
16
|
desc "data [FROM=production, TO=local]", "copy database and assets from FROM to TO"
|
11
|
-
def data(from
|
12
|
-
|
13
|
-
|
17
|
+
def data(from=nil, to="local")
|
18
|
+
from ||= @config.servers.key?(:production) ? "production" : "staging"
|
19
|
+
Data.new(self, from, to).call
|
14
20
|
end
|
15
21
|
|
16
22
|
method_options %w( verbose -v ) => :boolean
|
17
|
-
desc "stage", "pushes current branch, and stages it"
|
18
|
-
def stage
|
19
|
-
unless
|
23
|
+
desc "stage [BRANCH=HEAD]", "pushes current branch, and stages it"
|
24
|
+
def stage branch=Git.current_branch
|
25
|
+
unless @config.servers.key?(:production)
|
20
26
|
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.")
|
21
27
|
end
|
22
28
|
|
23
|
-
branch = Git.current_branch
|
24
|
-
|
25
29
|
run_crucial "git push -u origin #{branch}", true
|
26
|
-
|
30
|
+
command = "git fetch && git checkout -f origin/#{branch} && bin/setup"
|
31
|
+
run_crucial ssh_command(:staging, command)
|
27
32
|
puts green("Stage Succeeded")
|
28
33
|
|
29
|
-
|
30
|
-
puts red("Staging is now down!")
|
31
|
-
end
|
34
|
+
ping :staging
|
32
35
|
end
|
33
36
|
|
34
|
-
method_options %w
|
35
|
-
desc "deploy [
|
36
|
-
def deploy
|
37
|
+
method_options %w[verbose -v] => :boolean, %w[skip-ci] => :boolean, %w[local-ci -l] => :boolean
|
38
|
+
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."
|
39
|
+
def deploy to=nil
|
40
|
+
branch = Git.current_branch
|
41
|
+
|
37
42
|
if branch == "master"
|
38
43
|
run_crucial "git push origin master:master"
|
39
|
-
invoke :ci unless options["skip-ci"]
|
44
|
+
invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
|
40
45
|
|
41
46
|
else
|
42
47
|
run_crucial "git fetch origin master:master"
|
43
48
|
|
44
|
-
|
49
|
+
unless Git.fast_forward_merge?("origin/master", branch)
|
45
50
|
puts "The master branch has advanced. Attempting rebase..."
|
46
51
|
run_crucial "git rebase origin/master"
|
47
52
|
end
|
48
53
|
|
49
54
|
run_crucial "git push -f origin #{branch}:#{branch}"
|
50
55
|
|
51
|
-
invoke :ci unless options["skip-ci"]
|
56
|
+
invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
|
52
57
|
|
53
58
|
run_crucial "git push origin #{branch}:master"
|
54
59
|
run_crucial "git fetch origin master:master"
|
55
60
|
end
|
56
61
|
|
57
|
-
|
62
|
+
if `git remote` =~ /\bgithub\b/
|
63
|
+
run_crucial "git push github"
|
64
|
+
end
|
65
|
+
|
66
|
+
to ||= @config.servers.key?(:production) ? :production : :staging
|
67
|
+
|
68
|
+
command = "git pull origin master && bin/setup"
|
69
|
+
run_crucial ssh_command(to, command)
|
58
70
|
|
59
71
|
puts green("Deploy Succeeded")
|
60
72
|
|
@@ -62,26 +74,20 @@ class Bard::CLI < Thor
|
|
62
74
|
puts "Deleting branch: #{branch}"
|
63
75
|
run_crucial "git push --delete origin #{branch}"
|
64
76
|
|
65
|
-
|
66
|
-
when branch
|
77
|
+
if branch == Git.current_branch
|
67
78
|
run_crucial "git checkout master"
|
68
|
-
run_crucial "git branch -d #{branch}"
|
69
|
-
when "master"
|
70
|
-
run_crucial "git branch -d #{branch}"
|
71
|
-
else
|
72
|
-
run_crucial "git branch -D #{branch}"
|
73
79
|
end
|
74
|
-
end
|
75
80
|
|
76
|
-
|
77
|
-
puts red("Production is now down!")
|
81
|
+
run_crucial "git branch -D #{branch}"
|
78
82
|
end
|
83
|
+
|
84
|
+
ping to
|
79
85
|
end
|
80
86
|
|
81
|
-
method_options %w
|
87
|
+
method_options %w[verbose -v] => :boolean, %w[local-ci -l] => :boolean
|
82
88
|
desc "ci [BRANCH=HEAD]", "runs ci against BRANCH"
|
83
89
|
def ci branch=Git.current_branch
|
84
|
-
ci = CI.new(project_name, `git rev-parse #{branch}`.chomp)
|
90
|
+
ci = CI.new(project_name, `git rev-parse #{branch}`.chomp, local: options["local-ci"])
|
85
91
|
if ci.exists?
|
86
92
|
puts "Continuous integration: starting build on #{branch}..."
|
87
93
|
|
@@ -100,9 +106,9 @@ class Bard::CLI < Thor
|
|
100
106
|
if success
|
101
107
|
puts
|
102
108
|
puts "Continuous integration: success!"
|
103
|
-
if File.exist?("coverage")
|
109
|
+
if !options["local-ci"] && File.exist?("coverage")
|
104
110
|
puts "Downloading test coverage from CI..."
|
105
|
-
|
111
|
+
download_ci_test_coverage
|
106
112
|
end
|
107
113
|
puts "Deploying..."
|
108
114
|
else
|
@@ -120,6 +126,13 @@ class Bard::CLI < Thor
|
|
120
126
|
end
|
121
127
|
end
|
122
128
|
|
129
|
+
desc "open [SERVER=production]", "opens the url in the web browser."
|
130
|
+
def open server=nil
|
131
|
+
server ||= @config.servers.key?(:production) ? :production : :staging
|
132
|
+
server = @config.servers[server.to_sym]
|
133
|
+
exec "xdg-open #{server.default_ping}"
|
134
|
+
end
|
135
|
+
|
123
136
|
desc "hurt", "reruns a command until it fails"
|
124
137
|
def hurt *args
|
125
138
|
1.upto(Float::INFINITY) do |count|
|
@@ -132,16 +145,16 @@ class Bard::CLI < Thor
|
|
132
145
|
end
|
133
146
|
end
|
134
147
|
|
135
|
-
method_options %w
|
148
|
+
method_options %w[home] => :boolean
|
136
149
|
desc "ssh [TO=production]", "logs into the specified server via SSH"
|
137
|
-
def ssh to
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
command = %(
|
142
|
-
exec command
|
150
|
+
def ssh to=:production
|
151
|
+
command = "exec $SHELL -l"
|
152
|
+
if to == "gubs" && !options["home"]
|
153
|
+
server = @config.servers[:gubs]
|
154
|
+
command = %(bash -lic "exec ./vagrant \\"cd #{server.path} && #{command}\\"")
|
155
|
+
exec ssh_command(to, command, home: true)
|
143
156
|
else
|
144
|
-
exec
|
157
|
+
exec ssh_command(to, command, home: options["home"])
|
145
158
|
end
|
146
159
|
end
|
147
160
|
|
@@ -150,5 +163,39 @@ class Bard::CLI < Thor
|
|
150
163
|
install_files_path = File.expand_path(File.join(__dir__, "../install_files/*"))
|
151
164
|
system "cp #{install_files_path} bin/"
|
152
165
|
end
|
166
|
+
|
167
|
+
desc "ping [SERVER=production]", "hits the server over http to verify that its up."
|
168
|
+
def ping server=:production
|
169
|
+
server = @config.servers[server.to_sym]
|
170
|
+
return false if server.ping == false
|
171
|
+
|
172
|
+
url = server.default_ping
|
173
|
+
if server.ping =~ %r{^/}
|
174
|
+
url += server.ping
|
175
|
+
elsif server.ping.to_s.length > 0
|
176
|
+
url = server.ping
|
177
|
+
end
|
178
|
+
|
179
|
+
command = "curl -sfL #{url} 2>&1 1>/dev/null"
|
180
|
+
unless system command
|
181
|
+
puts "#{server.to_s.capitalize} is down!"
|
182
|
+
exit 1
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
desc "master_key [FROM=production, TO=local]", "copy master key from FROM to TO"
|
187
|
+
def master_key from="production", to="local"
|
188
|
+
if to == "local"
|
189
|
+
copy :from, from, "config/master.key"
|
190
|
+
end
|
191
|
+
if from == "local"
|
192
|
+
copy :to, to, "config/master.key"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
desc "download_ci_test_coverage", "download latest test coverage information from CI"
|
197
|
+
def download_ci_test_coverage
|
198
|
+
rsync :from, :ci, "coverage"
|
199
|
+
end
|
153
200
|
end
|
154
201
|
|
data/lib/bard/base.rb
CHANGED
@@ -1,28 +1,79 @@
|
|
1
1
|
require "thor"
|
2
2
|
require "term/ansicolor"
|
3
|
-
require "
|
3
|
+
require "open3"
|
4
4
|
|
5
5
|
class Bard::CLI < Thor
|
6
6
|
include Term::ANSIColor
|
7
7
|
|
8
8
|
private
|
9
9
|
|
10
|
-
def fatal(message)
|
11
|
-
raise red("!!! ") + message
|
12
|
-
end
|
13
|
-
|
14
10
|
def run_crucial(command, verbose = false)
|
15
|
-
|
16
|
-
|
17
|
-
if verbose
|
11
|
+
stdout, stderr, status = Open3.capture3(command)
|
12
|
+
failed = status.to_i.nonzero?
|
13
|
+
if verbose || failed
|
18
14
|
$stdout.puts stdout
|
19
15
|
$stderr.puts stderr
|
20
16
|
end
|
17
|
+
if failed
|
18
|
+
puts red("!!! ") + "Running command failed: #{yellow(command)}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
21
|
stdout.chomp
|
22
22
|
end
|
23
23
|
|
24
24
|
def project_name
|
25
25
|
@project_name ||= File.expand_path(".").split("/").last
|
26
26
|
end
|
27
|
+
|
28
|
+
def ssh_command server, command, home: false
|
29
|
+
server = @config.servers[server.to_sym]
|
30
|
+
uri = URI.parse("ssh://#{server.ssh}")
|
31
|
+
command = "cd #{server.path} && #{command}" unless home
|
32
|
+
command = "ssh -tt #{"-p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} '#{command}'"
|
33
|
+
if server.gateway
|
34
|
+
uri = URI.parse("ssh://#{server.gateway}")
|
35
|
+
command = "ssh -tt #{" -p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} \"#{command}\""
|
36
|
+
end
|
37
|
+
command
|
38
|
+
end
|
39
|
+
|
40
|
+
def copy direction, server, path
|
41
|
+
server = @config.servers[server.to_sym]
|
42
|
+
|
43
|
+
uri = URI.parse("ssh://#{server.gateway}")
|
44
|
+
port = uri.port ? "-p#{uri.port}" : ""
|
45
|
+
gateway = server.gateway ? "-oProxyCommand='ssh #{port} #{uri.user}@#{uri.host} -W %h:%p'" : ""
|
46
|
+
|
47
|
+
uri = URI.parse("ssh://#{server.ssh}")
|
48
|
+
port = uri.port ? "-P#{uri.port}" : ""
|
49
|
+
from_and_to = [path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
|
50
|
+
|
51
|
+
from_and_to.reverse! if direction == :from
|
52
|
+
command = "scp #{gateway} #{port} #{from_and_to.join(" ")}"
|
53
|
+
|
54
|
+
run_crucial command
|
55
|
+
end
|
56
|
+
|
57
|
+
def rsync direction, server, path
|
58
|
+
server = @config.servers[server.to_sym]
|
59
|
+
|
60
|
+
uri = URI.parse("ssh://#{server.gateway}")
|
61
|
+
port = uri.port ? "-p#{uri.port}" : ""
|
62
|
+
gateway = server.gateway ? "-oProxyCommand=\"ssh #{port} #{uri.user}@#{uri.host} -W %h:%p\"" : ""
|
63
|
+
|
64
|
+
uri = URI.parse("ssh://#{server.ssh}")
|
65
|
+
port = uri.port ? "-p#{uri.port}" : ""
|
66
|
+
ssh = "-e'ssh #{port} #{gateway}'"
|
67
|
+
|
68
|
+
dest_path = path.dup
|
69
|
+
dest_path = "./#{dest_path}"
|
70
|
+
from_and_to = [dest_path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
|
71
|
+
from_and_to.reverse! if direction == :from
|
72
|
+
from_and_to[-1].sub! %r(/[^/]+$), '/'
|
73
|
+
|
74
|
+
command = "rsync #{ssh} --delete -avz #{from_and_to.join(" ")}"
|
75
|
+
|
76
|
+
run_crucial command
|
77
|
+
end
|
27
78
|
end
|
28
79
|
|
data/lib/bard/ci.rb
CHANGED
@@ -1,69 +1,142 @@
|
|
1
|
+
require "json"
|
2
|
+
require "forwardable"
|
3
|
+
require "open3"
|
4
|
+
|
1
5
|
class Bard::CLI < Thor
|
2
|
-
class CI
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
class CI
|
7
|
+
def initialize project_name, sha, local: false
|
8
|
+
@project_name = project_name
|
9
|
+
@sha = sha
|
10
|
+
@local = !!local
|
11
|
+
@runner = @local ? Local.new(project_name, sha) : Remote.new(project_name, sha)
|
12
|
+
end
|
7
13
|
|
8
|
-
|
9
|
-
while building?
|
10
|
-
elapsed_time = Time.new.to_i - start_time
|
11
|
-
yield elapsed_time, last_time_elapsed
|
12
|
-
sleep(2)
|
13
|
-
end
|
14
|
+
attr_reader :project_name, :sha, :runner
|
14
15
|
|
15
|
-
|
16
|
+
def local?
|
17
|
+
@local
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
`curl -s -I #{ci_host}/?token=botandrose` =~ /\b200 OK\b/
|
20
|
-
end
|
20
|
+
extend Forwardable
|
21
21
|
|
22
|
-
|
23
|
-
raw = `curl -s #{ci_host}/lastBuild/console?token=botandrose`
|
24
|
-
raw[%r{<pre.*?>(.+)</pre>}m, 1]
|
25
|
-
end
|
22
|
+
delegate [:run, :exists?, :console, :last_response] => :runner
|
26
23
|
|
27
|
-
|
24
|
+
class Remote < Struct.new(:project_name, :sha)
|
25
|
+
def run
|
26
|
+
last_time_elapsed = get_last_time_elapsed
|
27
|
+
start
|
28
|
+
sleep(2) until started?
|
28
29
|
|
29
|
-
|
30
|
+
start_time = Time.new.to_i
|
31
|
+
while building?
|
32
|
+
elapsed_time = Time.new.to_i - start_time
|
33
|
+
yield elapsed_time, last_time_elapsed
|
34
|
+
sleep(2)
|
35
|
+
end
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
response.match(/<duration>(\d+)<\/duration>/)
|
34
|
-
$1 ? $1.to_i / 1000 : nil
|
35
|
-
end
|
37
|
+
success?
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
def exists?
|
41
|
+
`curl -s -I #{ci_host}/?token=botandrose` =~ /\b200 OK\b/
|
42
|
+
end
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
44
|
+
def console
|
45
|
+
raw = `curl -s #{ci_host}/lastBuild/console?token=botandrose`
|
46
|
+
raw[%r{<pre.*?>(.+)</pre>}m, 1]
|
47
|
+
end
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
output =~ /"queueId":#{@queueId}\b/
|
51
|
-
end
|
49
|
+
attr_accessor :last_response
|
50
|
+
|
51
|
+
private
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
def get_last_time_elapsed
|
54
|
+
response = `curl -s #{ci_host}/lastStableBuild/api/xml?token=botandrose`
|
55
|
+
response.match(/<duration>(\d+)<\/duration>/)
|
56
|
+
$1 ? $1.to_i / 1000 : nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def ci_host
|
60
|
+
"http://botandrose:thecakeisalie!@ci.botandrose.com/job/#{project_name}"
|
57
61
|
end
|
58
|
-
end
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
def start
|
64
|
+
command = "curl -s -I -X POST '#{ci_host}/buildWithParameters?token=botandrose&GIT_REF=#{sha}'"
|
65
|
+
output = `#{command}`
|
66
|
+
@queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
def started?
|
70
|
+
command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
|
71
|
+
output = `#{command}`
|
72
|
+
JSON.parse(output)["builds"][0]["queueId"] == @queueId
|
73
|
+
end
|
74
|
+
|
75
|
+
def job_id
|
76
|
+
@job_id ||= begin
|
77
|
+
output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
|
78
|
+
output[/"number":(\d+),"queueId":#{@queueId}\b/, 1].to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def building?
|
83
|
+
self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
|
84
|
+
if last_response.blank?
|
85
|
+
sleep(2) # retry
|
86
|
+
self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
|
87
|
+
if last_response.blank?
|
88
|
+
raise "Blank response from CI twice in a row. Aborting!"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
last_response.include? '"building":true'
|
92
|
+
end
|
93
|
+
|
94
|
+
def success?
|
95
|
+
last_response.include? '"result":"SUCCESS"'
|
96
|
+
end
|
63
97
|
end
|
64
98
|
|
65
|
-
|
66
|
-
|
99
|
+
class Local < Struct.new(:project_name, :sha)
|
100
|
+
def run
|
101
|
+
start
|
102
|
+
|
103
|
+
start_time = Time.new.to_i
|
104
|
+
while building?
|
105
|
+
elapsed_time = Time.new.to_i - start_time
|
106
|
+
yield elapsed_time, nil
|
107
|
+
sleep(2)
|
108
|
+
end
|
109
|
+
|
110
|
+
@stdin.close
|
111
|
+
@console = @stdout_and_stderr.read
|
112
|
+
@stdout_and_stderr.close
|
113
|
+
|
114
|
+
success?
|
115
|
+
end
|
116
|
+
|
117
|
+
def exists?
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
def console
|
122
|
+
@console
|
123
|
+
end
|
124
|
+
|
125
|
+
attr_accessor :last_response
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def start
|
130
|
+
@stdin, @stdout_and_stderr, @wait_thread = Open3.popen2e("bin/rake ci")
|
131
|
+
end
|
132
|
+
|
133
|
+
def building?
|
134
|
+
![nil, false].include?(@wait_thread.status)
|
135
|
+
end
|
136
|
+
|
137
|
+
def success?
|
138
|
+
@wait_thread.value.success?
|
139
|
+
end
|
67
140
|
end
|
68
141
|
end
|
69
142
|
end
|
data/lib/bard/config.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
class Bard::CLI < Thor
|
2
|
+
class Config
|
3
|
+
def initialize project_name, path
|
4
|
+
@project_name = project_name
|
5
|
+
@servers = {
|
6
|
+
local: Server.new(
|
7
|
+
project_name,
|
8
|
+
:local,
|
9
|
+
false,
|
10
|
+
"./",
|
11
|
+
false,
|
12
|
+
),
|
13
|
+
gubs: Server.new(
|
14
|
+
project_name,
|
15
|
+
:gubs,
|
16
|
+
"gubito@gubs.pagekite.me",
|
17
|
+
"Sites/#{project_name}",
|
18
|
+
false,
|
19
|
+
),
|
20
|
+
ci: Server.new(
|
21
|
+
project_name,
|
22
|
+
:ci,
|
23
|
+
"jenkins@ci.botandrose.com:22022",
|
24
|
+
"jobs/#{project_name}/workspace",
|
25
|
+
false,
|
26
|
+
),
|
27
|
+
staging: Server.new(
|
28
|
+
project_name,
|
29
|
+
:staging,
|
30
|
+
"www@#{project_name}.botandrose.com:22022",
|
31
|
+
),
|
32
|
+
}
|
33
|
+
load_local_config! path
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :servers
|
37
|
+
|
38
|
+
def server key, &block
|
39
|
+
@servers[key] ||= Server.new(@project_name, key)
|
40
|
+
@servers[key].instance_eval &block if block_given?
|
41
|
+
@servers[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
def data *paths
|
45
|
+
if paths.length == 0
|
46
|
+
Array(@data)
|
47
|
+
else
|
48
|
+
@data = paths
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def load_local_config! path
|
55
|
+
instance_eval File.read(File.expand_path(path)) if File.exist?(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
class Server < Struct.new(:project_name, :key, :ssh, :path, :ping, :gateway)
|
59
|
+
def self.setting *fields
|
60
|
+
fields.each do |field|
|
61
|
+
define_method field do |*args|
|
62
|
+
if args.length == 1
|
63
|
+
send :"#{field}=", args.first
|
64
|
+
elsif args.length == 0
|
65
|
+
super()
|
66
|
+
else
|
67
|
+
raise ArgumentError
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
setting :ssh, :path, :ping, :gateway
|
74
|
+
|
75
|
+
def default_ping
|
76
|
+
uri = URI.parse("ssh://#{ssh}")
|
77
|
+
"http://#{uri.host}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def path(*args)
|
81
|
+
if args.length == 1
|
82
|
+
self.path = args.first
|
83
|
+
elsif args.length == 0
|
84
|
+
super() || project_name
|
85
|
+
else
|
86
|
+
raise ArgumentError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/bard/data.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
class Bard::CLI < Thor
|
2
|
+
class Data < Struct.new(:bard, :from, :to)
|
3
|
+
def call
|
4
|
+
if to == "local"
|
5
|
+
data_pull_db from.to_sym
|
6
|
+
data_pull_assets from.to_sym
|
7
|
+
end
|
8
|
+
if from == "local"
|
9
|
+
data_push_db to.to_sym
|
10
|
+
data_push_assets to.to_sym
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def data_pull_db server
|
17
|
+
bard.instance_eval do
|
18
|
+
run_crucial ssh_command(server, "bin/rake db:dump && gzip -9f db/data.sql")
|
19
|
+
copy :from, server, "db/data.sql.gz"
|
20
|
+
run_crucial "gunzip -f db/data.sql.gz && bin/rake db:load"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def data_push_db server
|
25
|
+
bard.instance_eval do
|
26
|
+
run_crucial "bin/rake db:dump && gzip -9f db/data.sql"
|
27
|
+
copy :to, server, "db/data.sql.gz"
|
28
|
+
run_crucial ssh_command(server, "gunzip -f db/data.sql.gz && bin/rake db:load")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def data_pull_assets server
|
33
|
+
bard.instance_eval do
|
34
|
+
@config.data.each do |path|
|
35
|
+
rsync :from, server, path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def data_push_assets server
|
41
|
+
bard.instance_eval do
|
42
|
+
@config.data.each do |path|
|
43
|
+
rsync :to, server, path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
data/lib/bard/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.44.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micah Geisel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -24,34 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.19.0
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: capistrano
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - '='
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 2.5.10
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - '='
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 2.5.10
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: net-ssh
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '4.0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '4.0'
|
55
27
|
- !ruby/object:Gem::Dependency
|
56
28
|
name: rvm
|
57
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,34 +38,6 @@ dependencies:
|
|
66
38
|
- - ">="
|
67
39
|
- !ruby/object:Gem::Version
|
68
40
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rvm-capistrano
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: systemu
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 1.2.0
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 1.2.0
|
97
41
|
- !ruby/object:Gem::Dependency
|
98
42
|
name: term-ansicolor
|
99
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -174,8 +118,6 @@ extra_rdoc_files: []
|
|
174
118
|
files:
|
175
119
|
- ".gitignore"
|
176
120
|
- ".gitmodules"
|
177
|
-
- ".ruby-gemset"
|
178
|
-
- ".ruby-version"
|
179
121
|
- Gemfile
|
180
122
|
- LICENSE
|
181
123
|
- README.rdoc
|
@@ -201,10 +143,12 @@ files:
|
|
201
143
|
- install_files/specified_node.rb
|
202
144
|
- install_files/specified_ruby.rb
|
203
145
|
- install_files/specified_yarn.rb
|
146
|
+
- install_files/yarn
|
204
147
|
- lib/bard.rb
|
205
148
|
- lib/bard/base.rb
|
206
|
-
- lib/bard/capistrano.rb
|
207
149
|
- lib/bard/ci.rb
|
150
|
+
- lib/bard/config.rb
|
151
|
+
- lib/bard/data.rb
|
208
152
|
- lib/bard/git.rb
|
209
153
|
- lib/bard/version.rb
|
210
154
|
- spec/bard_spec.rb
|
@@ -228,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
228
172
|
- !ruby/object:Gem::Version
|
229
173
|
version: '0'
|
230
174
|
requirements: []
|
231
|
-
rubygems_version: 3.0.
|
175
|
+
rubygems_version: 3.0.3
|
232
176
|
signing_key:
|
233
177
|
specification_version: 4
|
234
178
|
summary: CLI to automate common development tasks.
|
data/.ruby-gemset
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
bard
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
ruby-2.4.3
|
data/lib/bard/capistrano.rb
DELETED
@@ -1,131 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
Capistrano::Configuration.instance(:must_exist).load do
|
4
|
-
require "rvm/capistrano"
|
5
|
-
set :rvm_type, :user
|
6
|
-
ruby_version = File.read(".ruby-version").chomp
|
7
|
-
ruby_gemset = File.read(".ruby-gemset").chomp
|
8
|
-
set :rvm_ruby_string, [ruby_version, ruby_gemset].join("@")
|
9
|
-
|
10
|
-
set :application, File.basename(Dir.pwd)
|
11
|
-
|
12
|
-
role :staging, "www@staging.botandrose.com:22022"
|
13
|
-
role :ci, "jenkins@ci.botandrose.com:22022"
|
14
|
-
|
15
|
-
set :asset_paths, []
|
16
|
-
|
17
|
-
namespace "data" do
|
18
|
-
namespace "pull" do
|
19
|
-
desc "pull data"
|
20
|
-
task "default" do
|
21
|
-
run "cd #{application} && bundle exec rake db:dump && gzip -9f db/data.sql"
|
22
|
-
transfer :down, "#{application}/db/data.sql.gz", "db/data.sql.gz"
|
23
|
-
system "gunzip -f db/data.sql.gz && bundle exec rake db:load"
|
24
|
-
end
|
25
|
-
|
26
|
-
desc "sync the static assets"
|
27
|
-
task "assets" do
|
28
|
-
uri = URI.parse("ssh://#{roles[ENV['ROLES'].to_sym].first.to_s}")
|
29
|
-
portopt = "-e'ssh -p#{uri.port}'" if uri.port
|
30
|
-
|
31
|
-
[asset_paths].flatten.each do |path|
|
32
|
-
dest_path = path.dup
|
33
|
-
dest_path.sub! %r(/[^/]+$), '/'
|
34
|
-
system "rsync #{portopt} --delete -avz #{uri.user}@#{uri.host}:#{application}/#{path} #{dest_path}"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
namespace "push" do
|
40
|
-
desc "push data"
|
41
|
-
task "default" do
|
42
|
-
system "bundle exec rake db:dump && gzip -9f db/data.sql"
|
43
|
-
transfer :up, "db/data.sql.gz", "#{application}/db/data.sql.gz"
|
44
|
-
run "cd #{application} && gunzip -f db/data.sql.gz && bundle exec rake db:load"
|
45
|
-
end
|
46
|
-
|
47
|
-
desc "sync the static assets"
|
48
|
-
task "assets" do
|
49
|
-
uri = URI.parse("ssh://#{roles[ENV['ROLES'].to_sym].first.to_s}")
|
50
|
-
portopt = "-e'ssh -p#{uri.port}'" if uri.port
|
51
|
-
|
52
|
-
[asset_paths].flatten.each do |path|
|
53
|
-
dest_path = path.dup
|
54
|
-
dest_path.sub! %r(/[^/]+$), '/'
|
55
|
-
system "rsync #{portopt} --delete -avz #{path} #{uri.user}@#{uri.host}:#{application}/#{dest_path}"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
after 'data:pull', 'data:pull:assets'
|
62
|
-
after 'data:push', 'data:push:assets'
|
63
|
-
|
64
|
-
desc "push app to production"
|
65
|
-
task :deploy do
|
66
|
-
deploy_roles = roles.keys.include?(:production) ? :production : :staging
|
67
|
-
ENV["ROLES"] = deploy_roles.to_s
|
68
|
-
find_and_execute_task "rvm:install_ruby"
|
69
|
-
system "git push github" if `git remote` =~ /\bgithub\b/
|
70
|
-
run "cd #{application} && git pull origin master && bin/setup", roles: deploy_roles
|
71
|
-
end
|
72
|
-
|
73
|
-
desc "push app to staging"
|
74
|
-
task :stage do
|
75
|
-
ENV["ROLES"] = "staging"
|
76
|
-
find_and_execute_task "rvm:install_ruby"
|
77
|
-
branch = ENV.fetch("BRANCH")
|
78
|
-
run "cd #{application} && git fetch && git checkout -f origin/#{branch} && bin/setup", :roles => :staging
|
79
|
-
end
|
80
|
-
|
81
|
-
desc "test app for downtime"
|
82
|
-
task :ping do
|
83
|
-
deploy_roles = roles.keys.include?(:production) ? :production : :staging
|
84
|
-
ENV["ROLES"] = deploy_roles.to_s
|
85
|
-
role_name = ENV.fetch("ROLES", "production").to_sym
|
86
|
-
server_definition = roles[role_name].first
|
87
|
-
|
88
|
-
url = server_definition.host
|
89
|
-
if role_name == :staging && server_definition.host == "staging.botandrose.com"
|
90
|
-
url = "#{application}.botandrose.com"
|
91
|
-
end
|
92
|
-
|
93
|
-
ping_option = server_definition.options.fetch(:ping, "")
|
94
|
-
if ping_option =~ %r{^/}
|
95
|
-
url += ping_option
|
96
|
-
else
|
97
|
-
url = ping_option
|
98
|
-
end
|
99
|
-
|
100
|
-
command = "curl -sfL #{url} 2>&1 1>/dev/null"
|
101
|
-
unless system command
|
102
|
-
puts "#{role_name.to_s.capitalize} is down!"
|
103
|
-
exit 1
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
desc "push master key"
|
108
|
-
task :push_master_key do
|
109
|
-
transfer :up, "config/master.key", "#{application}/config/master.key"
|
110
|
-
end
|
111
|
-
|
112
|
-
desc "pull master key"
|
113
|
-
task :pull_master_key do
|
114
|
-
transfer :down, "#{application}/config/master.key", "config/master.key"
|
115
|
-
end
|
116
|
-
|
117
|
-
desc "log in via ssh"
|
118
|
-
task :ssh do
|
119
|
-
role = ENV['ROLES'].to_sym
|
120
|
-
path = role == :ci ? "jobs/#{application}/workspace" : application
|
121
|
-
uri = URI.parse("ssh://#{roles[role].first.to_s}")
|
122
|
-
exec "ssh -t #{"-p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} '#{"cd #{path} && " unless ENV['NOCD']}exec $SHELL -l'"
|
123
|
-
end
|
124
|
-
|
125
|
-
desc "download latest test coverage information from CI"
|
126
|
-
task :download_ci_test_coverage do
|
127
|
-
uri = URI.parse("ssh://#{roles[:staging].first}")
|
128
|
-
portopt = "-e'ssh -p#{uri.port}'" if uri.port
|
129
|
-
system "rsync #{portopt} --delete -avz #{uri.user}@#{uri.host}:~jenkins/jobs/#{application}/workspace/coverage ./"
|
130
|
-
end
|
131
|
-
end
|