hu 1.3.6 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +56 -0
- data/Gemfile +1 -0
- data/Rakefile +2 -1
- data/bin/hu +1 -1
- data/hu.gemspec +28 -27
- data/lib/hu/cli.rb +5 -5
- data/lib/hu/collab.rb +58 -58
- data/lib/hu/common.rb +10 -5
- data/lib/hu/deploy.rb +161 -167
- data/lib/hu/version.rb +2 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8a02a4340c2f4801397be2b132d2288cbf6753a
|
|
4
|
+
data.tar.gz: 7b2dc932c8dc82a4a67ce5b976b33d7e45de401a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65aa3c9352155a1c6a975a97b6cda88b26b6b923f44dddfb87a42e62919e594cb26422fdda354a3932ead1d63ae52d441644af8fe9df22648de53813d8df97d5
|
|
7
|
+
data.tar.gz: d3517a63fa07bce2035acaffbd3cb95415a59041b3241b1a05c3004ae5883956dcde6e1ef46c63bc242cb6df1f5532405b3563aace78467a20d70bb55a6c6ab9
|
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Offense count: 13
|
|
2
|
+
Metrics/AbcSize:
|
|
3
|
+
Max: 276
|
|
4
|
+
|
|
5
|
+
# Offense count: 2
|
|
6
|
+
# Configuration parameters: CountComments.
|
|
7
|
+
Metrics/ClassLength:
|
|
8
|
+
Max: 651
|
|
9
|
+
|
|
10
|
+
# Offense count: 6
|
|
11
|
+
Metrics/CyclomaticComplexity:
|
|
12
|
+
Max: 45
|
|
13
|
+
|
|
14
|
+
# Offense count: 83
|
|
15
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
|
16
|
+
# URISchemes: http, https
|
|
17
|
+
Metrics/LineLength:
|
|
18
|
+
Max: 176
|
|
19
|
+
|
|
20
|
+
# Offense count: 14
|
|
21
|
+
# Configuration parameters: CountComments.
|
|
22
|
+
Metrics/MethodLength:
|
|
23
|
+
Max: 230
|
|
24
|
+
|
|
25
|
+
# Offense count: 4
|
|
26
|
+
Metrics/PerceivedComplexity:
|
|
27
|
+
Max: 43
|
|
28
|
+
|
|
29
|
+
# Offense count: 4
|
|
30
|
+
Style/ClassVars:
|
|
31
|
+
Exclude:
|
|
32
|
+
- 'lib/hu/deploy.rb'
|
|
33
|
+
|
|
34
|
+
# Offense count: 5
|
|
35
|
+
Style/Documentation:
|
|
36
|
+
Exclude:
|
|
37
|
+
- 'spec/**/*'
|
|
38
|
+
- 'test/**/*'
|
|
39
|
+
- 'lib/hu/cli.rb'
|
|
40
|
+
- 'lib/hu/collab.rb'
|
|
41
|
+
- 'lib/hu/common.rb'
|
|
42
|
+
- 'lib/hu/deploy.rb'
|
|
43
|
+
|
|
44
|
+
# Offense count: 3
|
|
45
|
+
# Configuration parameters: AllowedVariables.
|
|
46
|
+
Style/GlobalVars:
|
|
47
|
+
Exclude:
|
|
48
|
+
- 'lib/hu/cli.rb'
|
|
49
|
+
- 'lib/hu/collab.rb'
|
|
50
|
+
|
|
51
|
+
# Offense count: 1
|
|
52
|
+
# Cop supports --auto-correct.
|
|
53
|
+
# Configuration parameters: AllowAsExpressionSeparator.
|
|
54
|
+
Style/Semicolon:
|
|
55
|
+
Exclude:
|
|
56
|
+
- 'lib/hu/deploy.rb'
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'bundler/gem_tasks'
|
data/bin/hu
CHANGED
data/hu.gemspec
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'hu/version'
|
|
5
6
|
|
|
6
7
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
8
|
+
spec.name = 'hu'
|
|
8
9
|
spec.version = Hu::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.description =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
10
|
+
spec.authors = ['moe']
|
|
11
|
+
spec.email = ['moe@busyloop.net']
|
|
12
|
+
spec.summary = 'Heroku Utility.'
|
|
13
|
+
spec.description = 'Heroku Utility.'
|
|
14
|
+
spec.homepage = 'https://github.com/busyloop/hu'
|
|
15
|
+
spec.license = 'MIT'
|
|
15
16
|
|
|
16
|
-
spec.files = `git ls-files`.split(
|
|
17
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
-
spec.require_paths = [
|
|
20
|
+
spec.require_paths = ['lib']
|
|
20
21
|
spec.required_ruby_version = '>= 2.3.0'
|
|
21
22
|
|
|
22
|
-
spec.add_development_dependency
|
|
23
|
-
spec.add_development_dependency
|
|
24
|
-
spec.add_development_dependency
|
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
|
24
|
+
spec.add_development_dependency 'rake'
|
|
25
|
+
spec.add_development_dependency 'bump'
|
|
25
26
|
|
|
26
|
-
spec.add_dependency
|
|
27
|
-
spec.add_dependency
|
|
28
|
-
spec.add_dependency
|
|
29
|
-
spec.add_dependency
|
|
30
|
-
spec.add_dependency
|
|
31
|
-
spec.add_dependency
|
|
32
|
-
spec.add_dependency
|
|
33
|
-
spec.add_dependency
|
|
34
|
-
spec.add_dependency
|
|
35
|
-
spec.add_dependency
|
|
36
|
-
spec.add_dependency
|
|
37
|
-
spec.add_dependency
|
|
38
|
-
spec.add_dependency
|
|
39
|
-
spec.add_dependency
|
|
40
|
-
spec.add_dependency
|
|
27
|
+
spec.add_dependency 'optix', '~> 1.2.4'
|
|
28
|
+
spec.add_dependency 'blackbox', '~> 3.1.4'
|
|
29
|
+
spec.add_dependency 'platform-api', '~> 0.7.0'
|
|
30
|
+
spec.add_dependency 'powerbar', '>= 1.0.16'
|
|
31
|
+
spec.add_dependency 'hashdiff', '~> 0.3.0'
|
|
32
|
+
spec.add_dependency 'version_sorter', '~> 2.0.0'
|
|
33
|
+
spec.add_dependency 'versionomy', '~> 0.5.0'
|
|
34
|
+
spec.add_dependency 'tty-prompt'
|
|
35
|
+
spec.add_dependency 'tty-spinner'
|
|
36
|
+
spec.add_dependency 'tty-table'
|
|
37
|
+
spec.add_dependency 'rainbow'
|
|
38
|
+
spec.add_dependency 'netrc'
|
|
39
|
+
spec.add_dependency 'chronic_duration'
|
|
40
|
+
spec.add_dependency 'thread_safe'
|
|
41
|
+
spec.add_dependency 'rugged'
|
|
41
42
|
end
|
data/lib/hu/cli.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'hu/version'
|
|
2
3
|
require 'optix'
|
|
3
4
|
require 'powerbar'
|
|
@@ -11,18 +12,17 @@ require 'hu/deploy'
|
|
|
11
12
|
|
|
12
13
|
module Hu
|
|
13
14
|
class Cli < Optix::Cli
|
|
14
|
-
Optix
|
|
15
|
+
Optix.command do
|
|
15
16
|
text "Hu v#{Hu::VERSION} - Heroku Utility"
|
|
16
|
-
opt :quiet,
|
|
17
|
-
opt :version,
|
|
17
|
+
opt :quiet, 'Quiet mode (no progress output)', default: false
|
|
18
|
+
opt :version, 'Print version and exit', short: :none
|
|
18
19
|
trigger :version do
|
|
19
20
|
puts "Hu v#{Hu::VERSION}"
|
|
20
21
|
end
|
|
21
|
-
filter do |
|
|
22
|
+
filter do |_cmd, opts, _argv|
|
|
22
23
|
$quiet = opts[:quiet]
|
|
23
24
|
$quiet = true unless STDOUT.isatty
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
|
-
|
data/lib/hu/collab.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'powerbar'
|
|
2
3
|
require 'yaml'
|
|
3
4
|
require 'hashdiff'
|
|
@@ -12,25 +13,25 @@ module Hu
|
|
|
12
13
|
class InvalidPlan < StandardError
|
|
13
14
|
attr_accessor :invalid_plan
|
|
14
15
|
|
|
15
|
-
def initialize(message=nil, invalid_plan=nil)
|
|
16
|
+
def initialize(message = nil, invalid_plan = nil)
|
|
16
17
|
super(message)
|
|
17
18
|
self.invalid_plan = invalid_plan
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
text
|
|
22
|
-
text
|
|
23
|
-
text
|
|
24
|
-
text
|
|
25
|
-
text
|
|
26
|
-
text
|
|
27
|
-
text
|
|
28
|
-
text
|
|
29
|
-
text
|
|
30
|
-
text
|
|
22
|
+
text 'Manage application/collaborator mapping.'
|
|
23
|
+
text ''
|
|
24
|
+
text 'Start by exporting the current mapping,'
|
|
25
|
+
text 'edit to taste, then diff and import.'
|
|
26
|
+
text ''
|
|
27
|
+
text 'The environment variable HU_IGNORE_APPS'
|
|
28
|
+
text 'may contain space delimited glob(7) patterns'
|
|
29
|
+
text 'of apps to be ignored.'
|
|
30
|
+
text ''
|
|
31
|
+
text 'WARNING: If you remove yourself from an application'
|
|
31
32
|
text " then hu won't be able to see it anymore."
|
|
32
33
|
if Hu::API_TOKEN.nil?
|
|
33
|
-
text
|
|
34
|
+
text ''
|
|
34
35
|
text "\e[1mWARNING: Environment variable 'HEROKU_API_KEY' must be set.\e[0m"
|
|
35
36
|
end
|
|
36
37
|
filter do
|
|
@@ -44,14 +45,14 @@ module Hu
|
|
|
44
45
|
OP_COLORS = {
|
|
45
46
|
'+' => "\e[0;1;32m",
|
|
46
47
|
'-' => "\e[0;1;34m",
|
|
47
|
-
'~' => "\e[0;32m"
|
|
48
|
-
}
|
|
49
|
-
desc
|
|
50
|
-
text
|
|
51
|
-
parent
|
|
52
|
-
def diff(
|
|
48
|
+
'~' => "\e[0;32m"
|
|
49
|
+
}.freeze
|
|
50
|
+
desc 'Read mapping from stdin and diff to heroku state'
|
|
51
|
+
text 'Read mapping from stdin and diff to heroku state'
|
|
52
|
+
parent 'collab'
|
|
53
|
+
def diff(_cmd, opts, _argv)
|
|
53
54
|
parsed_state = parse_as_json_or_yaml(STDIN.read)
|
|
54
|
-
show_plan(
|
|
55
|
+
show_plan(plan(HashDiff.diff(heroku_state['apps'], parsed_state['apps']), opts))
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def show_plan(plan)
|
|
@@ -61,7 +62,7 @@ module Hu
|
|
|
61
62
|
icon = ' '
|
|
62
63
|
if s[:method].nil?
|
|
63
64
|
color = "\e[0m"
|
|
64
|
-
msg =
|
|
65
|
+
msg = '<-- Can not resolve this (NO-OP)'
|
|
65
66
|
icon = "⚠️"
|
|
66
67
|
elsif s[:invalid]
|
|
67
68
|
color = "\e[0m"
|
|
@@ -74,27 +75,27 @@ module Hu
|
|
|
74
75
|
end
|
|
75
76
|
end
|
|
76
77
|
|
|
77
|
-
desc
|
|
78
|
-
text
|
|
79
|
-
opt :format,
|
|
80
|
-
parent
|
|
81
|
-
def export(
|
|
78
|
+
desc 'Print current mapping to stdout'
|
|
79
|
+
text 'Print current mapping to stdout'
|
|
80
|
+
opt :format, 'yaml|json', default: 'yaml'
|
|
81
|
+
parent 'collab', 'Manage application collaborators'
|
|
82
|
+
def export(_cmd, opts, _argv)
|
|
82
83
|
puts heroku_state.send("to_#{opts[:format]}".to_sym)
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
desc
|
|
86
|
-
text
|
|
87
|
-
opt :allow_create,
|
|
88
|
-
opt :silent_create,
|
|
89
|
-
parent
|
|
90
|
-
def import(
|
|
86
|
+
desc 'Read mapping from stdin and push to heroku'
|
|
87
|
+
text 'Read mapping from stdin and push to heroku'
|
|
88
|
+
opt :allow_create, 'Create new collaborators on heroku', short: 'c'
|
|
89
|
+
opt :silent_create, 'Suppress email invitation when creating collaborators'
|
|
90
|
+
parent 'collab'
|
|
91
|
+
def import(_cmd, opts, _argv)
|
|
91
92
|
parsed_state = parse_as_json_or_yaml(STDIN.read)
|
|
92
93
|
validators = {
|
|
93
|
-
:
|
|
94
|
-
unless heroku_state['collaborators'].include?
|
|
94
|
+
op_add_collaborators: proc do |op|
|
|
95
|
+
unless heroku_state['collaborators'].include?(op[:value]) || opts[:allow_create]
|
|
95
96
|
raise InvalidOperation, "Use -c to allow creation of new collaborator '#{op[:value]}'"
|
|
96
97
|
end
|
|
97
|
-
|
|
98
|
+
end
|
|
98
99
|
}
|
|
99
100
|
begin
|
|
100
101
|
plan(HashDiff.diff(heroku_state['apps'], parsed_state['apps']), opts, validators).each do |s|
|
|
@@ -104,14 +105,14 @@ module Hu
|
|
|
104
105
|
eol = "\e[1G"
|
|
105
106
|
if s[:method].nil?
|
|
106
107
|
color = "\e[0;41;33;1m"
|
|
107
|
-
msg =
|
|
108
|
+
msg = 'Skipped.'
|
|
108
109
|
icon = "\e[0;31;1m\u2718\e[0m" # X
|
|
109
110
|
eol = "\n"
|
|
110
111
|
end
|
|
111
112
|
STDERR.printf "%s %s%6s %-30s %-15s %-30s %s\e[0m%s", icon, color, s[:op_name], s[:app_name], s[:role], s[:value], msg, eol
|
|
112
113
|
next if s[:method].nil?
|
|
113
114
|
begin
|
|
114
|
-
|
|
115
|
+
send(s[:method], s)
|
|
115
116
|
STDERR.puts "\e[0;32;1m\u2713\e[0m\n" # check
|
|
116
117
|
rescue => e
|
|
117
118
|
STDERR.puts "\e[0;31;1m\u2718\e[0m\n" # X
|
|
@@ -130,9 +131,9 @@ module Hu
|
|
|
130
131
|
OP_MAP = {
|
|
131
132
|
'+' => 'add',
|
|
132
133
|
'-' => 'remove',
|
|
133
|
-
'~' => 'change'
|
|
134
|
-
}
|
|
135
|
-
def plan(diff, env={}, validators={})
|
|
134
|
+
'~' => 'change'
|
|
135
|
+
}.freeze
|
|
136
|
+
def plan(diff, env = {}, validators = {})
|
|
136
137
|
plan = []
|
|
137
138
|
last_error = nil
|
|
138
139
|
diff.each do |op, target, lval, rval|
|
|
@@ -146,10 +147,10 @@ module Hu
|
|
|
146
147
|
app_name: app_name,
|
|
147
148
|
op: op,
|
|
148
149
|
op_name: op_name,
|
|
149
|
-
method:
|
|
150
|
+
method: respond_to?(method_name) ? method_name : nil,
|
|
150
151
|
value: value,
|
|
151
152
|
role: role,
|
|
152
|
-
env: env
|
|
153
|
+
env: env
|
|
153
154
|
}
|
|
154
155
|
if validators.include? method_name
|
|
155
156
|
begin
|
|
@@ -160,12 +161,12 @@ module Hu
|
|
|
160
161
|
end
|
|
161
162
|
plan << operation
|
|
162
163
|
end
|
|
163
|
-
raise InvalidPlan.new(
|
|
164
|
+
raise InvalidPlan.new('Plan did not pass validation', plan) unless last_error.nil?
|
|
164
165
|
plan
|
|
165
166
|
end
|
|
166
167
|
|
|
167
168
|
def op_add_collaborators(args)
|
|
168
|
-
h.collaborator.create(args[:app_name], :
|
|
169
|
+
h.collaborator.create(args[:app_name], user: args[:value], silent: args[:env][:silent_create])
|
|
169
170
|
end
|
|
170
171
|
|
|
171
172
|
def op_remove_collaborators(args)
|
|
@@ -179,13 +180,13 @@ module Hu
|
|
|
179
180
|
begin
|
|
180
181
|
parsed = YAML.load(input)
|
|
181
182
|
if parsed.is_a? String
|
|
182
|
-
raise ArgumentError,
|
|
183
|
+
raise ArgumentError, 'Input parsed as YAML yields a String'
|
|
183
184
|
end
|
|
184
185
|
rescue => yex
|
|
185
|
-
STDERR.puts
|
|
186
|
-
STDERR.puts
|
|
186
|
+
STDERR.puts 'Error: Could neither parse stdin as YAML nor as JSON.'
|
|
187
|
+
STDERR.puts '-- JSON Error --'
|
|
187
188
|
STDERR.puts jex
|
|
188
|
-
STDERR.puts
|
|
189
|
+
STDERR.puts '-- YAML Error --'
|
|
189
190
|
STDERR.puts yex
|
|
190
191
|
raise ArgumentError
|
|
191
192
|
end
|
|
@@ -197,7 +198,7 @@ module Hu
|
|
|
197
198
|
unless parsed.include? 'apps'
|
|
198
199
|
raise ArgumentError, "Malformed input, key 'apps' not found."
|
|
199
200
|
end
|
|
200
|
-
parsed['apps'].reject!{ |e| ignored_app?(e) }
|
|
201
|
+
parsed['apps'].reject! { |e| ignored_app?(e) }
|
|
201
202
|
parsed['apps'].each do |app_name, v|
|
|
202
203
|
unless heroku_state['apps'].include? app_name
|
|
203
204
|
raise ArgumentError, "Unknown application: #{app_name}"
|
|
@@ -210,37 +211,37 @@ module Hu
|
|
|
210
211
|
end
|
|
211
212
|
|
|
212
213
|
def ignored_app?(app_name)
|
|
213
|
-
ENV.fetch('HU_IGNORE_APPS','').split(' ').each do |p|
|
|
214
|
+
ENV.fetch('HU_IGNORE_APPS', '').split(' ').each do |p|
|
|
214
215
|
return true if File.fnmatch(p, app_name)
|
|
215
216
|
end
|
|
216
217
|
false
|
|
217
218
|
end
|
|
218
219
|
|
|
219
|
-
def heroku_state(force_refresh=false)
|
|
220
|
-
return @heroku_state unless force_refresh
|
|
220
|
+
def heroku_state(force_refresh = false)
|
|
221
|
+
return @heroku_state unless force_refresh || @heroku_state.nil?
|
|
221
222
|
all_collaborators = Set.new
|
|
222
223
|
data = { 'apps' => {} }
|
|
223
|
-
app_names = h.app.list.map{|e| e['name']}.reject{ |e| ignored_app?(e) }
|
|
224
|
+
app_names = h.app.list.map { |e| e['name'] }.reject { |e| ignored_app?(e) }
|
|
224
225
|
threads = []
|
|
225
|
-
app_names.each_with_index do |app_name,
|
|
226
|
+
app_names.each_with_index do |app_name, _i|
|
|
226
227
|
threads << Thread.new do
|
|
227
228
|
d = data['apps'][app_name] = { 'collaborators' => [] }
|
|
228
|
-
h.collaborator.list(app_name).map
|
|
229
|
+
h.collaborator.list(app_name).map do |e|
|
|
229
230
|
case e['role']
|
|
230
231
|
when 'owner'
|
|
231
232
|
d['owner'] = e['user']['email']
|
|
232
233
|
when nil
|
|
233
234
|
d['collaborators'] << e['user']['email']
|
|
234
235
|
else
|
|
235
|
-
raise
|
|
236
|
+
raise "Unknown collaborator role: #{e['role']}"
|
|
236
237
|
end
|
|
237
238
|
all_collaborators << e['user']['email']
|
|
238
|
-
|
|
239
|
+
end
|
|
239
240
|
end
|
|
240
241
|
end
|
|
241
242
|
threads.each_with_index do |t, i|
|
|
242
243
|
t.join
|
|
243
|
-
pb :
|
|
244
|
+
pb msg: app_names[i], total: app_names.length, done: i + 1
|
|
244
245
|
end
|
|
245
246
|
pb :wipe
|
|
246
247
|
data['collaborators'] = all_collaborators.to_a.sort
|
|
@@ -256,7 +257,6 @@ module Hu
|
|
|
256
257
|
@pb ||= PowerBar.new
|
|
257
258
|
show_opts == :wipe ? @pb.wipe : @pb.show(show_opts)
|
|
258
259
|
end
|
|
259
|
-
|
|
260
260
|
end
|
|
261
261
|
end
|
|
262
262
|
end
|
data/lib/hu/common.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'blackbox/gem'
|
|
2
3
|
|
|
3
4
|
module Hu
|
|
@@ -11,9 +12,13 @@ class String
|
|
|
11
12
|
end
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
unless ENV['SKIP_VERSION_CHECK']
|
|
16
|
+
version_info = BB::Gem.version_info(check_interval: 900)
|
|
17
|
+
unless version_info[:installed_is_latest] == true
|
|
18
|
+
puts "\e[33;1mWoops! \e[0mA newer version of #{version_info[:gem_name]} is available."
|
|
19
|
+
puts " Please type '\e[1mgem install #{version_info[:gem_name]}\e[0m' to upgrade (v#{version_info[:gem_installed_version]} -> v#{version_info[:gem_latest_version]})."
|
|
20
|
+
sleep 1
|
|
21
|
+
puts
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
19
24
|
end
|
data/lib/hu/deploy.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'version_sorter'
|
|
2
3
|
require 'versionomy'
|
|
3
4
|
require 'tty-prompt'
|
|
@@ -24,10 +25,10 @@ module Hu
|
|
|
24
25
|
@@shutting_down = false
|
|
25
26
|
@@spinner = nil
|
|
26
27
|
|
|
27
|
-
text
|
|
28
|
-
desc
|
|
28
|
+
text 'Interactive deployment.'
|
|
29
|
+
desc 'Interactive deployment'
|
|
29
30
|
if Hu::API_TOKEN.nil?
|
|
30
|
-
text
|
|
31
|
+
text ''
|
|
31
32
|
text "\e[1mWARNING: Environment variable 'HEROKU_API_KEY' must be set.\e[0m"
|
|
32
33
|
end
|
|
33
34
|
filter do
|
|
@@ -37,22 +38,22 @@ module Hu
|
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
def deploy(
|
|
41
|
+
def deploy(_cmd, _opts, _argv)
|
|
41
42
|
trap('INT') { shutdown; safe_abort; exit 1 }
|
|
42
|
-
at_exit
|
|
43
|
-
if
|
|
43
|
+
at_exit do
|
|
44
|
+
if $ERROR_INFO.class == SystemExit && 130 == $ERROR_INFO.status
|
|
44
45
|
shutdown
|
|
45
46
|
puts
|
|
46
47
|
safe_abort
|
|
47
48
|
end
|
|
48
|
-
|
|
49
|
+
end
|
|
49
50
|
|
|
50
51
|
begin
|
|
51
52
|
@git = Rugged::Repository.discover('.')
|
|
52
53
|
rescue Rugged::RepositoryError => e
|
|
53
54
|
puts
|
|
54
55
|
puts "Git error: #{e}".color(:red)
|
|
55
|
-
puts
|
|
56
|
+
puts 'You need to be inside the working copy of the app that you wish to deploy.'.color(:red)
|
|
56
57
|
puts
|
|
57
58
|
safe_abort
|
|
58
59
|
print TTY::Cursor.prev_line
|
|
@@ -65,7 +66,7 @@ module Hu
|
|
|
65
66
|
puts
|
|
66
67
|
puts "ERROR: Remote of branch 'master' does not point to 'origin'.".color(:red)
|
|
67
68
|
puts
|
|
68
|
-
puts
|
|
69
|
+
puts ' Sorry, we need an origin here. We really do.'
|
|
69
70
|
puts
|
|
70
71
|
exit 1
|
|
71
72
|
end
|
|
@@ -82,16 +83,16 @@ module Hu
|
|
|
82
83
|
unless @git.config['gitflow.prefix.versiontag'].nil? ||
|
|
83
84
|
@git.config['gitflow.prefix.versiontag'].empty?
|
|
84
85
|
puts
|
|
85
|
-
puts
|
|
86
|
+
puts 'ERROR: git-flow version prefix configured.'.color(:red)
|
|
86
87
|
puts
|
|
87
|
-
puts
|
|
88
|
+
puts ' Please use this command to remove the prefix:'
|
|
88
89
|
puts
|
|
89
90
|
puts " git config --add gitflow.prefix.versiontag ''".bright
|
|
90
91
|
puts
|
|
91
92
|
exit 1
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
push_url =
|
|
95
|
+
push_url = heroku_git_remote
|
|
95
96
|
|
|
96
97
|
wc_update = Thread.new { update_working_copy }
|
|
97
98
|
|
|
@@ -100,7 +101,7 @@ module Hu
|
|
|
100
101
|
if app.nil?
|
|
101
102
|
puts
|
|
102
103
|
puts "ERROR: Found no heroku app for git remote #{push_url}".color(:red)
|
|
103
|
-
puts
|
|
104
|
+
puts ' Are you logged into the right heroku account?'.color(:red)
|
|
104
105
|
puts
|
|
105
106
|
puts " Please run 'git remote rm heroku'. Then run 'hu deploy' again to select a new remote."
|
|
106
107
|
puts
|
|
@@ -112,10 +113,10 @@ module Hu
|
|
|
112
113
|
if app['id'] != stag_app_id
|
|
113
114
|
puts
|
|
114
115
|
puts "ERROR: The git remote 'heroku' points to app '#{app['name']}'".color(:red)
|
|
115
|
-
puts " which is not in stage 'staging'".color(:red)+
|
|
116
|
+
puts " which is not in stage 'staging'".color(:red) +
|
|
116
117
|
" of pipeline '#{pipeline_name}'.".color(:red)
|
|
117
118
|
puts
|
|
118
|
-
puts
|
|
119
|
+
puts ' The referenced app MUST be the staging member of the pipeline.'
|
|
119
120
|
|
|
120
121
|
puts " Please run 'git remote rm heroku'. Then run 'hu deploy' again to select a new remote."
|
|
121
122
|
puts
|
|
@@ -139,14 +140,14 @@ module Hu
|
|
|
139
140
|
highest_versionomy = Versionomy.parse('v0.0.0')
|
|
140
141
|
end
|
|
141
142
|
|
|
142
|
-
all_tags = Set.new(@git.references.to_a(
|
|
143
|
+
all_tags = Set.new(@git.references.to_a('refs/tags/*').collect { |o| o.name[10..-1] })
|
|
143
144
|
|
|
144
145
|
tiny_bump = highest_versionomy.dup
|
|
145
146
|
minor_bump = highest_versionomy.dup
|
|
146
147
|
major_bump = highest_versionomy.dup
|
|
147
148
|
|
|
148
149
|
loop do
|
|
149
|
-
tiny_bump
|
|
150
|
+
tiny_bump = tiny_bump.bump(:tiny)
|
|
150
151
|
break unless all_tags.include? tiny_bump.to_s
|
|
151
152
|
end
|
|
152
153
|
loop do
|
|
@@ -172,60 +173,57 @@ module Hu
|
|
|
172
173
|
git_revisions = show_pipeline_status(pipeline_name, stag_app_name, prod_app_name, release_tag, clearscreen)
|
|
173
174
|
clearscreen = true
|
|
174
175
|
|
|
175
|
-
changelog='Initial revision'
|
|
176
|
+
changelog = 'Initial revision'
|
|
176
177
|
release_branch_exists = branch_exists?("release/#{release_tag}")
|
|
177
178
|
|
|
178
179
|
if release_branch_exists
|
|
179
|
-
puts "\nThis release will be "+release_tag.color(:red).bright
|
|
180
|
+
puts "\nThis release will be " + release_tag.color(:red).bright
|
|
180
181
|
unless highest_version == 'v0.0.0'
|
|
181
182
|
env = {
|
|
182
183
|
'PREVIOUS_TAG' => highest_version,
|
|
183
184
|
'RELEASE_TAG' => release_tag
|
|
184
185
|
}
|
|
185
|
-
changelog=create_changelog(env) unless highest_version == 'v0.0.0'
|
|
186
|
+
changelog = create_changelog(env) unless highest_version == 'v0.0.0'
|
|
186
187
|
unless changelog.empty?
|
|
187
|
-
puts "\nChanges since "+highest_version.bright+
|
|
188
|
+
puts "\nChanges since " + highest_version.bright + ':'
|
|
188
189
|
puts changelog
|
|
189
190
|
end
|
|
190
191
|
end
|
|
191
|
-
puts
|
|
192
192
|
else
|
|
193
|
-
puts "\nThis is release "+release_tag.color(:green).bright
|
|
194
|
-
puts
|
|
193
|
+
puts "\nThis is release " + release_tag.color(:green).bright
|
|
195
194
|
end
|
|
195
|
+
puts
|
|
196
196
|
|
|
197
|
-
unless git_revisions[:release] == git_revisions[stag_app_name]
|
|
198
|
-
puts
|
|
199
|
-
puts
|
|
200
|
-
puts
|
|
197
|
+
unless git_revisions[:release] == git_revisions[stag_app_name] || !release_branch_exists
|
|
198
|
+
puts 'Phase 1/3: The local release branch ' + "release/#{release_tag}".bright + ' was created.'
|
|
199
|
+
puts ' Nothing else has happened so far. Push this branch to'
|
|
200
|
+
puts ' ' + stag_app_name.to_s.bright + ' to begin the deploy procedure.'
|
|
201
201
|
puts
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
if release_branch_exists && git_revisions[:release] == git_revisions[stag_app_name]
|
|
205
|
-
puts
|
|
206
|
-
puts
|
|
207
|
-
puts
|
|
208
|
-
puts
|
|
205
|
+
puts 'Phase 2/3: Your local ' + "release/#{release_tag}".bright + ' (formerly ' + 'develop'.bright + ") is now live at #{stag_app_name}."
|
|
206
|
+
puts ' Please test thoroughly: ' + (app['web_url']).to_s.bright
|
|
207
|
+
puts ' If everything looks good, you may proceed and finish the release.'
|
|
208
|
+
puts ' If there are problems: Quit, delete the release branch and start fixing.'
|
|
209
209
|
puts
|
|
210
|
-
elsif git_revisions[prod_app_name] != git_revisions[stag_app_name]
|
|
211
|
-
puts
|
|
212
|
-
puts
|
|
210
|
+
elsif git_revisions[prod_app_name] != git_revisions[stag_app_name] && !release_branch_exists && git_revisions[:release] != git_revisions[stag_app_name]
|
|
211
|
+
puts 'Phase 3/3: HEADS UP. This is the last chance to detect problems.'
|
|
212
|
+
puts ' The final version of ' + "release/#{release_tag}".bright + ' is now staged.'
|
|
213
213
|
puts
|
|
214
|
-
puts
|
|
214
|
+
puts ' Test here: ' + (app['web_url']).to_s.bright
|
|
215
215
|
sleep 1
|
|
216
216
|
puts
|
|
217
|
-
puts
|
|
217
|
+
puts ' This is the exact version that will be promoted to production.'
|
|
218
218
|
puts " From here you are on your own. Good luck #{`whoami`.chomp}!"
|
|
219
219
|
puts
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
choice = prompt.select("Choose your destiny") do |menu|
|
|
222
|
+
choice = prompt.select('Choose your destiny') do |menu|
|
|
225
223
|
menu.enum '.'
|
|
226
|
-
menu.choice
|
|
227
|
-
menu.choice
|
|
228
|
-
unless git_revisions[:release] == git_revisions[stag_app_name]
|
|
224
|
+
menu.choice 'Refresh', :refresh
|
|
225
|
+
menu.choice 'Quit', :abort_ask
|
|
226
|
+
unless git_revisions[:release] == git_revisions[stag_app_name] || !release_branch_exists
|
|
229
227
|
menu.choice "Push release/#{release_tag} to #{stag_app_name}", :push_to_staging
|
|
230
228
|
end
|
|
231
229
|
if release_branch_exists
|
|
@@ -242,7 +240,7 @@ module Hu
|
|
|
242
240
|
end
|
|
243
241
|
|
|
244
242
|
if git_revisions[:release] == git_revisions[stag_app_name]
|
|
245
|
-
menu.choice
|
|
243
|
+
menu.choice 'Finish release (merge, tag and final stage)', :finish_release
|
|
246
244
|
end
|
|
247
245
|
elsif git_revisions[prod_app_name] != git_revisions[stag_app_name]
|
|
248
246
|
menu.choice "DEPLOY (promote #{stag_app_name} to #{prod_app_name})", :DEPLOY
|
|
@@ -252,66 +250,66 @@ module Hu
|
|
|
252
250
|
puts
|
|
253
251
|
|
|
254
252
|
case choice
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
253
|
+
when :DEPLOY
|
|
254
|
+
promote_to_production
|
|
255
|
+
anykey
|
|
256
|
+
when :finish_release
|
|
257
|
+
old_editor = ENV['EDITOR']
|
|
258
|
+
tf = Tempfile.new('hu-tag')
|
|
259
|
+
tf.write "#{release_tag}\n#{changelog}"
|
|
260
|
+
tf.close
|
|
261
|
+
ENV['EDITOR'] = "cp #{tf.path}"
|
|
262
|
+
env = {
|
|
263
|
+
'PREVIOUS_TAG' => highest_version,
|
|
264
|
+
'RELEASE_TAG' => release_tag
|
|
265
|
+
}
|
|
266
|
+
unless 0 == finish_release(release_tag, env)
|
|
267
|
+
abort_merge
|
|
268
|
+
puts '*** ERROR! Could not finish release *** '.color(:red)
|
|
269
|
+
puts
|
|
270
|
+
puts 'This usually means a merge conflict or'
|
|
271
|
+
puts 'something similarly complicated has occured.'
|
|
272
|
+
puts
|
|
273
|
+
puts 'Please bring the universe into a state'
|
|
274
|
+
puts 'where the above command succeeds, then try again.'
|
|
275
|
+
puts
|
|
276
|
+
exit 1
|
|
277
|
+
end
|
|
278
|
+
ENV['EDITOR'] = old_editor
|
|
279
|
+
anykey
|
|
280
|
+
when :push_to_staging
|
|
281
|
+
run_each <<-EOS.strip_heredoc
|
|
284
282
|
:stream
|
|
285
283
|
git push #{push_url} release/#{release_tag}:master -f
|
|
286
284
|
EOS
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
285
|
+
anykey
|
|
286
|
+
when :abort_ask
|
|
287
|
+
puts if delete_branch("release/#{release_tag}")
|
|
288
|
+
exit 0
|
|
289
|
+
when :bump_tiny
|
|
290
|
+
if delete_branch("release/#{release_tag}")
|
|
291
|
+
release_tag, branch_already_exists = prompt_for_release_tag(tiny_bump, tiny_bump)
|
|
292
|
+
end
|
|
293
|
+
when :bump_minor
|
|
294
|
+
if delete_branch("release/#{release_tag}")
|
|
295
|
+
release_tag, branch_already_exists = prompt_for_release_tag(minor_bump, minor_bump)
|
|
296
|
+
end
|
|
297
|
+
when :bump_major
|
|
298
|
+
if delete_branch("release/#{release_tag}")
|
|
299
|
+
release_tag, branch_already_exists = prompt_for_release_tag(major_bump, major_bump)
|
|
300
|
+
end
|
|
303
301
|
end
|
|
304
302
|
end
|
|
305
303
|
end
|
|
306
304
|
|
|
307
|
-
def show_pipeline_status(pipeline_name, stag_app_name, prod_app_name, release_tag, clear=true)
|
|
308
|
-
table = TTY::Table.new header: %w
|
|
305
|
+
def show_pipeline_status(pipeline_name, stag_app_name, prod_app_name, release_tag, clear = true)
|
|
306
|
+
table = TTY::Table.new header: %w(location commit tag app_last_modified app_last_modified_by dynos# state)
|
|
309
307
|
busy 'loading', :classic
|
|
310
308
|
ts = []
|
|
311
309
|
tpl_row = ['?', '', '', '', '', '', '']
|
|
312
310
|
revs = ThreadSafe::Hash.new
|
|
313
311
|
|
|
314
|
-
[[0,stag_app_name],[1,prod_app_name]].each do |idx, app_name|
|
|
312
|
+
[[0, stag_app_name], [1, prod_app_name]].each do |idx, app_name|
|
|
315
313
|
ts << Thread.new do
|
|
316
314
|
table_row = tpl_row.dup
|
|
317
315
|
table_row[0] = app_name
|
|
@@ -325,7 +323,7 @@ module Hu
|
|
|
325
323
|
release_version = dynos.dig(0, 'release', 'version')
|
|
326
324
|
break if release_version.nil?
|
|
327
325
|
|
|
328
|
-
state = Set.new(dynos.collect{|d| d['state']}).sort.join(', ')
|
|
326
|
+
state = Set.new(dynos.collect { |d| d['state'] }).sort.join(', ')
|
|
329
327
|
state_color = (state == 'up') ? 32 : 31
|
|
330
328
|
table_row[6] = "\e[#{state_color};1m#{state}"
|
|
331
329
|
|
|
@@ -340,15 +338,15 @@ module Hu
|
|
|
340
338
|
revs[app_name] = table_row[1] = slug_info['commit'][0..5]
|
|
341
339
|
|
|
342
340
|
table_row[2] = `git tag --points-at #{slug_info['commit']} 2>/dev/null`
|
|
343
|
-
table_row[2] = '' if
|
|
341
|
+
table_row[2] = '' if $CHILD_STATUS != 0
|
|
344
342
|
|
|
345
343
|
# heroku uses wrong timezone offset in the slug api... /facepalm
|
|
346
|
-
#table_row[3] = ChronicDuration.output(Time.now.utc - Time.parse(slug_info['updated_at']), :units => 1)
|
|
344
|
+
# table_row[3] = ChronicDuration.output(Time.now.utc - Time.parse(slug_info['updated_at']), :units => 1)
|
|
347
345
|
|
|
348
346
|
delta = Time.now.utc - Time.parse(release_info['updated_at'])
|
|
349
|
-
table_row[3] = delta < 60 ? 'less than a minute' : ChronicDuration.output(delta, :
|
|
350
|
-
table_row[3] +=
|
|
351
|
-
#table_row[3] += "\n\e[30;1m" + slug_info['updated_at']
|
|
347
|
+
table_row[3] = delta < 60 ? 'less than a minute' : ChronicDuration.output(delta, units: 1)
|
|
348
|
+
table_row[3] += ' ago'
|
|
349
|
+
# table_row[3] += "\n\e[30;1m" + slug_info['updated_at']
|
|
352
350
|
|
|
353
351
|
table_row[4] = release_info['user']['email']
|
|
354
352
|
table_row[5] = dynos.length
|
|
@@ -386,21 +384,21 @@ module Hu
|
|
|
386
384
|
|
|
387
385
|
unbusy
|
|
388
386
|
|
|
389
|
-
rows.each do |
|
|
390
|
-
table <<
|
|
387
|
+
rows.each do |r|
|
|
388
|
+
table << r
|
|
391
389
|
end
|
|
392
390
|
|
|
393
391
|
puts "\e[H\e[2J" if clear
|
|
394
392
|
puts " PIPELINE #{pipeline_name} ".inverse
|
|
395
393
|
puts
|
|
396
394
|
|
|
397
|
-
puts table.render(:unicode, padding: [0,1,0,1], multiline: true)
|
|
395
|
+
puts table.render(:unicode, padding: [0, 1, 0, 1], multiline: true)
|
|
398
396
|
revs
|
|
399
397
|
end
|
|
400
398
|
|
|
401
399
|
def heroku_app_by_git(git_url)
|
|
402
400
|
busy('fetching heroku apps', :dots)
|
|
403
|
-
r = h.app.list.select{ |e| e['git_url'] == git_url }
|
|
401
|
+
r = h.app.list.select { |e| e['git_url'] == git_url }
|
|
404
402
|
unbusy
|
|
405
403
|
raise "FATAL: Found multiple heroku apps with git_url=#{git_url}" if r.length > 1
|
|
406
404
|
r[0]
|
|
@@ -410,16 +408,16 @@ module Hu
|
|
|
410
408
|
busy('fetching heroku pipelines', :dots)
|
|
411
409
|
couplings = h.pipeline_coupling.list
|
|
412
410
|
unbusy
|
|
413
|
-
r = couplings.select{ |e| e['app']['id'] == app['id'] }
|
|
411
|
+
r = couplings.select { |e| e['app']['id'] == app['id'] }
|
|
414
412
|
raise "FATAL: Found multiple heroku pipelines with app.id=#{r['id']}" if r.length > 1
|
|
415
413
|
raise "FATAL: Found no heroku pipeline for app.id=#{r['id']}" if r.length != 1
|
|
416
414
|
r = r[0]
|
|
417
415
|
pipeline_name = r['pipeline']['name']
|
|
418
416
|
|
|
419
|
-
r = couplings.select{ |e| e['pipeline']['id'] == r['pipeline']['id']
|
|
417
|
+
r = couplings.select { |e| e['pipeline']['id'] == r['pipeline']['id'] && e['stage'] == 'staging' }[0]
|
|
420
418
|
staging_app_id = r['app']['id']
|
|
421
419
|
|
|
422
|
-
r = couplings.select{ |e| e['pipeline']['id'] == r['pipeline']['id']
|
|
420
|
+
r = couplings.select { |e| e['pipeline']['id'] == r['pipeline']['id'] && e['stage'] == 'production' }[0]
|
|
423
421
|
raise "FATAL: No production app in pipeline #{pipeline_name}" if r.nil?
|
|
424
422
|
prod_app_id = r['app']['id']
|
|
425
423
|
[pipeline_name, staging_app_id, prod_app_id]
|
|
@@ -429,7 +427,7 @@ module Hu
|
|
|
429
427
|
@h ||= PlatformAPI.connect_oauth(Hu::API_TOKEN)
|
|
430
428
|
end
|
|
431
429
|
|
|
432
|
-
def run_each(script, opts={})
|
|
430
|
+
def run_each(script, opts = {})
|
|
433
431
|
opts = {
|
|
434
432
|
quiet: false,
|
|
435
433
|
failfast: true,
|
|
@@ -441,23 +439,23 @@ module Hu
|
|
|
441
439
|
script.lines.each_with_index do |line, i|
|
|
442
440
|
line.chomp!
|
|
443
441
|
case line[0]
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
442
|
+
when '#'
|
|
443
|
+
puts "\n" + line.bright unless opts[:quiet]
|
|
444
|
+
when ':'
|
|
445
|
+
opts[:quiet] = true if line == ':quiet'
|
|
446
|
+
opts[:failfast] = false if line == ':return'
|
|
447
|
+
opts[:spinner] = false if line == ':nospinner'
|
|
448
|
+
if line == ':stream'
|
|
449
|
+
opts[:stream] = true
|
|
450
|
+
opts[:quiet] = false
|
|
451
|
+
end
|
|
454
452
|
end
|
|
455
|
-
next if line.empty?
|
|
453
|
+
next if line.empty? || ['#', ':'].include?(line[0])
|
|
456
454
|
|
|
457
455
|
status = nil
|
|
458
456
|
if opts[:stream]
|
|
459
457
|
puts "\n> ".color(:green) + line.color(:black).bright
|
|
460
|
-
PTY.spawn(line) do |r,
|
|
458
|
+
PTY.spawn(line) do |r, _w, pid|
|
|
461
459
|
@tspin ||= Thread.new do
|
|
462
460
|
@minispin_last_char = Time.now
|
|
463
461
|
i = 0
|
|
@@ -467,48 +465,47 @@ module Hu
|
|
|
467
465
|
sleep 0.1
|
|
468
466
|
next
|
|
469
467
|
end
|
|
470
|
-
@spinlock.synchronize
|
|
468
|
+
@spinlock.synchronize do
|
|
471
469
|
print "\e[?25l"
|
|
472
|
-
print Paint[' ', '#000', Lol.rainbow(1, i/3.0)]
|
|
470
|
+
print Paint[' ', '#000', Lol.rainbow(1, i / 3.0)]
|
|
473
471
|
sleep 0.12
|
|
474
472
|
print 8.chr
|
|
475
473
|
print ' '
|
|
476
474
|
print 8.chr
|
|
477
475
|
i += 1
|
|
478
476
|
print "\e[?25h"
|
|
479
|
-
|
|
477
|
+
end
|
|
480
478
|
end
|
|
481
479
|
end
|
|
482
480
|
|
|
483
|
-
|
|
481
|
+
until r.eof?
|
|
484
482
|
c = r.getc
|
|
485
|
-
@spinlock.synchronize
|
|
483
|
+
@spinlock.synchronize do
|
|
486
484
|
print c
|
|
487
485
|
@minispin_last_char = Time.now
|
|
488
|
-
|
|
486
|
+
end
|
|
489
487
|
end
|
|
490
|
-
|
|
488
|
+
_pid, status = Process.wait2(pid)
|
|
491
489
|
@minispin_last_char = :end
|
|
492
490
|
@tspin.join
|
|
493
491
|
@tspin = nil
|
|
494
|
-
#status = PTY.check(pid)
|
|
492
|
+
# status = PTY.check(pid)
|
|
495
493
|
end
|
|
496
494
|
else
|
|
497
495
|
busy line if opts[:spinner]
|
|
498
496
|
output, status = Open3.capture2e(line)
|
|
499
497
|
unbusy if opts[:spinner]
|
|
500
498
|
color = (status.exitstatus == 0) ? :green : :red
|
|
501
|
-
if status.exitstatus != 0
|
|
499
|
+
if status.exitstatus != 0 || !opts[:quiet]
|
|
502
500
|
puts "\n> ".color(color) + line.color(:black).bright
|
|
503
501
|
puts output
|
|
504
502
|
end
|
|
505
503
|
end
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
end
|
|
504
|
+
next unless status.exitstatus != 0
|
|
505
|
+
shutdown if opts[:failfast]
|
|
506
|
+
puts "Error, exit #{status.exitstatus}: #{line} (L#{i})".color(:red).bright
|
|
507
|
+
exit status.exitstatus if opts[:failfast]
|
|
508
|
+
return status.exitstatus
|
|
512
509
|
end
|
|
513
510
|
0
|
|
514
511
|
end
|
|
@@ -516,13 +513,13 @@ module Hu
|
|
|
516
513
|
def find_highest_version_tag
|
|
517
514
|
output, status = Open3.capture2e('git tag')
|
|
518
515
|
if status.exitstatus != 0
|
|
519
|
-
puts
|
|
516
|
+
puts 'Error fetching git tags.'
|
|
520
517
|
exit status.exitstatus
|
|
521
518
|
end
|
|
522
519
|
|
|
523
|
-
versions = VersionSorter.sort(output.lines.map(&:chomp).map {|e| e[0].
|
|
520
|
+
versions = VersionSorter.sort(output.lines.map(&:chomp).map { |e| e[0].casecmp('v').zero? ? e.downcase : "v#{e.downcase}" })
|
|
524
521
|
latest = versions[-1] || 'v0.0.0'
|
|
525
|
-
latest = "v#{latest}" unless latest[0] ==
|
|
522
|
+
latest = "v#{latest}" unless latest[0] == 'v'
|
|
526
523
|
latest
|
|
527
524
|
end
|
|
528
525
|
|
|
@@ -569,7 +566,7 @@ module Hu
|
|
|
569
566
|
EOS
|
|
570
567
|
end
|
|
571
568
|
|
|
572
|
-
def
|
|
569
|
+
def heroku_git_remote
|
|
573
570
|
ensure_repo_has_heroku_remote
|
|
574
571
|
`git remote show -n heroku | grep Push`.chomp.split(':', 2)[1][1..-1]
|
|
575
572
|
end
|
|
@@ -586,23 +583,23 @@ module Hu
|
|
|
586
583
|
# Setup git remote
|
|
587
584
|
puts
|
|
588
585
|
puts "This repository has no 'heroku' remote.".color(:red)
|
|
589
|
-
puts
|
|
586
|
+
puts 'We will set one up now. Please select the pipeline that you'
|
|
590
587
|
puts "wish to deploy to, and we will set the 'heroku' remote"
|
|
591
|
-
puts
|
|
588
|
+
puts 'to the staging application in that pipeline.'
|
|
592
589
|
puts
|
|
593
590
|
|
|
594
591
|
busy
|
|
595
|
-
heroku_apps=JSON.parse(`heroku pipelines:list --json`)
|
|
592
|
+
heroku_apps = JSON.parse(`heroku pipelines:list --json`)
|
|
596
593
|
unbusy
|
|
597
594
|
|
|
598
595
|
prompt = TTY::Prompt.new
|
|
599
|
-
pipeline_name = prompt.select(
|
|
596
|
+
pipeline_name = prompt.select('Select pipeline:') do |menu|
|
|
600
597
|
menu.enum '.'
|
|
601
598
|
heroku_apps.each do |app|
|
|
602
599
|
menu.choice app['name']
|
|
603
600
|
end
|
|
604
601
|
end
|
|
605
|
-
staging_app=JSON.parse(`heroku pipelines:info #{pipeline_name} --json`)['apps'].select{|e| e['coupling']['stage'] == 'staging'}[0]
|
|
602
|
+
staging_app = JSON.parse(`heroku pipelines:info #{pipeline_name} --json`)['apps'].select { |e| e['coupling']['stage'] == 'staging' }[0]
|
|
606
603
|
if staging_app.nil?
|
|
607
604
|
puts "Error: Pipeline #{pipeline_name} has no staging app.".color(:red)
|
|
608
605
|
exit 1
|
|
@@ -614,7 +611,7 @@ module Hu
|
|
|
614
611
|
EOS
|
|
615
612
|
end
|
|
616
613
|
|
|
617
|
-
def prompt_for_release_tag(propose_version='v0.0.1', try_version=nil, keep_existing=false)
|
|
614
|
+
def prompt_for_release_tag(propose_version = 'v0.0.1', try_version = nil, keep_existing = false)
|
|
618
615
|
prompt = TTY::Prompt.new
|
|
619
616
|
loop do
|
|
620
617
|
if try_version
|
|
@@ -622,14 +619,12 @@ module Hu
|
|
|
622
619
|
try_version = nil
|
|
623
620
|
else
|
|
624
621
|
show_existing_git_tags
|
|
625
|
-
release_tag = prompt.ask(
|
|
622
|
+
release_tag = prompt.ask('Please enter a tag for this release', default: propose_version)
|
|
626
623
|
begin
|
|
627
624
|
unless release_tag[0] == 'v'
|
|
628
|
-
raise ArgumentError,
|
|
629
|
-
end
|
|
630
|
-
if release_tag.length < 5
|
|
631
|
-
raise ArgumentError, "too short"
|
|
625
|
+
raise ArgumentError, 'Version string must start with the letter v'
|
|
632
626
|
end
|
|
627
|
+
raise ArgumentError, 'too short' if release_tag.length < 5
|
|
633
628
|
Versionomy.parse(release_tag)
|
|
634
629
|
rescue => e
|
|
635
630
|
puts "Error: Tag does not look like a semantic version (#{e})".color(:red)
|
|
@@ -638,32 +633,32 @@ module Hu
|
|
|
638
633
|
end
|
|
639
634
|
|
|
640
635
|
branches = `git for-each-ref refs/heads/ --format='%(refname:short)'`.lines.map(&:chomp)
|
|
641
|
-
existing_branch = branches.find {|
|
|
636
|
+
existing_branch = branches.find { |b| b.start_with? 'release/' }
|
|
642
637
|
branch_already_exists = !existing_branch.nil?
|
|
643
638
|
release_tag = existing_branch[8..-1] if keep_existing && branch_already_exists
|
|
644
639
|
|
|
645
640
|
if branch_already_exists && !keep_existing
|
|
646
|
-
choice = prompt.expand("The branch '"+"release/#{release_tag}".color(:red)+"' already exists. What shall we do?",
|
|
647
|
-
|
|
641
|
+
choice = prompt.expand("The branch '" + "release/#{release_tag}".color(:red) + "' already exists. What shall we do?",
|
|
642
|
+
default: 0) do |q|
|
|
648
643
|
q.choice key: 'k', name: 'Keep, continue with the existing branch', value: :keep
|
|
649
644
|
q.choice key: 'D', name: "Delete branch release/#{release_tag} and retry", value: :delete
|
|
650
645
|
q.choice key: 'q', name: 'Quit', value: :quit
|
|
651
646
|
end
|
|
652
647
|
|
|
653
648
|
case choice
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
649
|
+
when :quit
|
|
650
|
+
puts
|
|
651
|
+
exit 0
|
|
652
|
+
when :delete
|
|
653
|
+
delete_branch("release/#{release_tag}")
|
|
654
|
+
next
|
|
660
655
|
end
|
|
661
656
|
end
|
|
662
657
|
|
|
663
658
|
if branch_already_exists
|
|
664
659
|
checkout_branch("release/#{release_tag}")
|
|
665
660
|
else
|
|
666
|
-
develop_tag
|
|
661
|
+
develop_tag = `git tag --points-at develop 2>/dev/null`.lines.find { |tag| tag[0] == 'v' }&.chomp
|
|
667
662
|
if develop_tag
|
|
668
663
|
release_tag = develop_tag
|
|
669
664
|
else
|
|
@@ -697,7 +692,7 @@ module Hu
|
|
|
697
692
|
end
|
|
698
693
|
|
|
699
694
|
def finish_release(release_tag, env)
|
|
700
|
-
env.each { |k,v| ENV[k] = v }
|
|
695
|
+
env.each { |k, v| ENV[k] = v }
|
|
701
696
|
if File.executable? '.hu/hooks/pre_release'
|
|
702
697
|
run_each <<-EOS.strip_heredoc
|
|
703
698
|
# Run pre-release hook
|
|
@@ -733,7 +728,7 @@ module Hu
|
|
|
733
728
|
|
|
734
729
|
def create_changelog(env)
|
|
735
730
|
if File.executable? '.hu/hooks/changelog'
|
|
736
|
-
env.each { |k,v| ENV[k] = v }
|
|
731
|
+
env.each { |k, v| ENV[k] = v }
|
|
737
732
|
`.hu/hooks/changelog`
|
|
738
733
|
else
|
|
739
734
|
`git log --pretty=format:" - %s" #{env['PREVIOUS_TAG']}..HEAD 2>/dev/null`
|
|
@@ -745,10 +740,10 @@ module Hu
|
|
|
745
740
|
unbusy
|
|
746
741
|
end
|
|
747
742
|
|
|
748
|
-
def busy(msg='', format
|
|
743
|
+
def busy(msg = '', format = :classic, clear = true)
|
|
749
744
|
return if @@shutting_down
|
|
750
745
|
format ||= TTY::Formats::FORMATS.keys.sample
|
|
751
|
-
options = {format: format, hide_cursor: true, error_mark: "\e[31;1m✖\e[0m", success_mark: "\e[32;1m✔\e[0m", clear: clear}
|
|
746
|
+
options = { format: format, hide_cursor: true, error_mark: "\e[31;1m✖\e[0m", success_mark: "\e[32;1m✔\e[0m", clear: clear }
|
|
752
747
|
@@spinner = TTY::Spinner.new("\e[0;1m#{msg}#{msg.empty? ? '' : ' '}\e[0m\e[32;1m:spinner\e[0m", options)
|
|
753
748
|
@@spinner.start
|
|
754
749
|
end
|
|
@@ -758,15 +753,15 @@ module Hu
|
|
|
758
753
|
printf "\e[?25h"
|
|
759
754
|
end
|
|
760
755
|
|
|
761
|
-
def with_spinner(msg='', format
|
|
756
|
+
def with_spinner(msg = '', format = :classic)
|
|
762
757
|
busy(msg, format)
|
|
763
|
-
|
|
758
|
+
yield
|
|
764
759
|
unbusy
|
|
765
760
|
end
|
|
766
761
|
|
|
767
762
|
def anykey
|
|
768
763
|
puts TTY::Cursor.hide
|
|
769
|
-
print
|
|
764
|
+
print '--- Press any key ---'.color(:cyan)
|
|
770
765
|
STDIN.getch
|
|
771
766
|
print TTY::Cursor.clear_line + TTY::Cursor.show
|
|
772
767
|
end
|
|
@@ -774,7 +769,7 @@ module Hu
|
|
|
774
769
|
def dp(label, *args)
|
|
775
770
|
return unless ENV['DEBUG']
|
|
776
771
|
puts "--- DEBUG #{label} ---"
|
|
777
|
-
ap
|
|
772
|
+
ap(*args)
|
|
778
773
|
puts "--- ^#{label}^ ---"
|
|
779
774
|
end
|
|
780
775
|
|
|
@@ -800,4 +795,3 @@ module Hu
|
|
|
800
795
|
end
|
|
801
796
|
end
|
|
802
797
|
end
|
|
803
|
-
|
data/lib/hu/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- moe
|
|
@@ -271,6 +271,8 @@ extensions: []
|
|
|
271
271
|
extra_rdoc_files: []
|
|
272
272
|
files:
|
|
273
273
|
- ".gitignore"
|
|
274
|
+
- ".rubocop.yml"
|
|
275
|
+
- ".rubocop_todo.yml"
|
|
274
276
|
- Gemfile
|
|
275
277
|
- LICENSE.txt
|
|
276
278
|
- README.md
|