azuki 0.0.1
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 +7 -0
- data/README.md +71 -0
- data/bin/azuki +17 -0
- data/data/cacert.pem +3988 -0
- data/lib/azuki.rb +17 -0
- data/lib/azuki/auth.rb +339 -0
- data/lib/azuki/cli.rb +38 -0
- data/lib/azuki/client.rb +764 -0
- data/lib/azuki/client/azuki_postgresql.rb +141 -0
- data/lib/azuki/client/cisaurus.rb +26 -0
- data/lib/azuki/client/pgbackups.rb +113 -0
- data/lib/azuki/client/rendezvous.rb +108 -0
- data/lib/azuki/client/ssl_endpoint.rb +25 -0
- data/lib/azuki/command.rb +294 -0
- data/lib/azuki/command/account.rb +23 -0
- data/lib/azuki/command/accounts.rb +34 -0
- data/lib/azuki/command/addons.rb +305 -0
- data/lib/azuki/command/apps.rb +393 -0
- data/lib/azuki/command/auth.rb +86 -0
- data/lib/azuki/command/base.rb +230 -0
- data/lib/azuki/command/certs.rb +209 -0
- data/lib/azuki/command/config.rb +137 -0
- data/lib/azuki/command/db.rb +218 -0
- data/lib/azuki/command/domains.rb +85 -0
- data/lib/azuki/command/drains.rb +46 -0
- data/lib/azuki/command/fork.rb +164 -0
- data/lib/azuki/command/git.rb +64 -0
- data/lib/azuki/command/help.rb +179 -0
- data/lib/azuki/command/keys.rb +115 -0
- data/lib/azuki/command/labs.rb +147 -0
- data/lib/azuki/command/logs.rb +45 -0
- data/lib/azuki/command/maintenance.rb +61 -0
- data/lib/azuki/command/pg.rb +269 -0
- data/lib/azuki/command/pgbackups.rb +329 -0
- data/lib/azuki/command/plugins.rb +110 -0
- data/lib/azuki/command/ps.rb +232 -0
- data/lib/azuki/command/regions.rb +22 -0
- data/lib/azuki/command/releases.rb +124 -0
- data/lib/azuki/command/run.rb +180 -0
- data/lib/azuki/command/sharing.rb +89 -0
- data/lib/azuki/command/ssl.rb +43 -0
- data/lib/azuki/command/stack.rb +62 -0
- data/lib/azuki/command/status.rb +51 -0
- data/lib/azuki/command/update.rb +47 -0
- data/lib/azuki/command/version.rb +23 -0
- data/lib/azuki/deprecated.rb +5 -0
- data/lib/azuki/deprecated/help.rb +38 -0
- data/lib/azuki/distribution.rb +9 -0
- data/lib/azuki/excon.rb +9 -0
- data/lib/azuki/helpers.rb +517 -0
- data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
- data/lib/azuki/helpers/log_displayer.rb +70 -0
- data/lib/azuki/plugin.rb +163 -0
- data/lib/azuki/updater.rb +171 -0
- data/lib/azuki/version.rb +3 -0
- data/lib/vendor/azuki/okjson.rb +598 -0
- data/spec/azuki/auth_spec.rb +256 -0
- data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
- data/spec/azuki/client/pgbackups_spec.rb +43 -0
- data/spec/azuki/client/rendezvous_spec.rb +62 -0
- data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
- data/spec/azuki/client_spec.rb +564 -0
- data/spec/azuki/command/addons_spec.rb +601 -0
- data/spec/azuki/command/apps_spec.rb +351 -0
- data/spec/azuki/command/auth_spec.rb +38 -0
- data/spec/azuki/command/base_spec.rb +109 -0
- data/spec/azuki/command/certs_spec.rb +178 -0
- data/spec/azuki/command/config_spec.rb +144 -0
- data/spec/azuki/command/db_spec.rb +110 -0
- data/spec/azuki/command/domains_spec.rb +87 -0
- data/spec/azuki/command/drains_spec.rb +34 -0
- data/spec/azuki/command/fork_spec.rb +56 -0
- data/spec/azuki/command/git_spec.rb +144 -0
- data/spec/azuki/command/help_spec.rb +93 -0
- data/spec/azuki/command/keys_spec.rb +120 -0
- data/spec/azuki/command/labs_spec.rb +100 -0
- data/spec/azuki/command/logs_spec.rb +60 -0
- data/spec/azuki/command/maintenance_spec.rb +51 -0
- data/spec/azuki/command/pg_spec.rb +236 -0
- data/spec/azuki/command/pgbackups_spec.rb +307 -0
- data/spec/azuki/command/plugins_spec.rb +104 -0
- data/spec/azuki/command/ps_spec.rb +195 -0
- data/spec/azuki/command/releases_spec.rb +130 -0
- data/spec/azuki/command/run_spec.rb +83 -0
- data/spec/azuki/command/sharing_spec.rb +59 -0
- data/spec/azuki/command/stack_spec.rb +46 -0
- data/spec/azuki/command/status_spec.rb +48 -0
- data/spec/azuki/command/version_spec.rb +16 -0
- data/spec/azuki/command_spec.rb +211 -0
- data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
- data/spec/azuki/helpers_spec.rb +48 -0
- data/spec/azuki/plugin_spec.rb +172 -0
- data/spec/azuki/updater_spec.rb +44 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +224 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- metadata +211 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require "azuki/command/base"
|
|
2
|
+
|
|
3
|
+
# authentication (login, logout)
|
|
4
|
+
#
|
|
5
|
+
class Azuki::Command::Auth < Azuki::Command::Base
|
|
6
|
+
|
|
7
|
+
# auth
|
|
8
|
+
#
|
|
9
|
+
# Authenticate, display token and current user
|
|
10
|
+
def index
|
|
11
|
+
validate_arguments!
|
|
12
|
+
|
|
13
|
+
Azuki::Command::Help.new.send(:help_for_command, current_command)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# auth:login
|
|
17
|
+
#
|
|
18
|
+
# log in with your azuki credentials
|
|
19
|
+
#
|
|
20
|
+
#Example:
|
|
21
|
+
#
|
|
22
|
+
# $ azuki auth:login
|
|
23
|
+
# Enter your Azuki credentials:
|
|
24
|
+
# Email: email@example.com
|
|
25
|
+
# Password (typing will be hidden):
|
|
26
|
+
# Authentication successful.
|
|
27
|
+
#
|
|
28
|
+
def login
|
|
29
|
+
validate_arguments!
|
|
30
|
+
|
|
31
|
+
Azuki::Auth.login
|
|
32
|
+
display "Authentication successful."
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
alias_command "login", "auth:login"
|
|
36
|
+
|
|
37
|
+
# auth:logout
|
|
38
|
+
#
|
|
39
|
+
# clear local authentication credentials
|
|
40
|
+
#
|
|
41
|
+
#Example:
|
|
42
|
+
#
|
|
43
|
+
# $ azuki auth:logout
|
|
44
|
+
# Local credentials cleared.
|
|
45
|
+
#
|
|
46
|
+
def logout
|
|
47
|
+
validate_arguments!
|
|
48
|
+
|
|
49
|
+
Azuki::Auth.logout
|
|
50
|
+
display "Local credentials cleared."
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias_command "logout", "auth:logout"
|
|
54
|
+
|
|
55
|
+
# auth:token
|
|
56
|
+
#
|
|
57
|
+
# display your api token
|
|
58
|
+
#
|
|
59
|
+
#Example:
|
|
60
|
+
#
|
|
61
|
+
# $ azuki auth:token
|
|
62
|
+
# ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD
|
|
63
|
+
#
|
|
64
|
+
def token
|
|
65
|
+
validate_arguments!
|
|
66
|
+
|
|
67
|
+
display Azuki::Auth.api_key
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# auth:whoami
|
|
71
|
+
#
|
|
72
|
+
# display your azuki email address
|
|
73
|
+
#
|
|
74
|
+
#Example:
|
|
75
|
+
#
|
|
76
|
+
# $ azuki auth:whoami
|
|
77
|
+
# email@example.com
|
|
78
|
+
#
|
|
79
|
+
def whoami
|
|
80
|
+
validate_arguments!
|
|
81
|
+
|
|
82
|
+
display Azuki::Auth.user
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "azuki/auth"
|
|
3
|
+
require "azuki/client/rendezvous"
|
|
4
|
+
require "azuki/command"
|
|
5
|
+
|
|
6
|
+
class Azuki::Command::Base
|
|
7
|
+
include Azuki::Helpers
|
|
8
|
+
|
|
9
|
+
def self.namespace
|
|
10
|
+
self.to_s.split("::").last.downcase
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :args
|
|
14
|
+
attr_reader :options
|
|
15
|
+
|
|
16
|
+
def initialize(args=[], options={})
|
|
17
|
+
@args = args
|
|
18
|
+
@options = options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def app
|
|
22
|
+
@app ||= if options[:confirm].is_a?(String)
|
|
23
|
+
if options[:app] && (options[:app] != options[:confirm])
|
|
24
|
+
error("Mismatch between --app and --confirm")
|
|
25
|
+
end
|
|
26
|
+
options[:confirm]
|
|
27
|
+
elsif options[:app].is_a?(String)
|
|
28
|
+
options[:app]
|
|
29
|
+
elsif ENV.has_key?('AZUKI_APP')
|
|
30
|
+
ENV['AZUKI_APP']
|
|
31
|
+
elsif app_from_dir = extract_app_in_dir(Dir.pwd)
|
|
32
|
+
app_from_dir
|
|
33
|
+
else
|
|
34
|
+
# raise instead of using error command to enable rescuing when app is optional
|
|
35
|
+
raise Azuki::Command::CommandFailed.new("No app specified.\nRun this command from an app folder or specify which app to use with --app APP.")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def api
|
|
40
|
+
Azuki::Auth.api
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def azuki
|
|
44
|
+
Azuki::Auth.client
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def self.inherited(klass)
|
|
50
|
+
unless klass == Azuki::Command::Base
|
|
51
|
+
help = extract_help_from_caller(caller.first)
|
|
52
|
+
|
|
53
|
+
Azuki::Command.register_namespace(
|
|
54
|
+
:name => klass.namespace,
|
|
55
|
+
:description => help.first
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.method_added(method)
|
|
61
|
+
return if self == Azuki::Command::Base
|
|
62
|
+
return if private_method_defined?(method)
|
|
63
|
+
return if protected_method_defined?(method)
|
|
64
|
+
|
|
65
|
+
help = extract_help_from_caller(caller.first)
|
|
66
|
+
resolved_method = (method.to_s == "index") ? nil : method.to_s
|
|
67
|
+
command = [ self.namespace, resolved_method ].compact.join(":")
|
|
68
|
+
banner = extract_banner(help) || command
|
|
69
|
+
|
|
70
|
+
Azuki::Command.register_command(
|
|
71
|
+
:klass => self,
|
|
72
|
+
:method => method,
|
|
73
|
+
:namespace => self.namespace,
|
|
74
|
+
:command => command,
|
|
75
|
+
:banner => banner.strip,
|
|
76
|
+
:help => help.join("\n"),
|
|
77
|
+
:summary => extract_summary(help),
|
|
78
|
+
:description => extract_description(help),
|
|
79
|
+
:options => extract_options(help)
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.alias_command(new, old)
|
|
84
|
+
raise "no such command: #{old}" unless Azuki::Command.commands[old]
|
|
85
|
+
Azuki::Command.command_aliases[new] = old
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_app
|
|
89
|
+
output_with_bang "Command::Base#extract_app has been deprecated. Please use Command::Base#app instead. #{caller.first}"
|
|
90
|
+
app
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# Parse the caller format and identify the file and line number as identified
|
|
95
|
+
# in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
|
|
96
|
+
# look for a colon followed by a digit as the delimiter. The biggest
|
|
97
|
+
# complication is windows paths, which have a color after the drive letter.
|
|
98
|
+
# This regex will match paths as anything from the beginning to a colon
|
|
99
|
+
# directly followed by a number (the line number).
|
|
100
|
+
#
|
|
101
|
+
# Examples of the caller format :
|
|
102
|
+
# * c:/Ruby192/lib/.../lib/azuki/command/addons.rb:8:in `<module:Command>'
|
|
103
|
+
# * c:/Ruby192/lib/.../azuki-2.0.1/lib/azuki/command/pg.rb:96:in `<class:Pg>'
|
|
104
|
+
# * /Users/ph7/...../xray-1.1/lib/xray/thread_dump_signal_handler.rb:9
|
|
105
|
+
#
|
|
106
|
+
def self.extract_help_from_caller(line)
|
|
107
|
+
# pull out of the caller the information for the file path and line number
|
|
108
|
+
if line =~ /^(.+?):(\d+)/
|
|
109
|
+
extract_help($1, $2)
|
|
110
|
+
else
|
|
111
|
+
raise("unable to extract help from caller: #{line}")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.extract_help(file, line_number)
|
|
116
|
+
buffer = []
|
|
117
|
+
lines = Azuki::Command.files[file]
|
|
118
|
+
|
|
119
|
+
(line_number.to_i-2).downto(0) do |i|
|
|
120
|
+
line = lines[i]
|
|
121
|
+
case line[0..0]
|
|
122
|
+
when ""
|
|
123
|
+
when "#"
|
|
124
|
+
buffer.unshift(line[1..-1])
|
|
125
|
+
else
|
|
126
|
+
break
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
buffer
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def self.extract_banner(help)
|
|
134
|
+
help.first
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.extract_summary(help)
|
|
138
|
+
extract_description(help).split("\n")[2].to_s.split("\n").first
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.extract_description(help)
|
|
142
|
+
help.reject do |line|
|
|
143
|
+
line =~ /^\s+-(.+)#(.+)/
|
|
144
|
+
end.join("\n")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.extract_options(help)
|
|
148
|
+
help.select do |line|
|
|
149
|
+
line =~ /^\s+-(.+)#(.+)/
|
|
150
|
+
end.inject([]) do |options, line|
|
|
151
|
+
args = line.split('#', 2).first
|
|
152
|
+
args = args.split(/,\s*/).map {|arg| arg.strip}.sort.reverse
|
|
153
|
+
name = args.last.split(' ', 2).first[2..-1]
|
|
154
|
+
options << { :name => name, :args => args }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def current_command
|
|
159
|
+
Azuki::Command.current_command
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def extract_option(key)
|
|
163
|
+
options[key.dup.gsub('-','_').to_sym]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def invalid_arguments
|
|
167
|
+
Azuki::Command.invalid_arguments
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def shift_argument
|
|
171
|
+
Azuki::Command.shift_argument
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def validate_arguments!
|
|
175
|
+
Azuki::Command.validate_arguments!
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def extract_app_in_dir(dir)
|
|
179
|
+
return unless remotes = git_remotes(dir)
|
|
180
|
+
|
|
181
|
+
if remote = options[:remote]
|
|
182
|
+
remotes[remote]
|
|
183
|
+
elsif remote = extract_app_from_git_config
|
|
184
|
+
remotes[remote]
|
|
185
|
+
else
|
|
186
|
+
apps = remotes.values.uniq
|
|
187
|
+
if apps.size == 1
|
|
188
|
+
apps.first
|
|
189
|
+
else
|
|
190
|
+
raise(Azuki::Command::CommandFailed, "Multiple apps in folder and no app specified.\nSpecify app with --app APP.")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def extract_app_from_git_config
|
|
196
|
+
remote = git("config azuki.remote")
|
|
197
|
+
remote == "" ? nil : remote
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def git_remotes(base_dir=Dir.pwd)
|
|
201
|
+
remotes = {}
|
|
202
|
+
original_dir = Dir.pwd
|
|
203
|
+
Dir.chdir(base_dir)
|
|
204
|
+
|
|
205
|
+
return unless File.exists?(".git")
|
|
206
|
+
git("remote -v").split("\n").each do |remote|
|
|
207
|
+
name, url, method = remote.split(/\s/)
|
|
208
|
+
if url =~ /^git@#{Azuki::Auth.git_host}(?:[\.\w]*):([\w\d-]+)\.git$/
|
|
209
|
+
remotes[name] = $1
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
Dir.chdir(original_dir)
|
|
214
|
+
if remotes.empty?
|
|
215
|
+
nil
|
|
216
|
+
else
|
|
217
|
+
remotes
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def escape(value)
|
|
222
|
+
azuki.escape(value)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
module Azuki::Command
|
|
227
|
+
unless const_defined?(:BaseWithApp)
|
|
228
|
+
BaseWithApp = Base
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require "azuki/command/base"
|
|
2
|
+
require "excon"
|
|
3
|
+
|
|
4
|
+
# manage ssl endpoints for an app
|
|
5
|
+
#
|
|
6
|
+
class Azuki::Command::Certs < Azuki::Command::Base
|
|
7
|
+
SSL_DOCTOR = Excon.new(ENV["SSL_DOCTOR_URL"] || "https://ssl-doctor.azukiapp.com/")
|
|
8
|
+
|
|
9
|
+
class UsageError < StandardError; end
|
|
10
|
+
|
|
11
|
+
# certs
|
|
12
|
+
#
|
|
13
|
+
# List ssl endpoints for an app.
|
|
14
|
+
#
|
|
15
|
+
def index
|
|
16
|
+
endpoints = azuki.ssl_endpoint_list(app)
|
|
17
|
+
|
|
18
|
+
if endpoints.empty?
|
|
19
|
+
display "#{app} has no SSL Endpoints."
|
|
20
|
+
display "Use `azuki certs:add CRT KEY` to add one."
|
|
21
|
+
else
|
|
22
|
+
endpoints.map! do |endpoint|
|
|
23
|
+
{
|
|
24
|
+
'cname' => endpoint['cname'],
|
|
25
|
+
'domains' => endpoint['ssl_cert']['cert_domains'].join(', '),
|
|
26
|
+
'expires_at' => format_date(endpoint['ssl_cert']['expires_at']),
|
|
27
|
+
'ca_signed?' => endpoint['ssl_cert']['ca_signed?'].to_s.capitalize
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
display_table(
|
|
31
|
+
endpoints,
|
|
32
|
+
%w( cname domains expires_at ca_signed? ),
|
|
33
|
+
[ "Endpoint", "Common Name(s)", "Expires", "Trusted" ]
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# certs:chain CRT [CRT ...]
|
|
39
|
+
#
|
|
40
|
+
# Print the ordered and complete chain for the given certificate.
|
|
41
|
+
#
|
|
42
|
+
# Optional intermediate certificates may be given too, and will
|
|
43
|
+
# be used during chain resolution.
|
|
44
|
+
#
|
|
45
|
+
def chain
|
|
46
|
+
puts read_crt_through_ssl_doctor
|
|
47
|
+
rescue UsageError
|
|
48
|
+
fail("Usage: azuki certs:chain CRT [CRT ...]\nMust specify at least one certificate file.")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# certs:key CRT KEY [KEY ...]
|
|
52
|
+
#
|
|
53
|
+
# Print the correct key for the given certificate.
|
|
54
|
+
#
|
|
55
|
+
# You must pass one single certificate, and one or more keys.
|
|
56
|
+
# The first key that signs the certificate will be printed back.
|
|
57
|
+
#
|
|
58
|
+
def key
|
|
59
|
+
crt, key = read_crt_and_key_through_ssl_doctor("Testing for signing key")
|
|
60
|
+
puts key
|
|
61
|
+
rescue UsageError
|
|
62
|
+
fail("Usage: azuki certs:key CRT KEY [KEY ...]\nMust specify one certificate file and at least one key file.")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# certs:add CRT KEY
|
|
66
|
+
#
|
|
67
|
+
# Add an ssl endpoint to an app.
|
|
68
|
+
#
|
|
69
|
+
# --bypass # bypass the trust chain completion step
|
|
70
|
+
#
|
|
71
|
+
def add
|
|
72
|
+
crt, key = read_crt_and_key
|
|
73
|
+
endpoint = action("Adding SSL Endpoint to #{app}") { azuki.ssl_endpoint_add(app, crt, key) }
|
|
74
|
+
display_warnings(endpoint)
|
|
75
|
+
display "#{app} now served by #{endpoint['cname']}"
|
|
76
|
+
display "Certificate details:"
|
|
77
|
+
display_certificate_info(endpoint)
|
|
78
|
+
rescue UsageError
|
|
79
|
+
fail("Usage: azuki certs:add CRT KEY\nMust specify CRT and KEY to add cert.")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# certs:update CRT KEY
|
|
83
|
+
#
|
|
84
|
+
# Update an SSL Endpoint on an app.
|
|
85
|
+
#
|
|
86
|
+
# --bypass # bypass the trust chain completion step
|
|
87
|
+
#
|
|
88
|
+
def update
|
|
89
|
+
crt, key = read_crt_and_key
|
|
90
|
+
cname = options[:endpoint] || current_endpoint
|
|
91
|
+
endpoint = action("Updating SSL Endpoint #{cname} for #{app}") { azuki.ssl_endpoint_update(app, cname, crt, key) }
|
|
92
|
+
display_warnings(endpoint)
|
|
93
|
+
display "Updated certificate details:"
|
|
94
|
+
display_certificate_info(endpoint)
|
|
95
|
+
rescue UsageError
|
|
96
|
+
fail("Usage: azuki certs:update CRT KEY\nMust specify CRT and KEY to update cert.")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# certs:info
|
|
100
|
+
#
|
|
101
|
+
# Show certificate information for an ssl endpoint.
|
|
102
|
+
#
|
|
103
|
+
def info
|
|
104
|
+
cname = options[:endpoint] || current_endpoint
|
|
105
|
+
endpoint = action("Fetching SSL Endpoint #{cname} info for #{app}") do
|
|
106
|
+
azuki.ssl_endpoint_info(app, cname)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
display "Certificate details:"
|
|
110
|
+
display_certificate_info(endpoint)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# certs:remove
|
|
114
|
+
#
|
|
115
|
+
# Remove an SSL Endpoint from an app.
|
|
116
|
+
#
|
|
117
|
+
def remove
|
|
118
|
+
cname = options[:endpoint] || current_endpoint
|
|
119
|
+
action("Removing SSL Endpoint #{cname} from #{app}") do
|
|
120
|
+
azuki.ssl_endpoint_remove(app, cname)
|
|
121
|
+
end
|
|
122
|
+
display "NOTE: Billing is still active. Remove SSL Endpoint add-on to stop billing."
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# certs:rollback
|
|
126
|
+
#
|
|
127
|
+
# Rollback an SSL Endpoint for an app.
|
|
128
|
+
#
|
|
129
|
+
def rollback
|
|
130
|
+
cname = options[:endpoint] || current_endpoint
|
|
131
|
+
|
|
132
|
+
endpoint = action("Rolling back SSL Endpoint #{cname} for #{app}") do
|
|
133
|
+
azuki.ssl_endpoint_rollback(app, cname)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
display "New active certificate details:"
|
|
137
|
+
display_certificate_info(endpoint)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def current_endpoint
|
|
143
|
+
endpoint = azuki.ssl_endpoint_list(app).first || error("#{app} has no SSL Endpoints.")
|
|
144
|
+
endpoint["cname"]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def display_certificate_info(endpoint)
|
|
148
|
+
data = {
|
|
149
|
+
'Common Name(s)' => endpoint['ssl_cert']['cert_domains'],
|
|
150
|
+
'Expires At' => format_date(endpoint['ssl_cert']['expires_at']),
|
|
151
|
+
'Issuer' => endpoint['ssl_cert']['issuer'],
|
|
152
|
+
'Starts At' => format_date(endpoint['ssl_cert']['starts_at']),
|
|
153
|
+
'Subject' => endpoint['ssl_cert']['subject']
|
|
154
|
+
}
|
|
155
|
+
styled_hash(data)
|
|
156
|
+
|
|
157
|
+
if endpoint["ssl_cert"]["ca_signed?"]
|
|
158
|
+
display "SSL certificate is verified by a root authority."
|
|
159
|
+
elsif endpoint["issuer"] == endpoint["subject"]
|
|
160
|
+
display "SSL certificate is self signed."
|
|
161
|
+
else
|
|
162
|
+
display "SSL certificate is not trusted."
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def display_warnings(endpoint)
|
|
167
|
+
if endpoint["warnings"]
|
|
168
|
+
endpoint["warnings"].each do |field, warning|
|
|
169
|
+
display "WARNING: #{field} #{warning}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def display(msg = "", new_line = true)
|
|
175
|
+
super if $stdout.tty?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def post_to_ssl_doctor(path, action_text = nil)
|
|
179
|
+
raise UsageError if args.size < 1
|
|
180
|
+
action_text ||= "Resolving trust chain"
|
|
181
|
+
action(action_text) do
|
|
182
|
+
input = args.map { |arg| File.read(arg) rescue error("Unable to read #{args[0]} file") }.join("\n")
|
|
183
|
+
SSL_DOCTOR.post(:path => path, :body => input, :headers => {'Content-Type' => 'application/octet-stream'}, :expects => 200).body
|
|
184
|
+
end
|
|
185
|
+
rescue Excon::Errors::BadRequest, Excon::Errors::UnprocessableEntity => e
|
|
186
|
+
error(e.response.body)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def read_crt_and_key_through_ssl_doctor(action_text = nil)
|
|
190
|
+
crt_and_key = post_to_ssl_doctor("resolve-chain-and-key", action_text)
|
|
191
|
+
Azuki::OkJson.decode(crt_and_key).values_at("pem", "key")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def read_crt_through_ssl_doctor(action_text = nil)
|
|
195
|
+
post_to_ssl_doctor("resolve-chain", action_text)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def read_crt_and_key_bypassing_ssl_doctor
|
|
199
|
+
raise UsageError if args.size != 2
|
|
200
|
+
crt = File.read(args[0]) rescue error("Unable to read #{args[0]} CRT")
|
|
201
|
+
key = File.read(args[1]) rescue error("Unable to read #{args[1]} KEY")
|
|
202
|
+
[crt, key]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def read_crt_and_key
|
|
206
|
+
options[:bypass] ? read_crt_and_key_bypassing_ssl_doctor : read_crt_and_key_through_ssl_doctor
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|