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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3764347c283aa48f21056e77b433b8ac7e7e3ec6318d58908c7e000a25212e16
4
- data.tar.gz: caafd347a19135cdfc3cdd8596a22380fd4d6058aec23f914ffcfba753d53739
3
+ metadata.gz: 6ae411fccf104a6b049a476bfcc662b26d258687a304aa6a4115078a37aafa3f
4
+ data.tar.gz: ef5e3967d6569b1fb9c58b55ccc668a98fbda97b3f626cd31c3897106ecb04c2
5
5
  SHA512:
6
- metadata.gz: 878ad75a9eb8b63283782bb99b61463e4453cf36ad91f8e2921d1d53fc36ee43013e1011ea6469c5b3aac274bf69bdeb7358b03e64eb319c43f3239e8c4a18a1
7
- data.tar.gz: 2c378767e06c3832203dd13616d526edb2c0c15b68d30e537bcb5cfb657c3db91b24b4dc70b7d18113282acc1930d8ac5eb3f96d15279faf7e4e02d373432424
6
+ metadata.gz: 59a9e9b54fa1669571c4e61cdcb8ff12e19b511ab12a31ac79a9481cab6964ec53730daa789f685053ce658ba340efe5bfb7f1b031114436563a12c13a3d4ff1
7
+ data.tar.gz: 11036591830610869ab72ba374f47bd5d95b4de1f991366636dbc137fd394f75494c5a8ae192f7a089fddfd7e7b7f1756b2e1da6cc6f2ab3ada362ac93747130
data/.gitignore CHANGED
@@ -7,6 +7,7 @@ rdoc
7
7
  tmp
8
8
  .bundle
9
9
  .rvmrc
10
+ .ruby-version
10
11
  Gemfile.lock
