bard 0.68.0 → 0.69.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: 9d6ffeb50c8bcafe4462e42e462d34c81a1d44690c38cef2b44682e11fed4854
4
- data.tar.gz: 602a01d2fd94765904b0f767945149e8984d84856b2598f8a44afceda82ac62b
3
+ metadata.gz: 2605af57059f326785460af9d28344b196ad3f87eae1a672998fed6b0f6956ae
4
+ data.tar.gz: 666e6f8637a2342e981b19a8e9a2e2a3883be06e1401fb71b7358b5d44db4c68
5
5
  SHA512:
6
- metadata.gz: f5c40d167fe14e473dd56a3e14ccebe922068dcff113783b7074df31de8de100da799c10470a00779fc257cd85efa99568f8accf988886cafc7e0606f8423e6a
7
- data.tar.gz: 97fa25538c174668498f4a38de33b9cc17e4d60304675256a4a130d50920bfd814fc77aa48c76b88dda2d2dbe44648323cdc45736debe4b184201b3bf82e2a55
6
+ metadata.gz: 28d95083d05f69bdad245c7d8028d5f509b8d233a6a7d04708db1bfdad3fcf67d919deb011976ce029f95b7a72c24f3ed76c5dfbc4137c6fa204a43fd8329196
7
+ data.tar.gz: 42255ed7d0f2d959103f5340ad6c5e5289fd1a6713a831be791e6e5849f2caf197a5223db5812b2eddc107be1ba30945137d9cfdb4d7cde96f290910811c0ca2
data/bard.gemspec CHANGED
@@ -9,7 +9,6 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Micah Geisel"]
10
10
  spec.email = ["micah@botandrose.com"]
11
11
  spec.summary = "CLI to automate common development tasks."
12
- spec.description = "CLI to automate common development tasks."
13
12
  spec.homepage = "http://github.com/botandrose/bard"
14
13
  spec.license = "MIT"
15
14
 
@@ -21,7 +20,6 @@ Gem::Specification.new do |spec|
21
20
  spec.add_dependency "thor", ">= 0.19.0"
22
21
  spec.add_dependency "rvm"
23
22
  spec.add_dependency "term-ansicolor", ">= 1.0.3"
24
- spec.add_dependency "bard-rake", ">= 0.19.0"
25
23
 
26
24
  spec.add_development_dependency "byebug"
27
25
  spec.add_development_dependency "rspec"
data/bin/bard CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require "rubygems"
3
- require_relative "../lib/bard"
2
+ require_relative "../lib/bard/cli"
4
3
  Bard::CLI.start ARGV
5
4
 
@@ -1,8 +1,7 @@
1
- require "thor"
2
1
  require "time"
3
2
  require "bard/github"
4
3
 
5
- class Bard::CLI < Thor
4
+ module Bard
6
5
  class CI
7
6
  class GithubActions < Struct.new(:project_name, :branch, :sha)
8
7
  def run
@@ -1,6 +1,6 @@
1
1
  require "json"
2
2
 
3
- class Bard::CLI < Thor
3
+ module Bard
4
4
  class CI
5
5
  class Jenkins < Struct.new(:project_name, :branch, :sha)
6
6
  def run
data/lib/bard/ci/local.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "open3"
2
2
 
3
- class Bard::CLI < Thor
3
+ module Bard
4
4
  class CI
5
5
  class Local < Struct.new(:project_name, :branch, :sha)
6
6
  def run
data/lib/bard/ci.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "forwardable"
2
2
 
3
- class Bard::CLI < Thor
3
+ module Bard
4
4
  class CI
5
5
  def initialize project_name, branch, local: false
6
6
  @project_name = project_name
