awskeyring 1.8.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 821682d536efc3d5785f862fb8e1a53fda4107eb18020bc6b3796a1d3654ee7d
4
- data.tar.gz: 79bda10be2bfdabb9142e9798cfcdd47a20eba1b1cc4186526ca2f2860253514
3
+ metadata.gz: c40aae37a636dc9f09930e87a9c592d85b19889d96f0550875954d460bc7e9fe
4
+ data.tar.gz: 6d18d87a6301f6fe6f9cd8976da5bc80dbaf3cffe8570997b341d99426f56dfc
5
5
  SHA512:
6
- metadata.gz: a84c3ef0c53bdf13a5740e1231494700bd40b91acda358c27c99de2eb8170c8073d98536c3fdfd6973a8f80cf2c3b362b101de83a5415eebada7b3a7f391053c
7
- data.tar.gz: 728d2d23bd55a416d39bb94dbc5f7445941a15a64a18ef6f2a070ac6a21a6c9d4b14f3e0f210e6b85a964c08070c5435fc301665d4d6fd4989fe411dfed29efd
6
+ metadata.gz: 621e70e88839085be03c4c0cc30c8fe1e338392f46eca028c5a4fd244eac3d9f7c8cf68458730a154837408c9bd5cdc3d1da8625989c1b7de69bd007b3d91ecc
7
+ data.tar.gz: bac9058e92069e9206a070eb6fde527aeb1254e9135742cbbc7bd23a4666b27c6ee01da09f72083acff8e6b7fb0fc995e2553d2ebee196e05fc01e5f81419c4f
data/README.md CHANGED
@@ -61,25 +61,25 @@ more details on this config option.
61
61
 
