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



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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
# File 'lib/bard/plugins/deploy.rb', line 139

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



199
200
201
202
203
# File 'lib/bard/plugins/deploy.rb', line 199

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
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
# File 'lib/bard/plugins/setup.rb', line 6

def setup
  path = "/etc/nginx/sites-available/#{project_name}"
  system "sudo tee #{path} >/dev/null <<-'EOF'
upstream puma {
  server 127.0.0.1:3000 fail_timeout=5;
}

server {
  listen 80;
  server_name #{nginx_server_name};
  root #{Dir.pwd}/public;

  try_files $uri @app;

  location @app {
      proxy_pass http://puma;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
  }

  location ~* \\-[0-9a-f]\\{64\\}\\.(ico|css|js|gif|jpe?g|png|webp)$ {
      access_log off;
      expires max;
      add_header Cache-Control public;
  }

  gzip_static on;
}
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
# 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
  strategy.deploy(branch: branch, force: true)

  puts green("Stage Succeeded")

  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