data/lib/bard/cli.rb ADDED
@@ -0,0 +1,262 @@
1
+ # this file gets loaded in the CLI context, not the Rails boot context
2
+
3
+ require "thor"
4
+ require "bard/git"
5
+ require "bard/ci"
6
+ require "bard/copy"
7
+ require "bard/github"
8
+ require "bard/ping"
9
+ require "bard/config"
10
+ require "bard/command"
11
+ require "term/ansicolor"
12
+ require "open3"
13
+ require "uri"
14
+
15
+ module Bard
16
+ class CLI < Thor
17
+ include Term::ANSIColor
18
+
19
+ class_option :verbose, type: :boolean, aliases: :v
20
+
21
+ desc "data --from=production --to=local", "copy database and assets from from to to"
22
+ option :from, default: "production"
23
+ option :to, default: "local"
24
+ def data
25
+ from = config[options[:from]]
26
+ to = config[options[:to]]
27
+
28
+ if to.key == :production
29
+ url = to.ping.first
30
+ puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
31
+ answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
32
+ if answer != url
33
+ puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
34
+ exit 1
35
+ end
36
+ end
37
+
38
+ puts "Dumping #{from.key} database to file..."
39
+ from.run! "bin/rake db:dump"
40
+
41
+ puts "Transfering file from #{from.key} to #{to.key}..."
42
+ from.copy_file "db/data.sql.gz", to: to, verbose: true
43
+
44
+ puts "Loading file into #{to.key} database..."
45
+ to.run! "bin/rake db:load"
46
+
47
+ config.data.each do |path|
48
+ puts "Synchronizing files in #{path}..."
49
+ from.copy_dir path, to: to, verbose: true
50
+ end
51
+ end
52
+
53
+ desc "stage [branch=HEAD]", "pushes current branch, and stages it"
54
+ def stage branch=Git.current_branch
55
+ unless config.servers.key?(:production)
56
+ 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.")
57
+ end
58
+
59
+ run! "git push -u origin #{branch}", verbose: true
60
+ config[:staging].run! "git fetch && git checkout -f origin/#{branch} && bin/setup"
61
+ puts green("Stage Succeeded")
62
+
63
+ ping :staging
64
+ end
65
+
66
+ option :"skip-ci", type: :boolean
67
+ option :"local-ci", type: :boolean
68
+ 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."
69
+ def deploy to=:production
70
+ branch = Git.current_branch
71
+
72
+ if branch == "master"
73
+ if !Git.up_to_date_with_remote?(branch)
74
+ run! "git push origin #{branch}:#{branch}"
75
+ end
76
+ invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
77
+
78
+ else
79
+ run! "git fetch origin master:master"
80
+
81
+ unless Git.fast_forward_merge?("origin/master", branch)
82
+ puts "The master branch has advanced. Attempting rebase..."
83
+ run! "git rebase origin/master"
84
+ end
85
+
86
+ run! "git push -f origin #{branch}:#{branch}"
87
+
88
+ invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
89
+
90
+ run! "git push origin #{branch}:master"
91
+ run! "git fetch origin master:master"
92
+ end
93
+
94
+ if `git remote` =~ /\bgithub\b/
95
+ run! "git push github"
96
+ end
97
+
98
+ config[to].run! "git pull origin master && bin/setup"
99
+
100
+ puts green("Deploy Succeeded")
101
+
102
+ if branch != "master"
103
+ puts "Deleting branch: #{branch}"
104
+ run! "git push --delete origin #{branch}"
105
+
106
+ if branch == Git.current_branch
107
+ run! "git checkout master"
108
+ end
109
+
110
+ run! "git branch -D #{branch}"
111
+ end
112
+
113
+ ping to
114
+ end
115
+
116
+ option :"local-ci", type: :boolean
117
+ option :status, type: :boolean
118
+ desc "ci [branch=HEAD]", "runs ci against BRANCH"
119
+ def ci branch=Git.current_branch
120
+ ci = CI.new(project_name, branch, local: options["local-ci"])
121
+ if ci.exists?
122
+ return puts ci.status if options["status"]
123
+
124
+ puts "Continuous integration: starting build on #{branch}..."
125
+
126
+ success = ci.run do |elapsed_time, last_time|
127
+ if last_time
128
+ percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
129
+ output = " Estimated completion: #{percentage}%"
130
+ else
131
+ output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
132
+ end
133
+ print "\x08" * output.length
134
+ print output
135
+ $stdout.flush
136
+ end
137
+
138
+ if success
139
+ puts
140
+ puts "Continuous integration: success!"
141
+ puts "Deploying..."
142
+ else
143
+ puts
144
+ puts ci.last_response
145
+ puts ci.console
146
+ puts red("Automated tests failed!")
147
+ exit 1
148
+ end
149
+
150
+ else
151
+ puts red("No CI found for #{project_name}!")
152
+ puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
153
+ exit 1
154
+ end
155
+ end
156
+
157
+ desc "open [server=production]", "opens the url in the web browser."
158
+ def open server=:production
159
+ exec "xdg-open #{config[server].ping.first}"
160
+ end
161
+
162
+ desc "hurt <command>", "reruns a command until it fails"
163
+ def hurt *args
164
+ 1.upto(Float::INFINITY) do |count|
165
+ puts "Running attempt #{count}"
166
+ system *args
167
+ unless $?.success?
168
+ puts "Ran #{count-1} times before failing"
169
+ break
170
+ end
171
+ end
172
+ end
173
+
174
+ option :home, type: :boolean
175
+ desc "ssh [to=production]", "logs into the specified server via SSH"
176
+ def ssh to=:production
177
+ config[to].exec! "exec $SHELL -l", home: options[:home]
178
+ end
179
+
180
+ desc "install", "copies bin/setup and bin/ci scripts into current project."
181
+ def install
182
+ install_files_path = File.expand_path(File.join(__dir__, "../install_files/*"))
183
+ system "cp -R #{install_files_path} bin/"
184
+ github_files_path = File.expand_path(File.join(__dir__, "../install_files/.github"))
185
+ system "cp -R #{github_files_path} ./"
186
+ end
187
+
188
+ desc "setup", "installs app in nginx"
189
+ def setup
190
+ path = "/etc/nginx/sites-available/#{project_name}"
191
+ dest_path = path.sub("sites-available", "sites-enabled")
192
+ server_name = "#{project_name}.localhost"
193
+
194
+ system "sudo tee #{path} >/dev/null <<-EOF
195
+ server {
196
+ listen 80;
197
+ server_name #{server_name};
198
+
199
+ root #{Dir.pwd}/public;
200
+ passenger_enabled on;
201
+
202
+ location ~* \\.(ico|css|js|gif|jp?g|png|webp) {
203
+ access_log off;
204
+ if (\\$request_filename ~ \"-[0-9a-f]{32}\\.\") {
205
+ expires max;
206
+ add_header Cache-Control public;
207
+ }
208
+ }
209
+ gzip_static on;
210
+ }
211
+ EOF"
212
+ system "sudo ln -sf #{path} #{dest_path}" if !File.exist?(dest_path)
213
+ system "sudo service nginx restart"
214
+ end
215
+
216
+ desc "ping [server=production]", "hits the server over http to verify that its up."
217
+ def ping server=:production
218
+ server = config[server]
219
+ down_urls = Bard::Ping.call(config[server])
220
+ down_urls.each { |url| puts "#{url} is down!" }
221
+ exit 1 if down_urls.any?
222
+ end
223
+
224
+ option :on, default: "production"
225
+ desc "command <command> --on=production", "run the given command on the remote server"
226
+ def command command
227
+ server = config[options[:on]]
228
+ server.run! remote_command, verbose: true
229
+ end
230
+
231
+ desc "master_key --from=production --to=local", "copy master key from from to to"
232
+ option :from, default: "production"
233
+ option :to, default: "local"
234
+ def master_key
235
+ from = config[options[:from]]
236
+ to = config[options[:to]]
237
+ from.copy_file "config/master.key", to:
238
+ end
239
+
240
+ desc "vim [branch=master]", "open all files that have changed since master"
241
+ def vim branch="master"
242
+ exec "vim -p `git diff #{branch} --name-only | grep -v sass$ | tac`"
243
+ end
244
+
245
+ def self.exit_on_failure? = true
246
+
247
+ private
248
+
249
+ def config
250
+ @config ||= Bard::Config.new(project_name, path: "bard.rb")
251
+ end
252
+
253
+ def project_name
254
+ @project_name ||= File.expand_path(".").split("/").last
255
+ end
256
+
257
+ def run!(...)
258
+ Bard::Command.run!(...)
259
+ end
260
+ end
261
+ end
262
+
@@ -0,0 +1,64 @@
1
+ module Bard
2
+ class Command < Struct.new(:command, :on, :home)
3
+ def self.run! command, on: :local, home: false, verbose: false
4
+ new(command, on, home).run! verbose: verbose
5
+ end
6
+
7
+ def self.exec! command, on: :local, home: false
8
+ new(command, on, home).exec!
9
+ end
10
+
11
+ def run! verbose: false
12
+ failed = false
13
+
14
+ if verbose
15
+ failed = !(system full_command)
16
+ else
17
+ stdout, stderr, status = Open3.capture3(full_command)
18
+ failed = status.to_i.nonzero?
19
+ if failed
20
+ $stdout.puts stdout
21
+ $stderr.puts stderr
22
+ end
23
+ end
24
+
25
+ if failed
26
+ raise "Running command failed: #{full_command}"
27
+ # puts red("!!! ") + "Running command failed: #{yellow(command)}"
28
+ # exit 1
29
+ end
30
+ end
31
+
32
+ def exec!
33
+ exec full_command
34
+ end
35
+
36
+ private
37
+
38
+ def full_command
39
+ if on.to_sym == :local
40
+ command
41
+ else
42
+ remote_command
43
+ end
44
+ end
45
+
46
+ def remote_command
47
+ uri = URI.parse("ssh://#{on.ssh}")
48
+ ssh_key = on.ssh_key ? "-i #{on.ssh_key} " : ""
49
+ cmd = command
50
+ if on.env
51
+ cmd = "#{on.env} #{command}"
52
+ end
53
+ unless home
54
+ cmd = "cd #{on.path} && #{cmd}"
55
+ end
56
+ cmd = "ssh -tt #{ssh_key}#{"-p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} '#{cmd}'"
57
+ if on.gateway
58
+ uri = URI.parse("ssh://#{on.gateway}")
59
+ cmd = "ssh -tt #{" -p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} \"#{cmd}\""
60
+ end
61
+ cmd
62
+ end
63
+ end
64
+ end
data/lib/bard/config.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require "uri"
2
+ require "bard/command"
3
+ require "bard/copy"
2
4
 