62
62
  The CLI is using [Thor](http://whatisthor.com) with help provided interactively.
63
63
 
64
- Commands:
65
- awskeyring --version, -v # Prints the version
66
- awskeyring add ACCOUNT # Adds an ACCOUNT to the keyring
67
- awskeyring add-role ROLE # Adds a ROLE to the keyring
68
- awskeyring console ACCOUNT # Open the AWS Console for the ACCOUNT
69
- awskeyring env ACCOUNT # Outputs bourne shell environment exports for an ACCOUNT
70
- awskeyring exec ACCOUNT command... # Execute a COMMAND with the environment set for an ACCOUNT
71
- awskeyring help [COMMAND] # Describe available commands or one specific command
72
- awskeyring import ACCOUNT # Import an ACCOUNT to the keyring from ~/.aws/credentials
73
- awskeyring initialise # Initialises a new KEYCHAIN
74
- awskeyring json ACCOUNT # Outputs AWS CLI compatible JSON for an ACCOUNT
75
- awskeyring list # Prints a list of accounts in the keyring
76
- awskeyring list-role # Prints a list of roles in the keyring
77
- awskeyring remove ACCOUNT # Removes an ACCOUNT from the keyring
78
- awskeyring remove-role ROLE # Removes a ROLE from the keyring
79
- awskeyring remove-token ACCOUNT # Removes a token for ACCOUNT from the keyring
80
- awskeyring rotate ACCOUNT # Rotate access keys for an ACCOUNT
81
- awskeyring token ACCOUNT [ROLE] [MFA] # Create an STS Token from a ROLE or an MFA code
82
- awskeyring update ACCOUNT # Updates an ACCOUNT in the keyring
64
+ Awskeyring commands:
65
+ awskeyring --version, -v # Prints the version
66
+ awskeyring add ACCOUNT # Adds an ACCOUNT to the keyring
67
+ awskeyring add-role ROLE # Adds a ROLE to the keyring
68
+ awskeyring console ACCOUNT # Open the AWS Console for the ACCOUNT
69
+ awskeyring env ACCOUNT # Outputs bourne shell environment exports for an ACCOUNT
70
+ awskeyring exec ACCOUNT command... # Execute a COMMAND with the environment set for an ACCOUNT
71
+ awskeyring help [COMMAND] # Describe available commands or one specific command
72
+ awskeyring import ACCOUNT # Import an ACCOUNT to the keyring from ~/.aws/credentials
73
+ awskeyring initialise # Initialises a new KEYCHAIN
74
+ awskeyring json ACCOUNT # Outputs AWS CLI compatible JSON for an ACCOUNT
75
+ awskeyring list # Prints a list of accounts in the keyring
76
+ awskeyring list-role # Prints a list of roles in the keyring
77
+ awskeyring remove ACCOUNT # Removes an ACCOUNT from the keyring
78
+ awskeyring remove-role ROLE # Removes a ROLE from the keyring
79
+ awskeyring remove-token ACCOUNT # Removes a token for ACCOUNT from the keyring
80
+ awskeyring rotate ACCOUNT # Rotate access keys for an ACCOUNT
81
+ awskeyring token ACCOUNT [ROLE] [CODE] # Create an STS Token from a ROLE or an mfa CODE
82
+ awskeyring update ACCOUNT # Updates an ACCOUNT in the keyring
83
83
 
84
84
  and autocomplete that can be installed with:
85
85
 
@@ -91,7 +91,7 @@ There are also short forms of most commands if you prefer:
91
91
 
92
92
  To set your environment easily the following bash function helps:
93
93
 
94
- awsenv() { eval "$(awskeyring env $@)"; }
94
+ awsenv() { eval "$(awskeyring env ${@:-$AWS_ACCOUNT_NAME})"; }
95
95
 
96
96
  ## Development
97
97
 
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
4
  require 'rubocop/rake_task'
6
5
  require 'ronn'
7
6
  require 'github_changelog_generator/task'
@@ -49,13 +48,14 @@ end
49
48
  desc 'generate manpage'
50
49
  task :ronn do
51
50
  puts 'Running Ronn...'
52
- roff_text = Ronn::Document.new('man/awskeyring.5.ronn').to_roff
53
- File.write('man/awskeyring.5', roff_text)
51
+ doc = Ronn::Document.new('man/awskeyring.5.ronn')
52
+ doc.date = Time.parse(`git show -s --format=%ad --date=short`)
53
+ File.write('man/awskeyring.5', doc.to_roff)
54
54
  puts "done\n\n"
55
55
  end
56
56
 
57
57
  YARD::Rake::YardocTask.new do |t|
58
- t.options = ['--fail-on-warning', '--no-progress']
58
+ t.options = ['--fail-on-warning', '--no-progress', '--files', '*.md']
59
59
  t.stats_options = ['--list-undoc']
60
60
  end
61
61
 
data/i18n/en.yml CHANGED
@@ -1,41 +1,24 @@
1
1
  ---
2
2
  en:
3
- __version:
4
- desc: Prints the version
5
- add:
6
- desc: Adds an ACCOUNT to the keyring
7
- add_role:
8
- desc: Adds a ROLE to the keyring
9
- awskeyring:
10
- desc: Autocompletion for bourne shells
11
- console:
12
- desc: Open the AWS Console for the ACCOUNT
13
- env:
14
- desc: Outputs bourne shell environment exports for an ACCOUNT
15
- exec:
16
- desc: Execute a COMMAND with the environment set for an ACCOUNT
17
- import:
18
- desc: Import an ACCOUNT to the keyring from ~/.aws/credentials
19
- initialise:
20
- desc: Initialises a new KEYCHAIN
21
- json:
22
- desc: Outputs AWS CLI compatible JSON for an ACCOUNT
23
- list:
24
- desc: Prints a list of accounts in the keyring
25
- list_role:
26
- desc: Prints a list of roles in the keyring
27
- remove:
28
- desc: Removes an ACCOUNT from the keyring
29
- remove_role:
30
- desc: Removes a ROLE from the keyring
31
- remove_token:
32
- desc: Removes a token for ACCOUNT from the keyring
33
- rotate:
34
- desc: Rotate access keys for an ACCOUNT
35
- token:
36
- desc: Create an STS Token from a ROLE or an MFA code
37
- update:
38
- desc: Updates an ACCOUNT in the keyring
3
+ __version_desc: Prints the version
4
+ add_desc: Adds an ACCOUNT to the keyring
5
+ add_role_desc: Adds a ROLE to the keyring
6
+ awskeyring_desc: Autocompletion for bourne shells
7
+ console_desc: Open the AWS Console for the ACCOUNT
8
+ default_desc: Run default help or initialise if needed.
9
+ env_desc: Outputs bourne shell environment exports for an ACCOUNT
10
+ exec_desc: Execute a COMMAND with the environment set for an ACCOUNT
11
+ import_desc: Import an ACCOUNT to the keyring from ~/.aws/credentials
12
+ initialise_desc: Initialises a new KEYCHAIN
13
+ json_desc: Outputs AWS CLI compatible JSON for an ACCOUNT
14
+ list_desc: Prints a list of accounts in the keyring
15
+ list_role_desc: Prints a list of roles in the keyring
16
+ remove_desc: Removes an ACCOUNT from the keyring
17
+ remove_role_desc: Removes a ROLE from the keyring
18
+ remove_token_desc: Removes a token for ACCOUNT from the keyring
19
+ rotate_desc: Rotate access keys for an ACCOUNT
20
+ token_desc: Create an STS Token from a ROLE or an mfa CODE
21
+ update_desc: Updates an ACCOUNT in the keyring
39
22
  method_option:
40
23
  arn: 'AWS role arn.'
41
24
  code: 'Virtual mfa CODE.'
@@ -50,7 +33,6 @@ en:
50
33
  noremote: 'Do not validate with remote api.'
51
34
  path: 'The service PATH to open.'
52
35
  browser: 'Specify an alternative browser.'
53
- role: 'The ROLE to assume.'
54
36
  secret: 'AWS account secret.'
55
37
  unset: 'Unset environment variables.'
56
38
  message:
data/lib/awskeyring.rb CHANGED
@@ -97,6 +97,17 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
97
97
  all_items.where(account: account).first
98
98
  end
99
99
 
100
+ # return item that matches a prefix if only one.
101
+ def self.solo_select(list, prefix)
102
+ return prefix if list.include?(prefix)
103
+
104
+ list.select! { |elem| elem.start_with?(prefix) }
105
+
106
+ return list.first if list.length == 1
107
+
108
+ nil
109
+ end
110
+
100
111
  # Add an account item
101
112
  #
102
113
  # @param [String] account The account name to create
@@ -317,7 +328,7 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
317
328
  # @param [String] account_name the associated account name.
318
329
  def self.account_exists(account_name)
319
330
  Awskeyring::Validate.account_name(account_name)
320
- raise 'Account does not exist' unless list_account_names.include?(account_name)
331
+ raise 'Account does not exist' unless (account_name = solo_select(list_account_names, account_name))
321
332
 
322
333
  account_name
323
334
  end
@@ -347,7 +358,7 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
347
358
  # @param [String] role_name the associated role name.
348
359
  def self.role_exists(role_name)
349
360
  Awskeyring::Validate.role_name(role_name)
350
- raise 'Role does not exist' unless list_role_names.include?(role_name)
361
+ raise 'Role does not exist' unless (role_name = solo_select(list_role_names, role_name))
351
362
 
352
363
  role_name
353
364
  end
@@ -367,7 +378,7 @@ module Awskeyring # rubocop:disable Metrics/ModuleLength
367
378
  # @param [String] token_name the associated account name.
368
379
  def self.token_exists(token_name)
369
380
  Awskeyring::Validate.account_name(token_name)
370
- raise 'Token does not exist' unless list_token_names.include?(token_name)
381
+ raise 'Token does not exist' unless (token_name = solo_select(list_token_names, token_name))
371
382
 
372
383
  token_name
373
384
  end
@@ -209,7 +209,7 @@ module Awskeyring
209
209
  # Get the signin token param
210
210
  private_class_method def self.token_param(session_json:)
211
211
  get_signin_token_url = AWS_SIGNIN_URL + '?Action=getSigninToken' \
212
- '&Session=' + CGI.escape(session_json)
212
+ '&Session=' + CGI.escape(session_json)
213
213
 
214
214
  uri = URI(get_signin_token_url)
215
215
  request = Net::HTTP.new(uri.host, uri.port)
@@ -6,7 +6,7 @@ require 'json'
6
6
  # Version const and query of latest.
7
7
  module Awskeyring
8
8
  # The Gem's version number
9
- VERSION = '1.8.2'
9
+ VERSION = '1.9.0'
10
10
  # The Gem's homepage
11
11
  HOMEPAGE = 'https://github.com/servian/awskeyring'
12
12
 
@@ -11,29 +11,36 @@ require 'awskeyring/version'
11
11
 
12
12
  # AWSkeyring command line interface.
13
13
  class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
14
+ package_name 'Awskeyring'
14
15
  I18n.load_path = Dir.glob(File.join(File.realpath(__dir__), '..', 'i18n', '*.{yml,yaml}'))
15
16
  I18n.backend.load_translations
16
17
 
17
18
  map %w[--version -v] => :__version
18
- map %w[--help -h] => :help
19
- map 'init' => :initialise
20
19
  map 'adr' => :add_role
21
- map 'con' => :console
20
+ map 'assume-role' => :token
22
21
  map 'ls' => :list
23
22
  map 'lsr' => :list_role
24
23
  map 'rm' => :remove
25
24
  map 'rmr' => :remove_role
26
25
  map 'rmt' => :remove_token
27
- map 'rot' => :rotate
28
- map 'tok' => :token
29
- map 'up' => :update
26
+ default_command :default
30
27
 
31
28
  # default to returning an error on failure.
32
29
  def self.exit_on_failure?
33
30
  true
34
31
  end
35
32
 
36
- desc '--version, -v', I18n.t('__version.desc')
33
+ desc 'default', I18n.t('default_desc'), hide: true
34
+ # default command to run
35
+ def default
36
+ if Awskeyring.prefs.empty?
37
+ invoke :initialise
38
+ else
39
+ invoke :help
40
+ end
41
+ end
42
+
43
+ desc '--version, -v', I18n.t('__version_desc')
37
44
  method_option 'no-remote', type: :boolean, aliases: '-r', desc: I18n.t('method_option.noremote'), default: false
38
45
  # print the version number
39
46
  def __version
@@ -44,7 +51,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
44
51
  puts "Homepage #{Awskeyring::HOMEPAGE}"
45
52
  end
46
53
 
47
- desc 'initialise', I18n.t('initialise.desc')
54
+ desc 'initialise', I18n.t('initialise_desc')
48
55
  method_option :keychain, type: :string, aliases: '-n', desc: I18n.t('method_option.keychain')
49
56
  # initialise the keychain
50
57
  def initialise
@@ -69,7 +76,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
69
76
  puts I18n.t('message.addkeychain', keychain: keychain, exec_name: exec_name)
70
77
  end
71
78
 
72
- desc 'list', I18n.t('list.desc')
79
+ desc 'list', I18n.t('list_desc')
73
80
  # list the accounts
74
81
  def list
75
82
  if Awskeyring.list_account_names.empty?
@@ -79,9 +86,8 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
79
86
  puts Awskeyring.list_account_names.join("\n")
80
87
  end
81
88
 
82
- map 'list-role' => :list_role
83
- desc 'list-role', I18n.t('list_role.desc')
84
- method_option 'detail', type: :boolean, aliases: '-d', desc: I18n.t('method_option.detail'), default: false
89
+ desc 'list-role', I18n.t('list_role_desc')
90
+ method_option :detail, type: :boolean, aliases: '-d', desc: I18n.t('method_option.detail'), default: false
85
91
  # List roles
86
92
  def list_role
87
93
  if Awskeyring.list_role_names.empty?
@@ -95,9 +101,9 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
95
101
  end
96
102
  end
97
103
 
98
- desc 'env ACCOUNT', I18n.t('env.desc')
104
+ desc 'env ACCOUNT', I18n.t('env_desc')
99
105
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
100
- method_option 'unset', type: :boolean, aliases: '-u', desc: I18n.t('method_option.unset'), default: false
106
+ method_option :unset, type: :boolean, aliases: '-u', desc: I18n.t('method_option.unset'), default: false
101
107
  # Print Env vars
102
108
  def env(account = nil)
103
109
  if options[:unset]
@@ -113,7 +119,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
113
119
  end
114
120
  end
115
121
 
116
- desc 'json ACCOUNT', I18n.t('json.desc')
122
+ desc 'json ACCOUNT', I18n.t('json_desc')
117
123
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
118
124
  # Print JSON for use with credential_process
119
125
  def json(account)
@@ -130,7 +136,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
130
136
  )
