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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/azuki +17 -0
  4. data/data/cacert.pem +3988 -0
  5. data/lib/azuki.rb +17 -0
  6. data/lib/azuki/auth.rb +339 -0
  7. data/lib/azuki/cli.rb +38 -0
  8. data/lib/azuki/client.rb +764 -0
  9. data/lib/azuki/client/azuki_postgresql.rb +141 -0
  10. data/lib/azuki/client/cisaurus.rb +26 -0
  11. data/lib/azuki/client/pgbackups.rb +113 -0
  12. data/lib/azuki/client/rendezvous.rb +108 -0
  13. data/lib/azuki/client/ssl_endpoint.rb +25 -0
  14. data/lib/azuki/command.rb +294 -0
  15. data/lib/azuki/command/account.rb +23 -0
  16. data/lib/azuki/command/accounts.rb +34 -0
  17. data/lib/azuki/command/addons.rb +305 -0
  18. data/lib/azuki/command/apps.rb +393 -0
  19. data/lib/azuki/command/auth.rb +86 -0
  20. data/lib/azuki/command/base.rb +230 -0
  21. data/lib/azuki/command/certs.rb +209 -0
  22. data/lib/azuki/command/config.rb +137 -0
  23. data/lib/azuki/command/db.rb +218 -0
  24. data/lib/azuki/command/domains.rb +85 -0
  25. data/lib/azuki/command/drains.rb +46 -0
  26. data/lib/azuki/command/fork.rb +164 -0
  27. data/lib/azuki/command/git.rb +64 -0
  28. data/lib/azuki/command/help.rb +179 -0
  29. data/lib/azuki/command/keys.rb +115 -0
  30. data/lib/azuki/command/labs.rb +147 -0
  31. data/lib/azuki/command/logs.rb +45 -0
  32. data/lib/azuki/command/maintenance.rb +61 -0
  33. data/lib/azuki/command/pg.rb +269 -0
  34. data/lib/azuki/command/pgbackups.rb +329 -0
  35. data/lib/azuki/command/plugins.rb +110 -0
  36. data/lib/azuki/command/ps.rb +232 -0
  37. data/lib/azuki/command/regions.rb +22 -0
  38. data/lib/azuki/command/releases.rb +124 -0
  39. data/lib/azuki/command/run.rb +180 -0
  40. data/lib/azuki/command/sharing.rb +89 -0
  41. data/lib/azuki/command/ssl.rb +43 -0
  42. data/lib/azuki/command/stack.rb +62 -0
  43. data/lib/azuki/command/status.rb +51 -0
  44. data/lib/azuki/command/update.rb +47 -0
  45. data/lib/azuki/command/version.rb +23 -0
  46. data/lib/azuki/deprecated.rb +5 -0
  47. data/lib/azuki/deprecated/help.rb +38 -0
  48. data/lib/azuki/distribution.rb +9 -0
  49. data/lib/azuki/excon.rb +9 -0
  50. data/lib/azuki/helpers.rb +517 -0
  51. data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
  52. data/lib/azuki/helpers/log_displayer.rb +70 -0
  53. data/lib/azuki/plugin.rb +163 -0
  54. data/lib/azuki/updater.rb +171 -0
  55. data/lib/azuki/version.rb +3 -0
  56. data/lib/vendor/azuki/okjson.rb +598 -0
  57. data/spec/azuki/auth_spec.rb +256 -0
  58. data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
  59. data/spec/azuki/client/pgbackups_spec.rb +43 -0
  60. data/spec/azuki/client/rendezvous_spec.rb +62 -0
  61. data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
  62. data/spec/azuki/client_spec.rb +564 -0
  63. data/spec/azuki/command/addons_spec.rb +601 -0
  64. data/spec/azuki/command/apps_spec.rb +351 -0
  65. data/spec/azuki/command/auth_spec.rb +38 -0
  66. data/spec/azuki/command/base_spec.rb +109 -0
  67. data/spec/azuki/command/certs_spec.rb +178 -0
  68. data/spec/azuki/command/config_spec.rb +144 -0
  69. data/spec/azuki/command/db_spec.rb +110 -0
  70. data/spec/azuki/command/domains_spec.rb +87 -0
  71. data/spec/azuki/command/drains_spec.rb +34 -0
  72. data/spec/azuki/command/fork_spec.rb +56 -0
  73. data/spec/azuki/command/git_spec.rb +144 -0
  74. data/spec/azuki/command/help_spec.rb +93 -0
  75. data/spec/azuki/command/keys_spec.rb +120 -0
  76. data/spec/azuki/command/labs_spec.rb +100 -0
  77. data/spec/azuki/command/logs_spec.rb +60 -0
  78. data/spec/azuki/command/maintenance_spec.rb +51 -0
  79. data/spec/azuki/command/pg_spec.rb +236 -0
  80. data/spec/azuki/command/pgbackups_spec.rb +307 -0
  81. data/spec/azuki/command/plugins_spec.rb +104 -0
  82. data/spec/azuki/command/ps_spec.rb +195 -0
  83. data/spec/azuki/command/releases_spec.rb +130 -0
  84. data/spec/azuki/command/run_spec.rb +83 -0
  85. data/spec/azuki/command/sharing_spec.rb +59 -0
  86. data/spec/azuki/command/stack_spec.rb +46 -0
  87. data/spec/azuki/command/status_spec.rb +48 -0
  88. data/spec/azuki/command/version_spec.rb +16 -0
  89. data/spec/azuki/command_spec.rb +211 -0
  90. data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
  91. data/spec/azuki/helpers_spec.rb +48 -0
  92. data/spec/azuki/plugin_spec.rb +172 -0
  93. data/spec/azuki/updater_spec.rb +44 -0
  94. data/spec/helper/legacy_help.rb +16 -0
  95. data/spec/spec.opts +1 -0
  96. data/spec/spec_helper.rb +224 -0
  97. data/spec/support/display_message_matcher.rb +49 -0
  98. data/spec/support/openssl_mock_helper.rb +8 -0
  99. 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