3
5
  module Bard
4
6
  class Config
@@ -48,11 +50,20 @@ module Bard
48
50
  attr_reader :project_name, :servers
49
51
 
50
52
  def server key, &block
53
+ key = key.to_sym
51
54
  @servers[key] ||= Server.new(project_name, key)
52
55
  @servers[key].instance_eval &block if block_given?
53
56
  @servers[key]
54
57
  end
55
58
 
59
+ def [] key
60
+ key = key.to_sym
61
+ if @servers[key].nil? && key == :production
62
+ key = :staging
63
+ end
64
+ @servers[key]
65
+ end
66
+
56
67
  def data *paths
57
68
  if paths.length == 0
58
69
  Array(@data)
@@ -123,6 +134,26 @@ module Bard
123
134
  raise ArgumentError
124
135
  end
125
136
  end
137
+
138
+ def to_sym
139
+ key
140
+ end
141
+
142
+ def run! command, home: false, verbose: false
143
+ Bard::Command.run! command, on: self, home:, verbose:
144
+ end
145
+
146
+ def exec! command, home: false
147
+ Bard::Command.exec! command, on: self, home:
148
+ end
149
+
150
+ def copy_file path, to:, verbose: false
151
+ Bard::Copy.file path, from: self, to:, verbose:
152
+ end
153
+
154
+ def copy_dir
155
+ Bard::Copy.dir path, from: self, to:, verbose:
156
+ end
126
157
  end
127
158
  end
128
159
  end
