Class: Bard::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/bard/cli.rb,
lib/bard/plugins/run.rb,
lib/bard/plugins/ssh.rb,
lib/bard/plugins/vim.rb,
lib/bard/plugins/data.rb,
lib/bard/plugins/hurt.rb,
lib/bard/plugins/open.rb,
lib/bard/plugins/ping.rb,
lib/bard/plugins/setup.rb,
lib/bard/plugins/deploy.rb,
lib/bard/plugins/install.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


18
# File 'lib/bard/cli.rb', line 18

def self.exit_on_failure? = true

Instance Method Details

#ci(branch = Bard::Git.current_branch) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/bard/plugins/deploy.rb', line 159

def ci(branch = Bard::Git.current_branch)
  runner_name = if options["local-ci"]
    :local
  elsif options["ci"]
    options["ci"].to_sym
  else
    config.ci
  end
  ci = Bard::CI.new(project_name, branch, runner_name: runner_name)
  unless ci.exists?
    puts red("No CI found for #{project_name}!")
    puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
    exit 1
  end

  return puts ci.status if options["status"]

  if options["resume"]
    puts "Continuous integration: resuming build..."
    success = ci.resume do |elapsed_time, last_time|
      if last_time
        percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
        output = "  Estimated completion: #{percentage}%"
      else
        output = "  No estimated completion time. Elapsed time: #{elapsed_time} sec"
      end
      print "\x08" * output.length
      print output
      $stdout.flush
    end
  else
    puts "Continuous integration: starting build on #{branch}..."

    success = ci.run do |elapsed_time, last_time|
      if last_time
        percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
        output = "  Estimated completion: #{percentage}%"
      else
        output = "  No estimated completion time. Elapsed time: #{elapsed_time} sec"
      end
      print "\x08" * output.length
      print output
      $stdout.flush
    end
  end

  if success
    puts
    puts "Continuous integration: success!"
  else
    puts
    puts ci.console
    puts red("Automated tests failed!")
    exit 1
  end
end

#dataObject



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/bard/plugins/data.rb', line 10

def data
  from = config[options[:from]]
  to = config[options[:to]]

  from.require_capability!(:ssh) unless from.key == :local
  to.require_capability!(:ssh) unless to.key == :local

  if to.key == :production
    url = to.url
    puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
    answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
    if answer != url
      puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
      exit 1
    end
  end

  puts "Dumping #{from.key} database to file..."
  from.run! "bin/rake db:dump"

  puts "Transfering file from #{from.key} to #{to.key}..."
  Bard::Copy.file "db/data.sql.gz", from: from, to: to, verbose: true

  puts "Loading file into #{to.key} database..."
  to.run! "bin/rake db:load"

  config.data.each do |path|
    puts "Synchronizing files in #{path}..."
    Bard::Copy.dir path, from: from, to: to, verbose: true
  end
rescue Bard::Command::Error => e
  puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
  exit 1
end

#deploy(branch = nil) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/bard/plugins/deploy.rb', line 19

def deploy(branch = nil)
  branch ||= Bard::Git.current_branch

  if branch == "master"
    if !Bard::Git.up_to_date_with_remote?(branch)
      run! "git push origin #{branch}:#{branch}"
    end
    invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false

  else
    run! "git fetch origin"
    if Bard::Git.current_branch != "master" && !Bard::Git.in_linked_worktree?
      run! "git fetch origin master:master"
    end

    unless Bard::Git.fast_forward_merge?("origin/master", branch)
      puts "The master branch has advanced. Attempting rebase..."
      if branch == Bard::Git.current_branch
        run! "git rebase origin/master"
      else
        tmpdir = Dir.mktmpdir("bard-rebase")
        begin
          run! "git worktree add --detach #{tmpdir} #{branch}"
          success = Dir.chdir(tmpdir) { system("git rebase origin/master") }
          rebased_sha = Dir.chdir(tmpdir) { `git rev-parse HEAD`.strip } if success
          run! "git worktree remove #{tmpdir} --force"
          unless success
            puts red("!!! ") + "Rebase failed due to conflicts."
            puts "Please rebase #{branch} manually:"
            puts "  git checkout #{branch}"
            puts "  git rebase origin/master"
            exit 1
          end
          run! "git branch -f #{branch} #{rebased_sha}"
        ensure
          FileUtils.rm_rf(tmpdir) if Dir.exist?(tmpdir)
        end
      end
    end

    run! "git push -f origin #{branch}:#{branch}"

    invoke :ci, [branch], options.slice("local-ci", "ci") unless options["skip-ci"] || config.ci == false

    run! "git push origin #{branch}:master"
    if Bard::Git.current_branch == "master"
      run! "git pull --ff-only origin master"
    elsif Bard::Git.in_linked_worktree?
      run! "git fetch origin master"
    else
      run! "git fetch origin master:master"
    end
  end

  if `git remote` =~ /\bgithub\b/
    run! "git push github"
  end

  to = options[:target].to_sym

  target = config[to]
  strategy = target.deploy_strategy_instance
  if options[:clone]
    strategy.deploy(clone: project_name)
  else
    strategy.deploy
  end

  puts green("Deploy Succeeded")

  if branch != "master"
    puts "Deleting branch: #{branch}"
    run! "git push --delete origin #{branch}"

    if branch == Bard::Git.current_branch && Bard::Git.in_linked_worktree?
      worktree_path = Dir.pwd
      main_checkout = File.dirname(File.expand_path(`git rev-parse --git-common-dir`.chomp))
      Dir.chdir(main_checkout)
      run! "git worktree remove #{worktree_path}"
      run! "git branch -D #{branch}"
      puts "Worktree removed. Run: cd #{main_checkout}"
    else
      if branch == Bard::Git.current_branch
        run! "git checkout master"
      end
      run! "git branch -D #{branch}"
    end
  end

  ping to