11
12
  pkg/*
12
13
 
@@ -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
 
@@ -2,7 +2,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
2
  require 'ruby-debug'
3
3
  require 'grit'
4
4
  require 'spec/expectations'
5
- require 'systemu'
6
5
  gem 'sqlite3-ruby'
7
6
 
8
7
  ENV["PATH"] += ":#{File.dirname(File.expand_path(__FILE__))}/../../bin"
@@ -1,5 +1,6 @@
1
+ require "open3"
1
2
  def type(command)
2
- @status, @stdout, @stderr = systemu command, :env => @env
3
+ @stdout, @stderr, @status = Open3.capture3(@env, command)
3
4
  if ENV['DEBUG']
4
5
  puts '-' * 20
5
6
  puts "Executing command: #{command}"
@@ -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
- "true"
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
- nvm "install" or raise NVMError.new($?.exitstatus)
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 restart
35
- exec %(bash -lc ". #{NVM_PATH}; #{$0}")
19
+ def install_binstub
20
+ system("cd bin && ln -fs ../#{NODE_PATH}")
36
21
  end
37
22
 
38
- def nvm_active?
39
- ENV.key?("NVM_DIR")
23
+ def node_installed?
24
+ File.exist?(NODE_PATH) && `#{NODE_PATH} --version`.chomp == NODE_VERSION
40
25
  end
41
26
 
42
- def nvm command
43
- system(". #{NVM_PATH}; nvm #{command}")
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
- YARN_PATH = "node_modules/yarn/bin/yarn"
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 unless binstub_installed?
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(". ~/.nvm/nvm.sh && npm install yarn@#{version} --no-save")
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 -s ../#{YARN_PATH}")
20
+ system("cd bin && ln -fs ../#{YARN_PATH}")
20
21
  end
21
22
 
22
23
  def yarn_installed?
23
- File.exist?(YARN_PATH) && `#{YARN_PATH} --version`.chomp == version
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
 
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ argv0=$(echo "$0" | sed -e 's,\\,/,g')
3
+ basedir=$(dirname "$(readlink -f "$0" || echo "$argv0")")
4
+ exec "$basedir/node" "$basedir/yarn.js" "$@"
5
+
@@ -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 = "production", to = "local")
12
- exec "cap _2.5.10_ data:pull ROLES=#{from}" if to == "local"
13
- exec "cap _2.5.10_ data:push ROLES=#{to}" if from == "local"
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 File.read("Capfile").include?("role :production")
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
- run_crucial "cap _2.5.10_ stage BRANCH=#{branch}", options.verbose?
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
- unless system("cap _2.5.10_ ping ROLES=staging >/dev/null 2>&1")
30
- puts red("Staging is now down!")
31
- end
34
+ ping :staging
32
35
  end
33
36
 
34
- method_options %w( verbose -v ) => :boolean, %w( skip-ci ) => :boolean
35
- desc "deploy [BRANCH=HEAD]", "checks that branch is a ff with master, checks with ci, and then merges into master and deploys to production, and deletes branch."
36
- def deploy branch=Git.current_branch
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
- if not Git.fast_forward_merge? "origin/master", branch
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
- run_crucial "cap _2.5.10_ deploy", options.verbose?
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
- case Git.current_branch
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
- unless system("cap _2.5.10_ ping ROLES=production >/dev/null 2>&1")
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( verbose -v ) => :boolean
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
- run_crucial "cap _2.5.10_ download_ci_test_coverage"
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( home ) => :boolean
148
+ method_options %w[home] => :boolean
136
149
  desc "ssh [TO=production]", "logs into the specified server via SSH"
137
- def ssh to="production"
138
- if to == "gubs"
139
- command = "exec $SHELL"
140
- command = "cd Sites/#{project_name} && #{command}" unless options["home"]
141
- command = %(ssh -t gubito@gubs.pagekite.me 'bash -l -c "exec ./vagrant \\"#{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 "cap _2.5.10_ ssh ROLES=#{to}#{" NOCD=1" if options["home"]}"
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
 
@@ -1,28 +1,79 @@
1
1
  require "thor"
2
2
  require "term/ansicolor"
3
- require "systemu"
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
- status, stdout, stderr = systemu command
16
- fatal "Running command: #{yellow(command)}: #{stderr}" if status.to_i.nonzero?
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
 
@@ -1,69 +1,142 @@
1
+ require "json"
2
+ require "forwardable"
3
+ require "open3"
4
+
1
5
  class Bard::CLI < Thor
2
- class CI < Struct.new(:project_name, :sha)
3
- def run
4
- last_time_elapsed = get_last_time_elapsed
5
- start
6
- sleep(2) until started?
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
- start_time = Time.new.to_i
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
- success?
16
+ def local?
17
+ @local
16
18
  end
17
19
 
18
- def exists?
19
- `curl -s -I #{ci_host}/?token=botandrose` =~ /\b200 OK\b/
20
- end
20
+ extend Forwardable
21
21
 
22
- def console
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
- attr_accessor :last_response
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
- private
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
- def get_last_time_elapsed
32
- response = `curl -s #{ci_host}/lastStableBuild/api/xml?token=botandrose`
33
- response.match(/<duration>(\d+)<\/duration>/)
34
- $1 ? $1.to_i / 1000 : nil
35
- end
37
+ success?
38
+ end
36
39
 
37
- def ci_host
38
- "http://botandrose:thecakeisalie!@ci.botandrose.com/job/#{project_name}"
39
- end
40
+ def exists?
41
+ `curl -s -I #{ci_host}/?token=botandrose` =~ /\b200 OK\b/
42
+ end
40
43
 
41
- def start
42
- command = "curl -s -I -X POST '#{ci_host}/buildWithParameters?token=botandrose&GIT_REF=#{sha}'"
43
- output = `#{command}`
44
- @queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
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
- def started?
48
- command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
49
- output = `#{command}`
50
- output =~ /"queueId":#{@queueId}\b/
51
- end
49
+ attr_accessor :last_response
50
+
51
+ private
52
52
 
53
- def job_id
54
- @job_id ||= begin
55
- output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
56
- output[/"number":(\d+),"queueId":#{@queueId}\b/, 1].to_i
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
- def building?
61
- self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
62
- last_response.include? '"building":true'
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
- def success?
66
- last_response.include? '"result":"SUCCESS"'
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
@@ -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
@@ -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
+
@@ -1,4 +1,4 @@
1
1
  module Bard
2
- VERSION = "0.41.1"
2
+ VERSION = "0.44.0"
3
3
  end
4
4
 
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.41.1
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: 2019-08-21 00:00:00.000000000 Z
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.4
175
+ rubygems_version: 3.0.3
232
176
  signing_key:
233
177
  specification_version: 4
234
178
  summary: CLI to automate common development tasks.
@@ -1 +0,0 @@
1
- bard
@@ -1 +0,0 @@
1
- ruby-2.4.3
@@ -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