131
137
  end
132
138
 
133
- desc 'import ACCOUNT', I18n.t('import.desc')
139
+ desc 'import ACCOUNT', I18n.t('import_desc')
134
140
  method_option 'no-remote', type: :boolean, aliases: '-r', desc: I18n.t('method_option.noremote'), default: false
135
141
  # Import an Account
136
142
  def import(account = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@@ -166,7 +172,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
166
172
  end
167
173
  end
168
174
 
169
- desc 'exec ACCOUNT command...', I18n.t('exec.desc')
175
+ desc 'exec ACCOUNT command...', I18n.t('exec_desc')
170
176
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
171
177
  method_option 'no-bundle', type: :boolean, aliases: '-b', desc: I18n.t('method_option.nobundle'), default: false
172
178
  # execute an external command with env set
@@ -188,7 +194,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
188
194
  end
189
195
  end
190
196
 
191
- desc 'add ACCOUNT', I18n.t('add.desc')
197
+ desc 'add ACCOUNT', I18n.t('add_desc')
192
198
  method_option :key, type: :string, aliases: '-k', desc: I18n.t('method_option.key')
193
199
  method_option :secret, type: :string, aliases: '-s', desc: I18n.t('method_option.secret')
194
200
  method_option :mfa, type: :string, aliases: '-m', desc: I18n.t('method_option.mfa')
@@ -219,7 +225,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
219
225
  puts I18n.t('message.addaccount', account: account)
220
226
  end
221
227
 
222
- desc 'update ACCOUNT', I18n.t('update.desc')
228
+ desc 'update ACCOUNT', I18n.t('update_desc')
223
229
  method_option :key, type: :string, aliases: '-k', desc: I18n.t('method_option.key')
224
230
  method_option :secret, type: :string, aliases: '-s', desc: I18n.t('method_option.secret')
225
231
  method_option 'no-remote', type: :boolean, aliases: '-r', desc: I18n.t('method_option.noremote'), default: false
@@ -246,8 +252,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
246
252
  puts I18n.t('message.upaccount', account: account)
247
253
  end
248
254
 
249
- map 'add-role' => :add_role
250
- desc 'add-role ROLE', I18n.t('add_role.desc')
255
+ desc 'add-role ROLE', I18n.t('add_role_desc')
251
256
  method_option :arn, type: :string, aliases: '-a', desc: I18n.t('method_option.arn')
252
257
  # Add a role
253
258
  def add_role(role = nil)
@@ -267,7 +272,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
267
272
  puts I18n.t('message.addrole', role: role)
268
273
  end
269
274
 
270
- desc 'remove ACCOUNT', I18n.t('remove.desc')
275
+ desc 'remove ACCOUNT', I18n.t('remove_desc')
271
276
  # Remove an account
272
277
  def remove(account = nil)
273
278
  account = ask_check(
@@ -277,18 +282,17 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
277
282
  Awskeyring.delete_account(account: account, message: I18n.t('message.delaccount', account: account))
278
283
  end
279
284
 
280
- desc 'remove-token ACCOUNT', I18n.t('remove_token.desc')
285
+ desc 'remove-token ACCOUNT', I18n.t('remove_token_desc')
281
286
  # remove a session token
282
- def remove_token(account = nil)
283
- account = ask_check(
284
- existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:token_exists),
287
+ def remove_token(token = nil)
288
+ token = ask_check(
289
+ existing: token, message: I18n.t('message.account'), validator: Awskeyring.method(:token_exists),
285
290
  limited_to: Awskeyring.list_token_names
286
291
  )
287
- Awskeyring.delete_token(account: account, message: I18n.t('message.deltoken', account: account))
292
+ Awskeyring.delete_token(account: token, message: I18n.t('message.deltoken', account: token))
288
293
  end
289
294
 
290
- map 'remove-role' => :remove_role
291
- desc 'remove-role ROLE', I18n.t('remove_role.desc')
295
+ desc 'remove-role ROLE', I18n.t('remove_role_desc')
292
296
  # remove a role
293
297
  def remove_role(role = nil)
294
298
  role = ask_check(
@@ -298,7 +302,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
298
302
  Awskeyring.delete_role(role_name: role, message: I18n.t('message.delrole', role: role))
299
303
  end
300
304
 
301
- desc 'rotate ACCOUNT', I18n.t('rotate.desc')
305
+ desc 'rotate ACCOUNT', I18n.t('rotate_desc')
302
306
  # rotate Account keys
303
307
  def rotate(account = nil) # rubocop:disable Metrics/MethodLength
304
308
  account = ask_check(
@@ -330,8 +334,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
330
334
  puts I18n.t('message.upaccount', account: account)
331
335
  end
332
336
 
333
- desc 'token ACCOUNT [ROLE] [MFA]', I18n.t('token.desc')
334
- method_option :role, type: :string, aliases: '-r', desc: I18n.t('method_option.role')
337
+ desc 'token ACCOUNT [ROLE] [CODE]', I18n.t('token_desc')
335
338
  method_option :code, type: :string, aliases: '-c', desc: I18n.t('method_option.code')
336
339
  method_option :duration, type: :string, aliases: '-d', desc: I18n.t('method_option.duration')
337
340
  # generate a sessiopn token
@@ -342,7 +345,6 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
342
345
  validator: Awskeyring.method(:account_exists),
343
346
  limited_to: Awskeyring.list_account_names
344
347
  )
345
- role ||= options[:role]
346
348
  if role
347
349
  role = ask_check(
348
350
  existing: role, message: I18n.t('message.role'), validator: Awskeyring.method(:role_exists),
@@ -385,7 +387,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
385
387
  puts I18n.t('message.addtoken', account: account, time: Time.at(new_creds[:expiry].to_i))
386
388
  end
387
389
 
388
- desc 'console ACCOUNT', I18n.t('console.desc')
390
+ desc 'console ACCOUNT', I18n.t('console_desc')
389
391
  method_option :path, type: :string, aliases: '-p', desc: I18n.t('method_option.path')
390
392
  method_option :browser, type: :string, aliases: '-b', desc: I18n.t('method_option.browser')
391
393
  method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false
@@ -424,110 +426,140 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
424
426
  end
425
427
  end
426
428
 
427
- desc 'awskeyring CURR PREV', I18n.t('awskeyring.desc'), hide: true
429
+ desc "#{File.basename($PROGRAM_NAME)} CURR PREV", I18n.t('awskeyring_desc'), hide: true
428
430
  map File.basename($PROGRAM_NAME) => :autocomplete
429
431
  # autocomplete
430
- def autocomplete(curr, prev)
432
+ def autocomplete(curr, prev = nil)
433
+ curr, prev = fix_args(curr, prev)
431
434
  comp_line = ENV['COMP_LINE']
432
- unless comp_line
435
+ comp_point_str = ENV['COMP_POINT']
436
+ unless comp_line && comp_point_str
433
437
  exec_name = File.basename($PROGRAM_NAME)
434
438
  warn I18n.t('message.awskeyring', path: $PROGRAM_NAME, bin: exec_name)
435
439
  exit 1
436
440
  end
437
441
 
438
- curr, comp_len, sub_cmd = comp_type(comp_line: comp_line, curr: curr, prev: prev)
439
- print_auto_resp(curr, comp_len, sub_cmd)
442
+ comp_lines = comp_line[0..(comp_point_str.to_i)].split
443
+
444
+ comp_type, sub_cmd = comp_type(comp_lines: comp_lines, prev: prev)
445
+ list = fetch_auto_resp(comp_type, sub_cmd)
446
+ puts list.select { |elem| elem.start_with?(curr) }.sort!.join("\n")
440
447
  end
441
448
 
442
449
  private
443
450
 
444
- def age_check_and_get(account:, no_token:)
445
- cred = Awskeyring.get_valid_creds(account: account, no_token: no_token)
446
-
447
- maxage = Awskeyring.key_age
448
- age = (Time.new - cred[:updated]).div Awskeyring::Awsapi::ONE_DAY
449
- warn I18n.t('message.age_check', account: account, age: age) unless age < maxage
450
-
451
- cred
451
+ # when a double dash is parsed it is dropped from the args but we need it
452
+ def fix_args(curr, prev)
453
+ if prev.nil?
454
+ [ARGV[1], ARGV[2]]
455
+ else
456
+ [curr, prev]
457
+ end
452
458
  end
453
459
 
454
- def comp_type(comp_line:, curr:, prev:)
455
- comp_len = comp_line.split.index(prev)
456
- sub_cmd = sub_command(comp_line.split)
457
-
458
- comp_len = 3 if curr.start_with?('-')
460
+ # determine the type of completion needed
461
+ def comp_type(comp_lines:, prev:)
462
+ sub_cmd = sub_command(comp_lines)
463
+ comp_idx = comp_lines.rindex(prev)
459
464
 
460
465
  case prev
461
- when 'help', File.basename($PROGRAM_NAME)
462
- comp_len = 0
463
- when 'remove-role', '-r', 'rmr'
464
- comp_len = 2
465
466
  when '--path', '-p'
466
- comp_len = 40
467
- when 'remove-token', 'rmt'
468
- comp_len = 50
467
+ comp_type = :path_type
469
468
  when '--browser', '-b'
470
- comp_len = 60
469
+ comp_type = :browser_type
470
+ else
471
+ comp_type = :command
472
+ comp_type = param_type(comp_idx, sub_cmd) unless sub_cmd.empty?
471
473
  end
472
474
 
473
- [curr, comp_len, sub_cmd]
475
+ [comp_type, sub_cmd]
474
476
  end
475
477
 
478
+ # check params for named params or fall back to flags
479
+ def param_type(comp_idx, sub_cmd)
480
+ types = %i[opt req]
481
+ param_list = method(sub_cmd).parameters.select { |elem| types.include? elem[0] }
482
+ if comp_idx.zero?
483
+ :command
484
+ elsif comp_idx > param_list.length
485
+ :flag
486
+ else
487
+ param_list[comp_idx - 1][1]
488
+ end
489
+ end
490
+
491
+ # catch the command from prefixes and aliases
476
492
  def sub_command(comp_lines)
477
- return nil if comp_lines.nil? || comp_lines.length < 2
493
+ return '' if comp_lines.length < 2
478
494
 
479
495
  sub_cmd = comp_lines[1]
480
496
 
481
- return sub_cmd if self.class.all_commands.keys.index(sub_cmd)
482
-
483
- self.class.map[sub_cmd].to_s
484
- end
485
-
486
- def print_auto_resp(curr, len, sub_cmd) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
487
- list = []
488
- case len
489
- when 0
490
- list = list_commands
491
- when 1
492
- list = Awskeyring.list_account_names
493
- when 2
494
- list = Awskeyring.list_role_names
495
- when 3..10
496
- list = list_arguments(command: sub_cmd)
497
- when 40
498
- list = Awskeyring.list_console_path
499
- when 50
500
- list = Awskeyring.list_token_names
501
- when 60
502
- list = Awskeyring.list_browsers
497
+ return self.class.map[sub_cmd].to_s if self.class.map.key? sub_cmd
498
+
499
+ (Awskeyring.solo_select(list_commands, sub_cmd) || '').tr('-', '_')
500
+ end
501
+
502
+ # given a type return the right list for completions
503
+ def fetch_auto_resp(comp_type, sub_cmd)
504
+ case comp_type
505
+ when :command
506
+ list_commands
507
+ when :account
508
+ Awskeyring.list_account_names
509
+ when :role
510
+ Awskeyring.list_role_names
511
+ when :path_type
512
+ Awskeyring.list_console_path
513
+ when :token
514
+ Awskeyring.list_token_names
515
+ when :browser_type
516
+ Awskeyring.list_browsers
503
517
  else
504
- exit 1
518
+ list_arguments(command: sub_cmd)
505
519
  end
506
- puts list.select { |elem| elem.start_with?(curr) }.sort!.join("\n")
507
520
  end
508
521
 
522
+ # list command names
509
523
  def list_commands
510
- self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }.reject! { |elem| elem == 'autocomplete' }
524
+ commands = self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
525
+ commands.reject! { |elem| %w[autocomplete default].include?(elem) }
511
526
  end
512
527
 
528
+ # list flags for a command
513
529
  def list_arguments(command:)
514
- exit 1 if command.empty?
515
- self.class.all_commands[command].options.values.map(&:aliases).flatten! +
516
- self.class.all_commands[command].options.values.map(&:switch_name)
530
+ options = self.class.all_commands[command].options.values
531
+ exit 1 if options.empty?
532
+
533
+ options.map(&:aliases).flatten! +
534
+ options.map(&:switch_name)
535
+ end
536
+
537
+ # add warning about old keys
538
+ def age_check_and_get(account:, no_token:)
539
+ cred = Awskeyring.get_valid_creds(account: account, no_token: no_token)
540
+
541
+ maxage = Awskeyring.key_age
542
+ age = (Time.new - cred[:updated]).div Awskeyring::Awsapi::ONE_DAY
543
+ warn I18n.t('message.age_check', account: account, age: age) unless age < maxage
544
+
545
+ cred
517
546
  end
518
547
 
548
+ # print exports from map
519
549
  def put_env_string(cred)
520
550
  env_var = Awskeyring::Awsapi.get_env_array(cred)
521
551
  env_var.each { |var, value| puts "export #{var}=\"#{value}\"" }
522
552
  Awskeyring::Awsapi::AWS_ENV_VARS.each { |key| puts "unset #{key}" unless env_var.key?(key) }
523
553
  end
524
554
 
555
+ # select duration for sts token types
525
556
  def default_duration(duration, role, code)
526
557
  duration ||= Awskeyring::Awsapi::ONE_HOUR.to_s if role
527
558
  duration ||= Awskeyring::Awsapi::TWELVE_HOUR.to_s if code
528
559
  duration || Awskeyring::Awsapi::ONE_HOUR.to_s
529
560
  end
530
561
 
562
+ # ask and validate input values.
531
563
  def ask_check(existing:, message:, flags: nil, validator: nil, limited_to: nil) # rubocop:disable Metrics/MethodLength
532
564
  retries ||= 3
533
565
  begin
@@ -548,10 +580,12 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
548
580
  value
549
581
  end
550
582
 
583
+ # ask for somthinng if its missing.
551
584
  def ask_missing(existing:, message:, secure: false, optional: false, limited_to: nil)
552
585
  existing || ask(message: message, secure: secure, optional: optional, limited_to: limited_to).strip
553
586
  end
554
587
 
588
+ # ask in different ways
555
589
  def ask(message:, secure: false, optional: false, limited_to: nil)
556
590
  if secure
557
591
  Awskeyring::Input.read_secret("#{message.rjust(20)}: ")
@@ -564,6 +598,7 @@ class AwskeyringCommand < Thor # rubocop:disable Metrics/ClassLength
564
598
  end
565
599
  end
566
600
 
601
+ # undo Bundler env vars
567
602
  def unbundle
568
603
  to_delete = ENV.keys.select { |elem| elem.start_with?('BUNDLER_ORIG_') }
569
604
  bundled_env = to_delete.map { |elem| elem[('BUNDLER_ORIG_'.length)..] }
data/man/awskeyring.5 CHANGED
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "AWSKEYRING" "5" "March 2021" "" ""
4
+ .TH "AWSKEYRING" "5" "July 2021" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain
@@ -189,17 +189,14 @@ rotate ACCOUNT:
189
189
  Rotate access keys for an ACCOUNT
190
190
  .
191
191
  .TP
192
- token ACCOUNT [ROLE] [MFA]:
192
+ token ACCOUNT [ROLE] [CODE]:
193
193
  .
194
194
  .IP
195
- Create an STS Token from a ROLE or an MFA code
195
+ Create an STS Token from a ROLE or an mfa CODE
196
196
  .
197
197
  .br
198
198
  .
199
199
  .IP
200
- \-r, \-\-role=ROLE: The ROLE to assume\.
201
- .
202
- .br
203
200
  \-c, \-\-code=CODE: Virtual mfa CODE\.
204
201
  .
205
202
  .br
@@ -267,8 +264,34 @@ awskeyring env personal\-aws
267
264
  .
268
265
  .IP "" 0
269
266
  .
267
+ .P
268
+ To open the AWS Console (web page) with your default browser simply run\.\.\.
269
+ .
270
+ .IP "" 4
271
+ .
272
+ .nf
273
+
274
+ awskeyring console personal\-aws
275
+ .
276
+ .fi
277
+ .
278
+ .IP "" 0
279
+ .
280
+ .P
281
+ Autocomplete is enabled in your current shell with the following command\.\.\.
282
+ .
283
+ .IP "" 4
284
+ .
285
+ .nf
286
+
287
+ complete \-C /usr/local/bin/awskeyring awskeyring
288
+ .
289
+ .fi
290
+ .
291
+ .IP "" 0
292
+ .
270
293
  .SH "HISTORY"
271
- The motivation of this application is to provide a local secure store of AWS credentials using specifically in the macOS Keychain, to have them easily accessed from the Terminal, and to provide useful functions like assuming roles and opening the AWS Console from the cli\. For Enterprise environments there are better suited tools to use like HashiCorp Vault \fIhttps://vaultproject\.io/\fR\.
294
+ The motivation of this application is to provide a local secure store of AWS credentials using specifically in the macOS Keychain, to have them easily accessed from the Terminal, and to provide useful functions like assuming roles and opening the AWS Console from the cli\. It then expanded to include autocomplete and a desire to have an almost complete test coverage to prevent regressions in its functionality\. For Enterprise environments there are better suited tools to use like HashiCorp Vault \fIhttps://vaultproject\.io/\fR\.
272
295
  .
273
296
  .SH "SECURITY"
274
297
  If you believe you have found a security issue in Awskeyring, please responsibly disclose by contacting me at \fItristan\.morgan@servian\.com\fR\. Awskeyring is a Ruby script and as such Ruby is whitelisted to access your "awskeyring" keychain\. Use a strong password and keep the unlock time short\.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awskeyring
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.2
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Morgan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-19 00:00:00.000000000 Z
11
+ date: 2021-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-iam
@@ -93,8 +93,8 @@ licenses:
93
93
  metadata:
94
94
  bug_tracker_uri: https://github.com/servian/awskeyring/issues
95
95
  changelog_uri: https://github.com/servian/awskeyring/blob/main/CHANGELOG.md
96
- documentation_uri: https://rubydoc.info/gems/awskeyring/1.8.2
97
- source_code_uri: https://github.com/servian/awskeyring/tree/v1.8.2
96
+ documentation_uri: https://rubydoc.info/gems/awskeyring/1.9.0
97
+ source_code_uri: https://github.com/servian/awskeyring/tree/v1.9.0
98
98
  wiki_uri: https://github.com/servian/awskeyring/wiki
99
99
  post_install_message:
100
100
  rdoc_options: []