data/lib/bard/copy.rb ADDED
@@ -0,0 +1,101 @@
1
+ module Bard
2
+ class Copy < Struct.new(:path, :from, :to, :verbose)
3
+ def self.file path, from:, to:, verbose: false
4
+ new(path, from, to, verbose).scp
5
+ end
6
+
7
+ def self.dir path, from:, to:, verbose: false
8
+ new(path, from, to, verbose).rsync
9
+ end
10
+
11
+ def scp
12
+ if from.key == :local
13
+ scp_using_local :to, to
14
+ elsif to.key == :local
15
+ scp_using_local :from, from
16
+ else
17
+ scp_as_mediator
18
+ end
19
+ end
20
+
21
+ def scp_using_local direction, server
22
+ uri = URI.parse("ssh://#{server.gateway}")
23
+ port = uri.port ? "-p#{uri.port}" : ""
24
+ gateway = server.gateway ? "-oProxyCommand='ssh #{port} #{uri.user}@#{uri.host} -W %h:%p'" : ""
25
+
26
+ ssh_key = server.ssh_key ? "-i #{server.ssh_key}" : ""
27
+
28
+ uri = URI.parse("ssh://#{server.ssh}")
29
+ port = uri.port ? "-P#{uri.port}" : ""
30
+ from_and_to = [path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
31
+
32
+ from_and_to.reverse! if direction == :from
33
+ command = "scp #{gateway} #{ssh_key} #{port} #{from_and_to.join(" ")}"
34
+
35
+ Bard::Command.run! command, verbose: verbose
36
+ end
37
+
38
+ def scp_as_mediator
39
+ raise NotImplementedError if from.gateway || to.gateway || from.ssh_key || to.ssh_key
40
+
41
+ from_uri = URI.parse("ssh://#{from.ssh}")
42
+ from_str = "scp://#{from_uri.user}@#{from_uri.host}:#{from_uri.port || 22}/#{from.path}/#{path}"
43
+
44
+ to_uri = URI.parse("ssh://#{to.ssh}")
45
+ to_str = "scp://#{to_uri.user}@#{to_uri.host}:#{to_uri.port || 22}/#{to.path}/#{path}"
46
+
47
+ command = "scp -o ForwardAgent=yes #{from_str} #{to_str}"
48
+
49
+ Bard::Command.run! command, verbose: verbose
50
+ end
51
+
52
+ def rsync
53
+ if from.key == :local
54
+ rsync_using_local :to, to
55
+ elsif to.key == :local
56
+ rsync_using_local :from, from
57
+ else
58
+ rsync_as_mediator from, to
59
+ end
60
+ end
61
+
62
+ def rsync_using_local direction, server
63
+ uri = URI.parse("ssh://#{server.gateway}")
64
+ port = uri.port ? "-p#{uri.port}" : ""
65
+ gateway = server.gateway ? "-oProxyCommand=\"ssh #{port} #{uri.user}@#{uri.host} -W %h:%p\"" : ""
66
+
67
+ ssh_key = server.ssh_key ? "-i #{server.ssh_key}" : ""
68
+ uri = URI.parse("ssh://#{server.ssh}")
69
+ port = uri.port ? "-p#{uri.port}" : ""
70
+ ssh = "-e'ssh #{ssh_key} #{port} #{gateway}'"
71
+
72
+ dest_path = path.dup
73
+ dest_path = "./#{dest_path}"
74
+ from_and_to = [dest_path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
75
+ from_and_to.reverse! if direction == :from
76
+ from_and_to[-1].sub! %r(/[^/]+$), '/'
77
+
78
+ command = "rsync #{ssh} --delete --info=progress2 -az #{from_and_to.join(" ")}"
79
+
80
+ Bard::Command.run! command, verbose: verbose
81
+ end
82
+
83
+ def rsync_as_mediator from, to
84
+ raise NotImplementedError if from.gateway || to.gateway || from.ssh_key || to.ssh_key
85
+
86
+ dest_path = path.dup
87
+ dest_path = "./#{dest_path}"
88
+
89
+ from_uri = URI.parse("ssh://#{from.ssh}")
90
+ from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
91
+
92
+ to_uri = URI.parse("ssh://#{to.ssh}")
93
+ to_str = "#{to_uri.user}@#{to_uri.host}:#{to.path}/#{path}"
94
+ to_str.sub! %r(/[^/]+$), '/'
95
+
96
+ command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no\" --delete --info=progress2 -az #{from.path}/#{path} #{to_str}')
97
+
98
+ Bard::Command.run! command, verbose: verbose
99
+ end
100
+ end
101
+ end
data/lib/bard/git.rb CHANGED
@@ -1,29 +1,31 @@
1
- module Bard::CLI::Git
2
- module_function
1
+ module Bard
2
+ module Git
3
+ module_function
3
4
 
4
- def current_branch
5
- ref = `git symbolic-ref HEAD 2>&1`.chomp
6
- return false if ref =~ /^fatal:/
7
- ref.sub(/refs\/heads\//, '') # refs/heads/master ... we want "master"
8
- end
5
+ def current_branch
6
+ ref = `git symbolic-ref HEAD 2>&1`.chomp
7
+ return false if ref =~ /^fatal:/
8
+ ref.sub(/refs\/heads\//, '') # refs/heads/master ... we want "master"
9
+ end
9
10
 
10
- def current_sha
11
- sha_of("HEAD")
12
- end
11
+ def current_sha
12
+ sha_of("HEAD")
13
+ end
13
14
 
14
- def fast_forward_merge?(root, branch)
15
- root_head = sha_of(root)
16
- branch_head = sha_of(branch)
17
- common_ancestor = `git merge-base #{root_head} #{branch_head}`.chomp
18
- common_ancestor == root_head
19
- end
15
+ def fast_forward_merge?(root, branch)
16
+ root_head = sha_of(root)
17
+ branch_head = sha_of(branch)
18
+ common_ancestor = `git merge-base #{root_head} #{branch_head}`.chomp
19
+ common_ancestor == root_head
20
+ end
20
21
 
21
- def up_to_date_with_remote? branch
22
- sha_of(branch) == sha_of("origin/#{branch}")
23
- end
22
+ def up_to_date_with_remote? branch
23
+ sha_of(branch) == sha_of("origin/#{branch}")
24
+ end
24
25
 
25
- def sha_of ref
26
- `git rev-parse #{ref}`.chomp
26
+ def sha_of ref
27
+ `git rev-parse #{ref}`.chomp
28
+ end
27
29
  end
28
30
  end
29
31
 
data/lib/bard/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Bard
2
- VERSION = "0.68.0"
2
+ VERSION = "0.69.0"
3
3
  end
4
4
 
data/lib/bard.rb CHANGED
@@ -1,253 +1,3 @@
1
- module Bard; end
2
-
3
- require "bard/base"
4
- require "bard/git"
5
- require "bard/ci"
6
- require "bard/data"
7
- require "bard/github"
8
- require "bard/ping"
9
- require "bard/config"
10
- require "bard/remote_command"
11
-
12
- class Bard::CLI < Thor
13
- include Thor::Actions
14
-
15
- def initialize(*args, **kwargs, &block)
16
- super
17
- @config = Bard::Config.new(project_name, path: "bard.rb")
18
- end
19
-
20
- desc "data --from=production --to=local", "copy database and assets from from to to"
21
- method_options %w[from] => :string, %w[to] => :string
22
- def data
23
- default_from = @config.servers.key?(:production) ? "production" : "staging"
24
- from = options.fetch(:from, default_from)
25
- to = options.fetch(:to, "local")
26
- Data.new(self, from, to).call
27
- end
28
-
29
- method_options %w( verbose -v ) => :boolean
30
- desc "stage [BRANCH=HEAD]", "pushes current branch, and stages it"
31
- def stage branch=Git.current_branch
32
- unless @config.servers.key?(:production)
33
- 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.")
34
- end
35
-
36
- run_crucial "git push -u origin #{branch}", verbose: true
37
- command = "git fetch && git checkout -f origin/#{branch} && bin/setup"
38
- run_crucial ssh_command(:staging, command)
39
- puts green("Stage Succeeded")
40
-
41
- ping :staging
42
- end
43
-
44
- method_options %w[verbose -v] => :boolean, %w[skip-ci] => :boolean, %w[local-ci -l] => :boolean
45
- 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."
46
- def deploy to=nil
47
- branch = Git.current_branch
48
-
49
- if branch == "master"
50
- run_crucial "git push origin #{branch}:#{branch}" if !Git.up_to_date_with_remote?(branch)
51
- invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
52
-
53
- else
54
- run_crucial "git fetch origin master:master"
55
-
56
- unless Git.fast_forward_merge?("origin/master", branch)
57
- puts "The master branch has advanced. Attempting rebase..."
58
- run_crucial "git rebase origin/master"
59
- end
60
-
61
- run_crucial "git push -f origin #{branch}:#{branch}"
62
-
63
- invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]
64
-
65
- run_crucial "git push origin #{branch}:master"
66
- run_crucial "git fetch origin master:master"
67
- end
68
-
69
- if `git remote` =~ /\bgithub\b/
70
- run_crucial "git push github"
71
- end
72
-
73
- to ||= @config.servers.key?(:production) ? :production : :staging
74
-
75
- command = "git pull origin master && bin/setup"
76
- run_crucial ssh_command(to, command)
77
-
78
- puts green("Deploy Succeeded")
79
-
80
- if branch != "master"
81
- puts "Deleting branch: #{branch}"
82
- run_crucial "git push --delete origin #{branch}"
83
-
84
- if branch == Git.current_branch
85
- run_crucial "git checkout master"
86
- end
87
-
88
- run_crucial "git branch -D #{branch}"
89
- end
90
-
91
- ping to
92
- end
93
-
94
- method_options %w[verbose -v] => :boolean, %w[local-ci -l] => :boolean, %w[status -s] => :boolean
95
- desc "ci [BRANCH=HEAD]", "runs ci against BRANCH"
96
- def ci branch=Git.current_branch
97
- ci = CI.new(project_name, branch, local: options["local-ci"])
98
- if ci.exists?
99
- return puts ci.status if options["status"]
100
-
101
- puts "Continuous integration: starting build on #{branch}..."
102
-
103
- success = ci.run do |elapsed_time, last_time|
104
- if last_time
105
- percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
106
- output = " Estimated completion: #{percentage}%"
107
- else
108
- output = " No estimated completion time. Elapsed time: #{elapsed_time} sec"
109
- end
110
- print "\x08" * output.length
111
- print output
112
- $stdout.flush
113
- end
114
-
115
- if success
116
- puts
117
- puts "Continuous integration: success!"
118
- if ci.jenkins? && File.exist?("coverage")
119
- puts "Downloading test coverage from CI..."
120
- download_ci_test_coverage
121
- end
122
- puts "Deploying..."
123
- else
124
- puts
125
- puts ci.last_response
126
- puts ci.console
127
- puts red("Automated tests failed!")
128
- exit 1
129
- end
130
-
131
- else
132
- puts red("No CI found for #{project_name}!")
133
- puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
134
- exit 1
135
- end
136
- end
137
-
138
- desc "open [SERVER=production]", "opens the url in the web browser."
139
- def open server=nil
140
- server ||= @config.servers.key?(:production) ? :production : :staging
141
- server = @config.servers[server.to_sym]
142
- exec "xdg-open #{server.ping.first}"
143
- end
144
-
145
- desc "hurt", "reruns a command until it fails"
146
- def hurt *args
147
- 1.upto(Float::INFINITY) do |count|
148
- puts "Running attempt #{count}"
149
- system *args
150
- unless $?.success?
151
- puts "Ran #{count-1} times before failing"
152
- break
153
- end
154
- end
155
- end
156
-
157
- method_options %w[home] => :boolean
158
- desc "ssh [TO=production]", "logs into the specified server via SSH"
159
- def ssh to=:production
160
- command = "exec $SHELL -l"
161
- if to == "theia" && !options["home"]
162
- server = @config.servers[:theia]
163
- command = %(bash -lic "exec ./vagrant \\"cd #{server.path} && #{command}\\"")
164
- exec ssh_command(to, command, home: true)
165
- else
166
- exec ssh_command(to, command, home: options["home"])
167
- end
168
- end
169
-
170
- desc "install", "copies bin/setup and bin/ci scripts into current project."
171
- def install
172
- install_files_path = File.expand_path(File.join(__dir__, "../install_files/*"))
173
- system "cp -R #{install_files_path} bin/"
174
- github_files_path = File.expand_path(File.join(__dir__, "../install_files/.github"))
175
- system "cp -R #{github_files_path} ./"
176
- end
177
-
178
- desc "setup", "installs app in nginx"
179
- def setup
180
- path = "/etc/nginx/sites-available/#{project_name}"
181
- dest_path = path.sub("sites-available", "sites-enabled")
182
- server_name = "#{project_name}.localhost"
183
-
184
- create_file path, <<~NGINX
185
- server {
186
- listen 80;
187
- server_name #{server_name};
188
-
189
- root #{Dir.pwd}/public;
190
- passenger_enabled on;
191
-
192
- location ~* \\.(ico|css|js|gif|jp?g|png|webp) {
193
- access_log off;
194
- if ($request_filename ~ "-[0-9a-f]{32}\\.") {
195
- expires max;
196
- add_header Cache-Control public;
197
- }
198
- }
199
- gzip_static on;
200
- }
201
- NGINX
202
-
203
- FileUtils.ln_sf(path, dest_path) if !File.exist?(dest_path)
204
- run "service nginx restart"
205
- rescue Errno::EACCES
206
- raise InvocationError.new("please re-run with sudo")
207
- end
208
-
209
- desc "ping [SERVER=production]", "hits the server over http to verify that its up."
210
- def ping server=:production
211
- server = @config.servers[server.to_sym]
212
- down_urls = Bard::Ping.call(server)
213
- down_urls.each { |url| puts "#{url} is down!" }
214
- exit 1 if down_urls.any?
215
- end
216
-
217
- desc "command <command> --on=production", "run the given command on the remote server"
218
- method_options %w[on] => :string
219
- def command command
220
- default_from = @config.servers.key?(:production) ? "production" : "staging"
221
- on = options.fetch(:on, default_from)
222
- server = @config.servers[on.to_sym]
223
- remote_command = Bard::RemoteCommand.new(server, command).local_command
224
- run_crucial remote_command, verbose: true
225
- end
226
-
227
- desc "master_key --from=production --to=local", "copy master key from from to to"
228
- method_options %w[from] => :string, %w[to] => :string
229
- def master_key
230
- default_from = @config.servers.key?(:production) ? "production" : "staging"
231
- from = options.fetch(:from, default_from)
232
- to = options.fetch(:to, "local")
233
- if to == "local"
234
- copy :from, from, "config/master.key"
235
- end
236
- if from == "local"
237
- copy :to, to, "config/master.key"
238
- end
239
- end
240
-
241
- desc "download_ci_test_coverage", "download latest test coverage information from CI"
242
- def download_ci_test_coverage
243
- rsync :from, :ci, "coverage"
244
- end
245
-
246
- desc "vim", "open all files that have changed since master"
247
- def vim branch="master"
248
- exec "vim -p `git diff #{branch} --name-only | grep -v sass$ | tac`"
249
- end
250
-
251
- def self.exit_on_failure? = true
1
+ module Bard
252
2
  end
253
3
 
@@ -1,6 +1,6 @@
1
1
  require "bard/ci/github_actions"
2
2
 
3
- describe Bard::CLI::CI::GithubActions do
3
+ describe Bard::CI::GithubActions do
4
4
  subject { described_class.new("metrc", "master", "0966308e204b256fdcc11457eb53306d84884c60") }
5
5
 
6
6
  xit "works" do
@@ -8,7 +8,7 @@ describe Bard::CLI::CI::GithubActions do
8
8
  end
9
9
  end
10
10
 
11
- describe Bard::CLI::CI::GithubActions::API do
11
+ describe Bard::CI::GithubActions::API do
12
12
  subject { described_class.new("metrc") }
13
13
 
14
14
  describe "#last_successful_run" do
@@ -29,7 +29,7 @@ describe Bard::CLI::CI::GithubActions::API do
29
29
  end
30
30
  end
31
31
 
32
- describe Bard::CLI::CI::GithubActions::Client do
32
+ describe Bard::Github do
33
33
  subject { described_class.new("metrc") }
34
34
  end
35
35
 
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.68.0
4
+ version: 0.69.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: 2024-08-15 00:00:00.000000000 Z
11
+ date: 2024-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.0.3
55
- - !ruby/object:Gem::Dependency
56
- name: bard-rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 0.19.0
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 0.19.0
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: byebug
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +94,7 @@ dependencies:
108
94
  - - ">="
109
95
  - !ruby/object:Gem::Version
110
96
  version: '0'
111
- description: CLI to automate common development tasks.
97
+ description:
112
98
  email:
113
99
  - micah@botandrose.com
114
100
  executables:
@@ -147,17 +133,17 @@ files:
147
133
  - install_files/specified_bundler.rb
148
134
  - install_files/specified_ruby.rb
149
135
  - lib/bard.rb
150
- - lib/bard/base.rb
151
136
  - lib/bard/ci.rb
152
137
  - lib/bard/ci/github_actions.rb
153
138
  - lib/bard/ci/jenkins.rb
154
139
  - lib/bard/ci/local.rb
140
+ - lib/bard/cli.rb
141
+ - lib/bard/command.rb
155
142
  - lib/bard/config.rb
156
- - lib/bard/data.rb
143
+ - lib/bard/copy.rb
157
144
  - lib/bard/git.rb
158
145
  - lib/bard/github.rb
159
146
  - lib/bard/ping.rb
160
- - lib/bard/remote_command.rb
161
147
  - lib/bard/version.rb
162
148
  - spec/bard/ci/github_actions_spec.rb
163
149
  - spec/bard_spec.rb
data/lib/bard/base.rb DELETED
@@ -1,119 +0,0 @@
1
- require "thor"
2
- require "term/ansicolor"
3
- require "open3"
4
- require "uri"
5
- require "bard/remote_command"
6
-
7
- class Bard::CLI < Thor
8
- include Term::ANSIColor
9
-
10
- private
11
-
12
- def run_crucial command, verbose: false
13
- failed = false
14
-
15
- if verbose
16
- failed = !(system command)
17
- else
18
- stdout, stderr, status = Open3.capture3(command)
19
- failed = status.to_i.nonzero?
20
- if failed
21
- $stdout.puts stdout
22
- $stderr.puts stderr
23
- end
24
- end
25
-
26
- if failed
27
- puts red("!!! ") + "Running command failed: #{yellow(command)}"
28
- exit 1
29
- end
30
- end
31
-
32
- def project_name
33
- @project_name ||= File.expand_path(".").split("/").last
34
- end
35
-
36
- def ssh_command server_name, command, home: false
37
- server = @config.servers.fetch(server_name.to_sym)
38
- Bard::RemoteCommand.new(server, command, home).local_command
39
- end
40
-
41
- def copy direction, server_name, path, verbose: false
42
- server = @config.servers.fetch(server_name.to_sym)
43
-
44
- uri = URI.parse("ssh://#{server.gateway}")
45
- port = uri.port ? "-p#{uri.port}" : ""
46
- gateway = server.gateway ? "-oProxyCommand='ssh #{port} #{uri.user}@#{uri.host} -W %h:%p'" : ""
47
-
48
- ssh_key = server.ssh_key ? "-i #{server.ssh_key}" : ""
49
-
50
- uri = URI.parse("ssh://#{server.ssh}")
51
- port = uri.port ? "-P#{uri.port}" : ""
52
- from_and_to = [path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
53
-
54
- from_and_to.reverse! if direction == :from
55
- command = "scp #{gateway} #{ssh_key} #{port} #{from_and_to.join(" ")}"
56
-
57
- run_crucial command, verbose: verbose
58
- end
59
-
60
- def move from_name, to_name, path, verbose: false
61
- from = @config.servers.fecth(from_name.to_sym)
62
- to = @config.servers.fetch(to_name.to_sym)
63
- raise NotImplementedError if from.gateway || to.gateway || from.ssh_key || to.ssh_key
64
-
65
- from_uri = URI.parse("ssh://#{from.ssh}")
66
- from_str = "scp://#{from_uri.user}@#{from_uri.host}:#{from_uri.port || 22}/#{from.path}/#{path}"
67
-
68
- to_uri = URI.parse("ssh://#{to.ssh}")
69
- to_str = "scp://#{to_uri.user}@#{to_uri.host}:#{to_uri.port || 22}/#{to.path}/#{path}"
70
-
71
- command = "scp -o ForwardAgent=yes #{from_str} #{to_str}"
72
-
73
- run_crucial command, verbose: verbose
74
- end
75
-
76
- def rsync direction, server_name, path, verbose: false
77
- server = @config.servers.fetch(server_name.to_sym)
78
-
79
- uri = URI.parse("ssh://#{server.gateway}")
80
- port = uri.port ? "-p#{uri.port}" : ""
81
- gateway = server.gateway ? "-oProxyCommand=\"ssh #{port} #{uri.user}@#{uri.host} -W %h:%p\"" : ""
82
-
83
- ssh_key = server.ssh_key ? "-i #{server.ssh_key}" : ""
84
- uri = URI.parse("ssh://#{server.ssh}")
85
- port = uri.port ? "-p#{uri.port}" : ""
86
- ssh = "-e'ssh #{ssh_key} #{port} #{gateway}'"
87
-
88
- dest_path = path.dup
89
- dest_path = "./#{dest_path}"
90
- from_and_to = [dest_path, "#{uri.user}@#{uri.host}:#{server.path}/#{path}"]
91
- from_and_to.reverse! if direction == :from
92
- from_and_to[-1].sub! %r(/[^/]+$), '/'
93
-
94
- command = "rsync #{ssh} --delete --info=progress2 -az #{from_and_to.join(" ")}"
95
-
96
- run_crucial command, verbose: verbose
97
- end
98
-
99
- def rsync_remote from_name, to_name, path, verbose: false
100
- from = @config.servers.fetch(from_name.to_sym)
101
- to = @config.servers.fetch(to_name.to_sym)
102
- raise NotImplementedError if from.gateway || to.gateway || from.ssh_key || to.ssh_key
103
-
104
- dest_path = path.dup
105
- dest_path = "./#{dest_path}"
106
-
107
- from_uri = URI.parse("ssh://#{from.ssh}")
108
- from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
109
-
110
- to_uri = URI.parse("ssh://#{to.ssh}")
111
- to_str = "#{to_uri.user}@#{to_uri.host}:#{to.path}/#{path}"
112
- to_str.sub! %r(/[^/]+$), '/'
113
-
114
- command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no\" --delete --info=progress2 -az #{from.path}/#{path} #{to_str}')
115
-
116
- run_crucial command, verbose: verbose
117
- end
118
- end
119
-
data/lib/bard/data.rb DELETED
@@ -1,96 +0,0 @@
1
- class Bard::CLI < Thor
2
- class Data < Struct.new(:bard, :from, :to)
3
- def call
4
- if to == "production"
5
- server = bard.instance_variable_get(:@config).servers[to.to_sym]
6
- url = server.ping.first
7
- puts bard.yellow("WARNING: You are about to push data to production, overwriting everything that is there!")
8
- answer = bard.ask("If you really want to do this, please type in the full HTTPS url of the production server:")
9
- if answer != url
10
- puts bard.red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
11
- exit 1
12
- end
13
- end
14
-
15
- if to == "local"
16
- data_pull_db from.to_sym
17
- data_pull_assets from.to_sym
18
- elsif from == "local"
19
- data_push_db to.to_sym
20
- data_push_assets to.to_sym
21
- else
22
- data_move_db from.to_sym, to.to_sym
23
- data_move_assets from.to_sym, to.to_sym
24
- end
25
- end
26
-
27
- private
28
-
29
- def data_pull_db server
30
- bard.instance_eval do
31
- puts "Dumping remote database to file..."
32
- run_crucial ssh_command(server, "bin/rake db:dump")
33
-
34
- puts "Downloading file..."
35
- copy :from, server, "db/data.sql.gz", verbose: true
36
-
37
- puts "Loading file into local database..."
38
- run_crucial "bin/rake db:load"
39
- end
40
- end
41
-
42
- def data_push_db server
43
- bard.instance_eval do
44
- puts "Dumping local database to file..."
45
- run_crucial "bin/rake db:dump"
46
-
47
- puts "Uploading file..."
48
- copy :to, server, "db/data.sql.gz", verbose: true
49
-
50
- puts "Loading file into remote database..."
51
- run_crucial ssh_command(server, "bin/rake db:load")
52
- end
53
- end
54
-
55
- def data_move_db from, to
56
- bard.instance_eval do
57
- puts "Dumping local database to file..."
58
- run_crucial ssh_command(from, "bin/rake db:load")
59
-
60
- puts "Uploading file..."
61
- move from, to, "db/data.sql.gz", verbose: true
62
-
63
- puts "Loading file into remote database..."
64
- run_crucial ssh_command(to, "bin/rake db:load")
65
- end
66
- end
67
-
68
- def data_pull_assets server
69
- bard.instance_eval do
70
- @config.data.each do |path|
71
- puts "Downloading files..."
72
- rsync :from, server, path, verbose: true
73
- end
74
- end
75
- end
76
-
77
- def data_push_assets server
78
- bard.instance_eval do
79
- @config.data.each do |path|
80
- puts "Uploading files..."
81
- rsync :to, server, path, verbose: true
82
- end
83
- end
84
- end
85
-
86
- def data_move_assets from, to
87
- bard.instance_eval do
88
- @config.data.each do |path|
89
- puts "Copying files..."
90
- rsync_remote from, to, path, verbose: true
91
- end
92
- end
93
- end
94
- end
95
- end
96
-
@@ -1,45 +0,0 @@
1
- module Bard
2
- class RemoteCommand < Struct.new(:server, :command, :home)
3
- def self.run! *args
4
- new(*args).run!
5
- end
6
-
7
- def local_command
8
- uri = URI.parse("ssh://#{server.ssh}")
9
- ssh_key = server.ssh_key ? "-i #{server.ssh_key} " : ""
10
- cmd = command
11
- if server.env
12
- cmd = "#{server.env} #{command}"
13
- end
14
- unless home
15
- cmd = "cd #{server.path} && #{cmd}"
16
- end
17
- cmd = "ssh -tt #{ssh_key}#{"-p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} '#{cmd}'"
18
- if server.gateway
19
- uri = URI.parse("ssh://#{server.gateway}")
20
- cmd = "ssh -tt #{" -p#{uri.port} " if uri.port}#{uri.user}@#{uri.host} \"#{cmd}\""
21
- end
22
- cmd
23
- end
24
-
25
- def run! verbose: false
26
- failed = false
27
-
28
- if verbose
29
- failed = !(system local_command)
30
- else
31
- stdout, stderr, status = Open3.capture3(local_command)
32
- failed = status.to_i.nonzero?
33
- if failed
34
- $stdout.puts stdout
35
- $stderr.puts stderr
36
- end
37
- end
38
-
39
- if failed
40
- raise "Running command failed: #{local_command}"
41
- end
42
- end
43
- end
44
- end
45
-