azuki 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,164 @@
|
|
1
|
+
require "azuki/client/cisaurus"
|
2
|
+
require "azuki/command/base"
|
3
|
+
|
4
|
+
module Azuki::Command
|
5
|
+
|
6
|
+
# clone an existing app
|
7
|
+
#
|
8
|
+
class Fork < Base
|
9
|
+
|
10
|
+
# fork [NEWNAME]
|
11
|
+
#
|
12
|
+
# Fork an existing app -- copy config vars and Azuki Postgres data, and re-provision add-ons to a new app.
|
13
|
+
# New app name should not be an existing app. The new app will be created as part of the forking process.
|
14
|
+
#
|
15
|
+
# -s, --stack STACK # specify a stack
|
16
|
+
# --region REGION # HIDDEN: specify a region
|
17
|
+
#
|
18
|
+
def index
|
19
|
+
|
20
|
+
from = app
|
21
|
+
to = shift_argument || "#{from}-#{(rand*1000).to_i}"
|
22
|
+
|
23
|
+
from_info = api.get_app(from).body
|
24
|
+
|
25
|
+
to_info = action("Creating fork #{to}") do
|
26
|
+
api.post_app({
|
27
|
+
:name => to,
|
28
|
+
:region => options[:region] || from_info["region"],
|
29
|
+
:stack => options[:stack] || from_info["stack"],
|
30
|
+
:tier => from_info["tier"] == "legacy" ? "production" : from_info["tier"]
|
31
|
+
}).body
|
32
|
+
end
|
33
|
+
|
34
|
+
action("Copying slug") do
|
35
|
+
job_location = cisaurus_client.copy_slug(from, to)
|
36
|
+
loop do
|
37
|
+
break if cisaurus_client.job_done?(job_location)
|
38
|
+
print "."
|
39
|
+
sleep 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
from_config = api.get_config_vars(from).body
|
44
|
+
from_addons = api.get_addons(from).body
|
45
|
+
|
46
|
+
from_addons.each do |addon|
|
47
|
+
print "Adding #{addon["name"]}... "
|
48
|
+
begin
|
49
|
+
to_addon = api.post_addon(to, addon["name"]).body
|
50
|
+
puts "done"
|
51
|
+
rescue Azuki::API::Errors::RequestFailed => ex
|
52
|
+
puts "skipped (%s)" % json_decode(ex.response.body)["error"]
|
53
|
+
rescue Azuki::API::Errors::NotFound
|
54
|
+
puts "skipped (not found)"
|
55
|
+
end
|
56
|
+
if addon["name"] =~ /^azuki-postgresql:/
|
57
|
+
from_var_name = "#{addon["attachment_name"]}_URL"
|
58
|
+
from_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1]
|
59
|
+
if from_config[from_var_name] == from_config["DATABASE_URL"]
|
60
|
+
from_config["DATABASE_URL"] = api.get_config_vars(to).body["#{from_attachment}_URL"]
|
61
|
+
end
|
62
|
+
from_config.delete(from_var_name)
|
63
|
+
|
64
|
+
plan = addon["name"].split(":").last
|
65
|
+
unless %w(dev basic).include? plan
|
66
|
+
wait_for_db to, to_addon
|
67
|
+
end
|
68
|
+
|
69
|
+
check_for_pgbackups! from
|
70
|
+
check_for_pgbackups! to
|
71
|
+
migrate_db addon, from, to_addon, to
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
to_config = api.get_config_vars(to).body
|
76
|
+
|
77
|
+
action("Copying config vars") do
|
78
|
+
diff = from_config.inject({}) do |ax, (key, val)|
|
79
|
+
ax[key] = val unless to_config[key]
|
80
|
+
ax
|
81
|
+
end
|
82
|
+
api.put_config_vars to, diff
|
83
|
+
end
|
84
|
+
|
85
|
+
puts "Fork complete, view it at #{to_info['web_url']}"
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def cisaurus_client
|
91
|
+
cisaurus_url = ENV["CISAURUS_HOST"] || "https://cisaurus.azukiapp.com"
|
92
|
+
@cisaurus_client ||= Azuki::Client::Cisaurus.new(cisaurus_url)
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_for_pgbackups!(app)
|
96
|
+
unless api.get_addons(app).body.detect { |addon| addon["name"] =~ /^pgbackups:/ }
|
97
|
+
action("Adding pgbackups:plus to #{app}") do
|
98
|
+
api.post_addon app, "pgbackups:plus"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def migrate_db(from_addon, from, to_addon, to)
|
104
|
+
transfer = nil
|
105
|
+
|
106
|
+
action("Creating database backup from #{from}") do
|
107
|
+
from_config = api.get_config_vars(from).body
|
108
|
+
from_attachment = from_addon["attachment_name"]
|
109
|
+
pgb = Azuki::Client::Pgbackups.new(from_config["PGBACKUPS_URL"])
|
110
|
+
transfer = pgb.create_transfer(from_config["#{from_attachment}_URL"], from_attachment, nil, "BACKUP", :expire => "true")
|
111
|
+
error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
|
112
|
+
loop do
|
113
|
+
transfer = pgb.get_transfer(transfer["id"])
|
114
|
+
error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
|
115
|
+
break if transfer["finished_at"]
|
116
|
+
sleep 1
|
117
|
+
print "."
|
118
|
+
end
|
119
|
+
print " "
|
120
|
+
end
|
121
|
+
|
122
|
+
action("Restoring database backup to #{to}") do
|
123
|
+
to_config = api.get_config_vars(to).body
|
124
|
+
to_attachment = to_addon["message"].match(/Attached as (\w+)_URL\n/)[1]
|
125
|
+
pgb = Azuki::Client::Pgbackups.new(to_config["PGBACKUPS_URL"])
|
126
|
+
transfer = pgb.create_transfer(transfer["public_url"], "EXTERNAL_BACKUP", to_config["#{to_attachment}_URL"], to_attachment)
|
127
|
+
error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
|
128
|
+
loop do
|
129
|
+
transfer = pgb.get_transfer(transfer["id"])
|
130
|
+
error transfer["errors"].values.flatten.join("\n") if transfer["errors"]
|
131
|
+
break if transfer["finished_at"]
|
132
|
+
sleep 1
|
133
|
+
print "."
|
134
|
+
end
|
135
|
+
print " "
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def pg_api(starter=false)
|
140
|
+
host = starter ? "postgres-starter-api.azukiapp.com" : "postgres-api.azukiapp.com"
|
141
|
+
RestClient::Resource.new "https://#{host}/client/v11/databases", Azuki::Auth.user, Azuki::Auth.password
|
142
|
+
end
|
143
|
+
|
144
|
+
def wait_for_db(app, attachment)
|
145
|
+
attachments = api.get_attachments(app).body.inject({}) { |ax,att| ax.update(att["name"] => att["resource"]["name"]) }
|
146
|
+
attachment_name = attachment["message"].match(/Attached as (\w+)_URL\n/)[1]
|
147
|
+
action("Waiting for database to be ready") do
|
148
|
+
loop do
|
149
|
+
begin
|
150
|
+
waiting = json_decode(pg_api["#{attachments[attachment_name]}/wait_status"].get.to_s)["waiting?"]
|
151
|
+
break unless waiting
|
152
|
+
print "."
|
153
|
+
sleep 5
|
154
|
+
rescue RestClient::ResourceNotFound
|
155
|
+
rescue Interrupt
|
156
|
+
exit 0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
print " "
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "azuki/command/base"
|
2
|
+
|
3
|
+
# manage git for apps
|
4
|
+
#
|
5
|
+
class Azuki::Command::Git < Azuki::Command::Base
|
6
|
+
|
7
|
+
# git:clone APP [DIRECTORY]
|
8
|
+
#
|
9
|
+
# clones a azuki app to your local machine at DIRECTORY (defaults to app name)
|
10
|
+
#
|
11
|
+
# -r, --remote REMOTE # the git remote to create, default "azuki"
|
12
|
+
#
|
13
|
+
#Examples:
|
14
|
+
#
|
15
|
+
# $ azuki git:clone example
|
16
|
+
# Cloning from app 'example'...
|
17
|
+
# Cloning into 'example'...
|
18
|
+
# remote: Counting objects: 42, done.
|
19
|
+
# ...
|
20
|
+
#
|
21
|
+
def clone
|
22
|
+
remote = options[:remote] || "azuki"
|
23
|
+
|
24
|
+
name = options[:app] || shift_argument || error("Usage: azuki git:clone APP [DIRECTORY]")
|
25
|
+
directory = shift_argument
|
26
|
+
validate_arguments!
|
27
|
+
|
28
|
+
git_url = api.get_app(name).body["git_url"]
|
29
|
+
|
30
|
+
puts "Cloning from app '#{name}'..."
|
31
|
+
system "git clone -o #{remote} #{git_url} #{directory}".strip
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_command "clone", "git:clone"
|
35
|
+
|
36
|
+
# git:remote [OPTIONS]
|
37
|
+
#
|
38
|
+
# adds a git remote to an app repo
|
39
|
+
#
|
40
|
+
# if OPTIONS are specified they will be passed to git remote add
|
41
|
+
#
|
42
|
+
# -r, --remote REMOTE # the git remote to create, default "azuki"
|
43
|
+
#
|
44
|
+
#Examples:
|
45
|
+
#
|
46
|
+
# $ azuki git:remote -a example
|
47
|
+
# Git remote azuki added
|
48
|
+
#
|
49
|
+
# $ azuki git:remote -a example
|
50
|
+
# ! Git remote azuki already exists
|
51
|
+
#
|
52
|
+
def remote
|
53
|
+
git_options = args.join(" ")
|
54
|
+
remote = options[:remote] || 'azuki'
|
55
|
+
|
56
|
+
if git('remote').split("\n").include?(remote)
|
57
|
+
error("Git remote #{remote} already exists")
|
58
|
+
else
|
59
|
+
app_data = api.get_app(app).body
|
60
|
+
create_git_remote(remote, app_data['git_url'])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "azuki/command/base"
|
2
|
+
require "azuki/deprecated/help"
|
3
|
+
|
4
|
+
# list commands and display help
|
5
|
+
#
|
6
|
+
class Azuki::Command::Help < Azuki::Command::Base
|
7
|
+
|
8
|
+
PRIMARY_NAMESPACES = %w( auth apps ps run addons config releases domains logs sharing )
|
9
|
+
|
10
|
+
include Azuki::Deprecated::Help
|
11
|
+
|
12
|
+
# help [COMMAND]
|
13
|
+
#
|
14
|
+
# list available commands or display help for a specific command
|
15
|
+
#
|
16
|
+
#Examples:
|
17
|
+
#
|
18
|
+
# $ azuki help
|
19
|
+
# Usage: azuki COMMAND [--app APP] [command-specific-options]
|
20
|
+
#
|
21
|
+
# Primary help topics, type "azuki help TOPIC" for more details:
|
22
|
+
#
|
23
|
+
# addons # manage addon resources
|
24
|
+
# apps # manage apps (create, destroy)
|
25
|
+
# ...
|
26
|
+
#
|
27
|
+
# Additional topics:
|
28
|
+
#
|
29
|
+
# account # manage azuki account options
|
30
|
+
# accounts # manage multiple azuki accounts
|
31
|
+
# ...
|
32
|
+
#
|
33
|
+
# $ azuki help apps:create
|
34
|
+
# Usage: azuki apps:create [NAME]
|
35
|
+
#
|
36
|
+
# create a new app
|
37
|
+
#
|
38
|
+
# --addons ADDONS # a comma-delimited list of addons to install
|
39
|
+
# -b, --buildpack BUILDPACK # a buildpack url to use for this app
|
40
|
+
# -r, --remote REMOTE # the git remote to create, default "azuki"
|
41
|
+
# -s, --stack STACK # the stack on which to create the app
|
42
|
+
#
|
43
|
+
def index
|
44
|
+
if command = args.shift
|
45
|
+
help_for_command(command)
|
46
|
+
else
|
47
|
+
help_for_root
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_command "-h", "help"
|
52
|
+
alias_command "--help", "help"
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def commands_for_namespace(name)
|
57
|
+
Azuki::Command.commands.values.select do |command|
|
58
|
+
command[:namespace] == name && command[:command] != name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def namespaces
|
63
|
+
namespaces = Azuki::Command.namespaces
|
64
|
+
namespaces.delete("app")
|
65
|
+
namespaces
|
66
|
+
end
|
67
|
+
|
68
|
+
def commands
|
69
|
+
Azuki::Command.commands
|
70
|
+
end
|
71
|
+
|
72
|
+
def legacy_help_for_namespace(namespace)
|
73
|
+
instance = Azuki::Command::Help.groups.map do |group|
|
74
|
+
[ group.title, group.select { |c| c.first =~ /^#{namespace}/ }.length ]
|
75
|
+
end.sort_by { |l| l.last }.last
|
76
|
+
return nil unless instance
|
77
|
+
return nil if instance.last.zero?
|
78
|
+
instance.first
|
79
|
+
end
|
80
|
+
|
81
|
+
def legacy_help_for_command(command)
|
82
|
+
Azuki::Command::Help.groups.each do |group|
|
83
|
+
group.each do |cmd, description|
|
84
|
+
return description if cmd.split(" ").first == command
|
85
|
+
end
|
86
|
+
end
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def skip_namespace?(ns)
|
91
|
+
return true if ns[:description] =~ /DEPRECATED:/
|
92
|
+
return true if ns[:description] =~ /HIDDEN:/
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def skip_command?(command)
|
97
|
+
return true if command[:help] =~ /DEPRECATED:/
|
98
|
+
return true if command[:help] =~ /^ HIDDEN:/
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
def primary_namespaces
|
103
|
+
PRIMARY_NAMESPACES.map { |name| namespaces[name] }.compact
|
104
|
+
end
|
105
|
+
|
106
|
+
def additional_namespaces
|
107
|
+
(namespaces.values - primary_namespaces)
|
108
|
+
end
|
109
|
+
|
110
|
+
def summary_for_namespaces(namespaces)
|
111
|
+
size = longest(namespaces.map { |n| n[:name] })
|
112
|
+
namespaces.sort_by {|namespace| namespace[:name]}.each do |namespace|
|
113
|
+
next if skip_namespace?(namespace)
|
114
|
+
name = namespace[:name]
|
115
|
+
namespace[:description] ||= legacy_help_for_namespace(name)
|
116
|
+
puts " %-#{size}s # %s" % [ name, namespace[:description] ]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def help_for_root
|
121
|
+
puts "Usage: azuki COMMAND [--app APP] [command-specific-options]"
|
122
|
+
puts
|
123
|
+
puts "Primary help topics, type \"azuki help TOPIC\" for more details:"
|
124
|
+
puts
|
125
|
+
summary_for_namespaces(primary_namespaces)
|
126
|
+
puts
|
127
|
+
puts "Additional topics:"
|
128
|
+
puts
|
129
|
+
summary_for_namespaces(additional_namespaces)
|
130
|
+
puts
|
131
|
+
end
|
132
|
+
|
133
|
+
def help_for_namespace(name)
|
134
|
+
namespace_commands = commands_for_namespace(name)
|
135
|
+
|
136
|
+
unless namespace_commands.empty?
|
137
|
+
size = longest(namespace_commands.map { |c| c[:banner] })
|
138
|
+
namespace_commands.sort_by { |c| c[:banner].to_s }.each do |command|
|
139
|
+
next if skip_command?(command)
|
140
|
+
command[:summary] ||= legacy_help_for_command(command[:command])
|
141
|
+
puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def help_for_command(name)
|
147
|
+
if command_alias = Azuki::Command.command_aliases[name]
|
148
|
+
display("Alias: #{name} redirects to #{command_alias}")
|
149
|
+
name = command_alias
|
150
|
+
end
|
151
|
+
if command = commands[name]
|
152
|
+
puts "Usage: azuki #{command[:banner]}"
|
153
|
+
|
154
|
+
if command[:help].strip.length > 0
|
155
|
+
help = command[:help].split("\n").reject do |line|
|
156
|
+
line =~ /HIDDEN/
|
157
|
+
end
|
158
|
+
puts help[1..-1].join("\n")
|
159
|
+
else
|
160
|
+
puts
|
161
|
+
puts " " + legacy_help_for_command(name).to_s
|
162
|
+
end
|
163
|
+
puts
|
164
|
+
end
|
165
|
+
|
166
|
+
namespace_commands = commands_for_namespace(name).reject do |command|
|
167
|
+
command[:help] =~ /DEPRECATED/
|
168
|
+
end
|
169
|
+
|
170
|
+
if !namespace_commands.empty?
|
171
|
+
puts "Additional commands, type \"azuki help COMMAND\" for more details:"
|
172
|
+
puts
|
173
|
+
help_for_namespace(name)
|
174
|
+
puts
|
175
|
+
elsif command.nil?
|
176
|
+
error "#{name} is not a azuki command. See `azuki help`."
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "azuki/command/base"
|
2
|
+
|
3
|
+
module Azuki::Command
|
4
|
+
|
5
|
+
# manage authentication keys
|
6
|
+
#
|
7
|
+
class Keys < Base
|
8
|
+
|
9
|
+
# keys
|
10
|
+
#
|
11
|
+
# display keys for the current user
|
12
|
+
#
|
13
|
+
# -l, --long # display extended information for each key
|
14
|
+
#
|
15
|
+
#Examples:
|
16
|
+
#
|
17
|
+
# $ azuki keys
|
18
|
+
# === email@example.com Keys
|
19
|
+
# ssh-rsa ABCDEFGHIJK...OPQRSTUV== email@example.com
|
20
|
+
#
|
21
|
+
# $ azuki keys --long
|
22
|
+
# === email@example.com Keys
|
23
|
+
# ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAp9AJD5QABmOcrkHm6SINuQkDefaR0MUrfgZ1Pxir3a4fM1fwa00dsUwbUaRuR7FEFD8n1E9WwDf8SwQTHtyZsJg09G9myNqUzkYXCmydN7oGr5IdVhRyv5ixcdiE0hj7dRnOJg2poSQ3Qi+Ka8SVJzF7nIw1YhuicHPSbNIFKi5s0D5a+nZb/E6MNGvhxoFCQX2IcNxaJMqhzy1ESwlixz45aT72mXYq0LIxTTpoTqma1HuKdRY8HxoREiivjmMQulYP+CxXFcMyV9kxTKIUZ/FXqlC6G5vSm3J4YScSatPOj9ID5HowpdlIx8F6y4p1/28r2tTl4CY40FFyoke4MQ== email@example.com
|
24
|
+
#
|
25
|
+
def index
|
26
|
+
validate_arguments!
|
27
|
+
keys = api.get_keys.body
|
28
|
+
if keys.length > 0
|
29
|
+
styled_header("#{Azuki::Auth.user} Keys")
|
30
|
+
keys = if options[:long]
|
31
|
+
keys.map {|key| key["contents"].strip}
|
32
|
+
else
|
33
|
+
keys.map {|key| format_key_for_display(key["contents"])}
|
34
|
+
end
|
35
|
+
styled_array(keys)
|
36
|
+
else
|
37
|
+
display("You have no keys.")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# keys:add [KEY]
|
42
|
+
#
|
43
|
+
# add a key for the current user
|
44
|
+
#
|
45
|
+
# if no KEY is specified, will try to find ~/.ssh/id_[rd]sa.pub
|
46
|
+
#
|
47
|
+
#Examples:
|
48
|
+
#
|
49
|
+
# $ azuki keys:add
|
50
|
+
# Could not find an existing public key.
|
51
|
+
# Would you like to generate one? [Yn] y
|
52
|
+
# Generating new SSH public key.
|
53
|
+
# Uploading SSH public key /.ssh/id_rsa.pub... done
|
54
|
+
#
|
55
|
+
# $ azuki keys:add /my/key.pub
|
56
|
+
# Uploading SSH public key /my/key.pub... done
|
57
|
+
#
|
58
|
+
def add
|
59
|
+
keyfile = shift_argument
|
60
|
+
validate_arguments!
|
61
|
+
|
62
|
+
if keyfile
|
63
|
+
Azuki::Auth.associate_key(keyfile)
|
64
|
+
else
|
65
|
+
# make sure we have credentials
|
66
|
+
Azuki::Auth.get_credentials
|
67
|
+
Azuki::Auth.associate_or_generate_ssh_key
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# keys:remove KEY
|
72
|
+
#
|
73
|
+
# remove a key from the current user
|
74
|
+
#
|
75
|
+
#Examples:
|
76
|
+
#
|
77
|
+
# $ azuki keys:remove email@example.com
|
78
|
+
# Removing email@example.com SSH key... done
|
79
|
+
#
|
80
|
+
def remove
|
81
|
+
key = shift_argument
|
82
|
+
if key.nil? || key.empty?
|
83
|
+
error("Usage: azuki keys:remove KEY\nMust specify KEY to remove.")
|
84
|
+
end
|
85
|
+
validate_arguments!
|
86
|
+
|
87
|
+
action("Removing #{key} SSH key") do
|
88
|
+
api.delete_key(key)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# keys:clear
|
93
|
+
#
|
94
|
+
# remove all authentication keys from the current user
|
95
|
+
#
|
96
|
+
#Examples:
|
97
|
+
#
|
98
|
+
# $ azuki keys:clear
|
99
|
+
# Removing all SSH keys... done
|
100
|
+
#
|
101
|
+
def clear
|
102
|
+
validate_arguments!
|
103
|
+
|
104
|
+
action("Removing all SSH keys") do
|
105
|
+
api.delete_keys
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
def format_key_for_display(key)
|
111
|
+
type, hex, local = key.strip.split(/\s/)
|
112
|
+
[type, hex[0,10] + '...' + hex[-10,10], local].join(' ')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|