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,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
|