rescue Bard::Command::Error => e
  puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
  exit 1
end

#hurt(*args) ⇒ Object



3
4
5
6
7
8
9
10
11
12
# File 'lib/bard/plugins/hurt.rb', line 3

def hurt(*args)
  (1..).each do |count|
    puts "Running attempt #{count}"
    system *args
    unless $?.success?
      puts "Ran #{count-1} times before failing"
      break
    end
  end
end

#installObject



3
4
5
6
7
8
# File 'lib/bard/plugins/install.rb', line 3

def install
  install_files_path = File.expand_path("install", __dir__)

  system "cp -R #{install_files_path}/* bin/"
  system "cp -R #{install_files_path}/.github ./"
end

#master_keyObject



219
220
221
222
223
# File 'lib/bard/plugins/deploy.rb', line 219

def master_key
  from = config[options[:from]]
  to = config[options[:to]]
  Bard::Copy.file "config/master.key", from: from, to: to
end

#open(target = :production) ⇒ Object



5
6
7
# File 'lib/bard/plugins/open.rb', line 5

def open(target = :production)
  exec "xdg-open #{open_url target}"
end

#ping(target = :production) ⇒ Object



5
6
7
8
9
# File 'lib/bard/plugins/ping.rb', line 5

def ping(target = :production)
  down_urls = Bard::Ping.call(config[target])
  down_urls.each { |url| puts "#{url} is down!" }
  exit 1 if down_urls.any?
end

#run(*args) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/bard/plugins/run.rb', line 12

def run(*args)
  target = config[options[:target].to_sym]
  target.run!(*args.join(" "), verbose: true, home: options[:home])
rescue Bard::Command::Error => e
  puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
  exit 1
end

#setupObject



6
7
8
9
10
11
12
13
14
# File 'lib/bard/plugins/setup.rb', line 6

def setup
  path = "/etc/nginx/sites-available/#{project_name}"
  system "sudo tee #{path} >/dev/null <<-'EOF'\n#{nginx_config}EOF"

  dest_path = path.sub("sites-available", "sites-enabled")
  system "sudo ln -sf #{path} #{dest_path}" if !File.exist?(dest_path)

  system "sudo service nginx restart"
end

#ssh(to = :production) ⇒ Object



7
8
9
# File 'lib/bard/plugins/ssh.rb', line 7

def ssh(to = :production)
  config[to].exec! "exec $SHELL -l", home: options[:home]
end

#stage(branch = Bard::Git.current_branch) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/bard/plugins/deploy.rb', line 115

def stage(branch = Bard::Git.current_branch)
  if config[:production] == config[:staging]
    raise Thor::Error.new("`bard stage` is disabled until a production target is defined. Until then, please use `bard deploy` to deploy to the staging target.")
  end

  run! "git push -u origin #{branch}", verbose: true

  target = config[:staging]
  strategy = target.deploy_strategy_instance

  if staging_provisioned?(target)
    strategy.deploy(branch: branch, force: true)
    puts green("Stage Succeeded")

  else # clone from scratch
    puts yellow("Staging site for #{project_name} not found — provisioning it from scratch…")
    strategy.deploy(clone: project_name, branch: branch)
    puts green("Stage Succeeded")

    puts "#{project_name} was rebuilt from scratch; its database and files are empty."
    if $stdin.tty? && !no?("Restore #{project_name}'s data from production now? (bard data --from production --to staging) [Y/n]")
      invoke :data, [], from: "production", to: "staging"
    else
      puts "Run #{yellow("bard data --from production --to staging")} to restore its data."
    end
  end

  ping :staging
rescue Bard::Command::Error => e
  puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
  exit 1
end

#versionObject



14
15
16
# File 'lib/bard/cli.rb', line 14

def version
  puts Bard::VERSION
end

#vim(branch = "master") ⇒ Object



3
4
5
# File 'lib/bard/plugins/vim.rb', line 3

def vim(branch = "master")
  exec "vim -p `(git diff #{branch} --name-only; git ls-files --others --exclude-standard) | grep -v '^app/assets/images/' | grep -v '^app/assets/stylesheets/' | while read f; do [ -f \"$f\" ] && ! file -b \"$f\" | grep -q \"binary\" && echo \"$f\"; done | tac`"
end