conjur-cli 4.25.2 → 4.26.0
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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/README.md +6 -0
- data/Rakefile +1 -33
- data/bin/{_conjur_completions → _conjur} +5 -26
- data/lib/conjur/command.rb +7 -4
- data/lib/conjur/command/audit.rb +10 -0
- data/lib/conjur/command/env.rb +1 -0
- data/lib/conjur/command/groups.rb +14 -15
- data/lib/conjur/command/hosts.rb +9 -9
- data/lib/conjur/command/init.rb +2 -0
- data/lib/conjur/command/layers.rb +22 -22
- data/lib/conjur/command/plugin.rb +6 -6
- data/lib/conjur/command/policy.rb +2 -2
- data/lib/conjur/command/pubkeys.rb +8 -8
- data/lib/conjur/command/resources.rb +30 -30
- data/lib/conjur/command/roles.rb +14 -14
- data/lib/conjur/command/rspec/audit_helpers.rb +0 -1
- data/lib/conjur/command/script.rb +2 -2
- data/lib/conjur/command/shellinit.rb +36 -0
- data/lib/conjur/command/users.rb +8 -8
- data/lib/conjur/command/variables.rb +12 -12
- data/lib/conjur/complete.rb +263 -0
- data/lib/conjur/version.rb +1 -1
- data/spec/command/audit_spec.rb +19 -0
- data/spec/complete_spec.rb +265 -0
- data/spec/spec_helper.rb +1 -0
- metadata +8 -6
- data/bin/_conjur_completions.yaml +0 -106
|
@@ -22,15 +22,15 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
22
22
|
desc "Manage variables"
|
|
23
23
|
command :variable do |var|
|
|
24
24
|
var.desc "Create and store a variable"
|
|
25
|
-
var.arg_name "
|
|
25
|
+
var.arg_name "NAME VALUE"
|
|
26
26
|
var.command :create do |c|
|
|
27
|
-
c.arg_name "
|
|
27
|
+
c.arg_name "MIME-TYPE"
|
|
28
28
|
c.flag [:m, :"mime-type"], default_value: 'text/plain'
|
|
29
29
|
|
|
30
|
-
c.arg_name "
|
|
30
|
+
c.arg_name "KIND"
|
|
31
31
|
c.flag [:k, :"kind"], default_value: 'secret'
|
|
32
32
|
|
|
33
|
-
c.arg_name "
|
|
33
|
+
c.arg_name "VALUE"
|
|
34
34
|
c.desc "Initial value, which may also be specified as the second command argument after the variable id"
|
|
35
35
|
c.flag [:v, :"value"]
|
|
36
36
|
|
|
@@ -91,21 +91,21 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
var.desc "Show a variable"
|
|
94
|
-
var.arg_name "
|
|
94
|
+
var.arg_name "VARIABLE"
|
|
95
95
|
var.command :show do |c|
|
|
96
96
|
c.action do |global_options,options,args|
|
|
97
|
-
id = require_arg(args, '
|
|
97
|
+
id = require_arg(args, 'VARIABLE')
|
|
98
98
|
display(api.variable(id), options)
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
var.desc "Decommission a variable"
|
|
103
|
-
var.arg_name "
|
|
103
|
+
var.arg_name "VARIABLE"
|
|
104
104
|
var.command :retire do |c|
|
|
105
105
|
retire_options c
|
|
106
106
|
|
|
107
107
|
c.action do |global_options,options,args|
|
|
108
|
-
id = require_arg(args, '
|
|
108
|
+
id = require_arg(args, 'VARIABLE')
|
|
109
109
|
|
|
110
110
|
variable = api.variable(id)
|
|
111
111
|
|
|
@@ -130,10 +130,10 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
130
130
|
var.desc "Access variable values"
|
|
131
131
|
var.command :values do |values|
|
|
132
132
|
values.desc "Add a value"
|
|
133
|
-
values.arg_name "
|
|
133
|
+
values.arg_name "VARIABLE VALUE"
|
|
134
134
|
values.command :add do |c|
|
|
135
135
|
c.action do |global_options,options,args|
|
|
136
|
-
id = require_arg(args, '
|
|
136
|
+
id = require_arg(args, 'VARIABLE')
|
|
137
137
|
value = args.shift || STDIN.read
|
|
138
138
|
|
|
139
139
|
api.variable(id).add_value(value)
|
|
@@ -143,13 +143,13 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
var.desc "Get a value"
|
|
146
|
-
var.arg_name "
|
|
146
|
+
var.arg_name "VARIABLE"
|
|
147
147
|
var.command :value do |c|
|
|
148
148
|
c.desc "Version number"
|
|
149
149
|
c.flag [:v, :version]
|
|
150
150
|
|
|
151
151
|
c.action do |global_options,options,args|
|
|
152
|
-
id = require_arg(args, '
|
|
152
|
+
id = require_arg(args, 'VARIABLE')
|
|
153
153
|
$stdout.write api.variable(id).value(options[:version])
|
|
154
154
|
end
|
|
155
155
|
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2015 Conjur Inc.
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person
|
|
5
|
+
# obtaining a copy of this software and associated documentation files
|
|
6
|
+
# (the "Software"), to deal in the Software without restriction,
|
|
7
|
+
# including without limitation the rights to use, copy, modify, merge,
|
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
9
|
+
# and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
# subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
19
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
20
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
#
|
|
24
|
+
require 'conjur/cli'
|
|
25
|
+
require 'shellwords'
|
|
26
|
+
|
|
27
|
+
# Class for generating `conjur` bash completions
|
|
28
|
+
class Conjur::CLI::Complete
|
|
29
|
+
attr_reader :line, :words, :current_word_index, :commands,
|
|
30
|
+
:switch_words, :flag_words, :arg_words, :command_words
|
|
31
|
+
def initialize line, point=nil
|
|
32
|
+
@line = line
|
|
33
|
+
@words = tokenize_cmd @line
|
|
34
|
+
point ||= @line.length
|
|
35
|
+
@current_word_index=(tokenize_cmd @line.slice(0,point)).length-1
|
|
36
|
+
# fix arrays for empty "current word"
|
|
37
|
+
# ie "conjur group list "
|
|
38
|
+
if @line.match(/[ =]$/)
|
|
39
|
+
@words << ''
|
|
40
|
+
@current_word_index += 1
|
|
41
|
+
end
|
|
42
|
+
@commands,
|
|
43
|
+
@switch_words,
|
|
44
|
+
@flag_words,
|
|
45
|
+
@arg_words = parse_command @words, @current_word_index
|
|
46
|
+
@command_words = @commands
|
|
47
|
+
.drop(1)
|
|
48
|
+
.map(&:name)
|
|
49
|
+
.map(&:to_s)
|
|
50
|
+
.unshift('conjur')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_word offset=0
|
|
54
|
+
@words[@current_word_index + offset]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Generate array of subcommands for which documentation is not hidden
|
|
58
|
+
#
|
|
59
|
+
# @param cmd [Conjur::CLI::Command] the command to search
|
|
60
|
+
# @return [Array] the subcommands
|
|
61
|
+
def subcommands cmd
|
|
62
|
+
cmd.commands.select do |_, c|
|
|
63
|
+
c.nodoc.nil?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Generate array of symbols representing switches for +cmd+ and
|
|
68
|
+
# their aliases
|
|
69
|
+
#
|
|
70
|
+
# @param cmd [Conjur::CLI::Command] the command to search
|
|
71
|
+
# @return [Array] the symbols representing switches and their aliases
|
|
72
|
+
def switches cmd
|
|
73
|
+
cmd.switches.map { |_,switch|
|
|
74
|
+
[switch.name] + (switch.aliases or [])
|
|
75
|
+
}.flatten
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Split line according on spaces and after '='
|
|
79
|
+
#
|
|
80
|
+
# @param line [String] to split
|
|
81
|
+
# @return [Array] the substrings
|
|
82
|
+
def tokenize_cmd line
|
|
83
|
+
line.split(/ |(?<==)/)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def flag_to_sym flag
|
|
87
|
+
flag.match(/--?([^=]+)=?/)[1].to_sym
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_command words, current_word_index
|
|
91
|
+
command = Conjur::CLI
|
|
92
|
+
commands = [command]
|
|
93
|
+
switches = []
|
|
94
|
+
flags = []
|
|
95
|
+
arguments = []
|
|
96
|
+
index = 1
|
|
97
|
+
until index >= current_word_index do
|
|
98
|
+
word = words[index]
|
|
99
|
+
case classify_word word, command
|
|
100
|
+
when :switch
|
|
101
|
+
switches.push word
|
|
102
|
+
when :flag
|
|
103
|
+
flags.push [word, words[index+1]]
|
|
104
|
+
index += 1
|
|
105
|
+
when :subcommand
|
|
106
|
+
command = command.commands[word.to_sym]
|
|
107
|
+
commands.push command
|
|
108
|
+
when :argument
|
|
109
|
+
arguments.push word
|
|
110
|
+
end
|
|
111
|
+
index += 1
|
|
112
|
+
end
|
|
113
|
+
return commands, switches, flags, arguments
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def classify_word word, command
|
|
117
|
+
if word.start_with? '-'
|
|
118
|
+
sym = flag_to_sym word
|
|
119
|
+
if switches(command).member? sym
|
|
120
|
+
:switch
|
|
121
|
+
else
|
|
122
|
+
:flag
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
if subcommands(command).has_key? word.to_sym
|
|
126
|
+
:subcommand
|
|
127
|
+
else
|
|
128
|
+
:argument
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def complete kind
|
|
134
|
+
kind = kind.to_s.downcase.gsub(/[^a-z]/, '')
|
|
135
|
+
case kind
|
|
136
|
+
when 'resource'
|
|
137
|
+
complete_resource
|
|
138
|
+
when 'role'
|
|
139
|
+
complete_role
|
|
140
|
+
when 'file'
|
|
141
|
+
complete_file current_word
|
|
142
|
+
when 'hostname'
|
|
143
|
+
complete_hostname
|
|
144
|
+
else
|
|
145
|
+
complete_resource kind if [
|
|
146
|
+
'group',
|
|
147
|
+
'user',
|
|
148
|
+
'variable',
|
|
149
|
+
'host',
|
|
150
|
+
'layer',
|
|
151
|
+
].member? kind
|
|
152
|
+
end or []
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# generate completions for the switches and flags of a Conjur::CLI::Command
|
|
156
|
+
#
|
|
157
|
+
# @param cmd [Conjur::CLI::Command] command for which to search for flags
|
|
158
|
+
# and switches
|
|
159
|
+
# @return [Array] completion words
|
|
160
|
+
def complete_flags cmd
|
|
161
|
+
cmd.flags.values.map do |flag|
|
|
162
|
+
candidates = [flag.name]
|
|
163
|
+
candidates += flag.aliases if flag.aliases
|
|
164
|
+
candidates.map do |c|
|
|
165
|
+
"-#{'-' if c.length > 1}#{c}#{'=' if c.length > 1}"
|
|
166
|
+
end
|
|
167
|
+
end + cmd.switches.values.map do |switch|
|
|
168
|
+
candidates = [switch.name]
|
|
169
|
+
candidates += switch.aliases if switch.aliases
|
|
170
|
+
candidates.map do |c|
|
|
171
|
+
"-#{'-' if c.length > 1}#{c}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def complete_args cmd, prev, num_args
|
|
177
|
+
kind=nil
|
|
178
|
+
if prev.start_with? '-'
|
|
179
|
+
flag_name=flag_to_sym prev
|
|
180
|
+
flag = cmd.flags[flag_name]
|
|
181
|
+
desc = flag.argument_name if defined? flag.argument_name
|
|
182
|
+
kind = desc.to_s.downcase
|
|
183
|
+
else
|
|
184
|
+
desc = cmd.arguments_description if defined? cmd.arguments_description
|
|
185
|
+
kind = desc.to_s.downcase.split[num_args-1]
|
|
186
|
+
end
|
|
187
|
+
complete kind
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def complete_resource resource_kind=nil
|
|
191
|
+
Conjur::Command.api.resources({kind: resource_kind})
|
|
192
|
+
.map do |r|
|
|
193
|
+
res = Resource.new r.attributes['id']
|
|
194
|
+
if resource_kind
|
|
195
|
+
res.name
|
|
196
|
+
else
|
|
197
|
+
res.to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def complete_role
|
|
203
|
+
Conjur::Command.api.current_role.all
|
|
204
|
+
.map { |r| Resource.new(r.roleid) }
|
|
205
|
+
.reject { |r| r.kind.start_with? '@' }
|
|
206
|
+
.map(&:to_s)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def complete_file word
|
|
210
|
+
# use Bash's file completion for compatibility
|
|
211
|
+
`bash -c "compgen -f #{word}"`.shellsplit
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def complete_hostname
|
|
215
|
+
`bash -c "compgen -A hostname"`.shellsplit
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def completions
|
|
219
|
+
prev = current_word(-1)
|
|
220
|
+
if current_word.start_with? '-'
|
|
221
|
+
complete_flags @commands.last
|
|
222
|
+
else
|
|
223
|
+
(subcommands @commands.last).keys.map(&:to_s) +
|
|
224
|
+
(complete_args @commands.last, prev, @arg_words.length)
|
|
225
|
+
end.flatten
|
|
226
|
+
.select do |candidate|
|
|
227
|
+
candidate.start_with? current_word.sub('\:',':') end
|
|
228
|
+
.map do |candidate|
|
|
229
|
+
# if the current word is colon separated, strip its complete tokens
|
|
230
|
+
# eg. for --as-role=user:ryanprior, we're actually only completing 'ryanprior'
|
|
231
|
+
# because bash treats 'user' as a separate word
|
|
232
|
+
non_escaped_colon_regex = /(?<!\\):/
|
|
233
|
+
num_tokens = current_word.split(non_escaped_colon_regex).length
|
|
234
|
+
if num_tokens > 1
|
|
235
|
+
candidate = candidate
|
|
236
|
+
.split(non_escaped_colon_regex)
|
|
237
|
+
.drop(num_tokens-1)
|
|
238
|
+
.join(':')
|
|
239
|
+
end
|
|
240
|
+
"#{candidate}#{' ' if not candidate.end_with? '='}" end
|
|
241
|
+
end
|
|
242
|
+
public :current_word, :completions
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
class Conjur::CLI::Complete::Resource
|
|
246
|
+
attr_reader :account, :kind, :name
|
|
247
|
+
attr_accessor :include_account
|
|
248
|
+
def initialize resource_string, include_account=false
|
|
249
|
+
@include_account = include_account
|
|
250
|
+
fields = resource_string.split ':'
|
|
251
|
+
raise ArgumentError.new "too many fields (#{resource_string})" if fields.length > 3
|
|
252
|
+
fields.unshift nil while fields.length < 3
|
|
253
|
+
@account, @kind, @name = fields
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def to_ary
|
|
257
|
+
[(@account if @include_account), @kind, @name].reject { |a| a.nil? }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def to_s
|
|
261
|
+
to_ary.join ':'
|
|
262
|
+
end
|
|
263
|
+
end
|
data/lib/conjur/version.rb
CHANGED
data/spec/command/audit_spec.rb
CHANGED
|
@@ -312,6 +312,25 @@ describe Conjur::Command::Audit, logged_in: true do
|
|
|
312
312
|
expect { invoke }.to write(" created role super:user")
|
|
313
313
|
end
|
|
314
314
|
end
|
|
315
|
+
|
|
316
|
+
describe 'audit of ssh:sudo' do
|
|
317
|
+
let(:ssh_event) { default_audit_event.merge('kind' => 'audit', 'facility' => 'ssh', 'action' => 'sudo', 'command' => '/bin/ls', 'system_user' => 'test_user', 'target_user' => 'root') }
|
|
318
|
+
context 'when sudo successful' do
|
|
319
|
+
let(:test_event) { ssh_event.merge('allowed' => true) }
|
|
320
|
+
it 'prints <user> ran <command>' do
|
|
321
|
+
expect { invoke }.to write(" test_user ran '/bin/ls' as root")
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
context 'when sudo fails' do
|
|
326
|
+
let(:test_event) { ssh_event.merge('allowed' => false) }
|
|
327
|
+
|
|
328
|
+
it 'prints <user> attempted to run <command>' do
|
|
329
|
+
expect { invoke }.to write(" test_user attempted to run '/bin/ls' as root")
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
315
334
|
end
|
|
316
335
|
end
|
|
317
336
|
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Conjur::CLI::Complete do
|
|
4
|
+
def expects_completions_for string, point=nil
|
|
5
|
+
expect(described_class.new("conjur #{string}",point)
|
|
6
|
+
.completions
|
|
7
|
+
.map { |c| c.chomp ' ' })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe 'conjur bash completion' do
|
|
11
|
+
describe 'for conjur subcommands beginning' do
|
|
12
|
+
|
|
13
|
+
before(:each) { expect(Conjur::Command).not_to receive :api }
|
|
14
|
+
|
|
15
|
+
context 'with "conjur gr"' do
|
|
16
|
+
it { expects_completions_for('gr').to include 'group' }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'with "conjur group"' do
|
|
20
|
+
it { expects_completions_for('group').to contain_exactly 'group' }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'with "conjur p"' do
|
|
24
|
+
it { expects_completions_for('p').to include 'plugin',
|
|
25
|
+
'policy',
|
|
26
|
+
'pubkeys' }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'with "conjur host l"' do
|
|
30
|
+
it { expects_completions_for('host l').to include 'layers',
|
|
31
|
+
'list' }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'with "conjur policy"' do
|
|
35
|
+
it { expects_completions_for('policy ').to include 'load' }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe 'for deprecated subcommands such as `conjur field`' do
|
|
40
|
+
it { expects_completions_for('fi').not_to include 'field' }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe 'for command flags beginning' do
|
|
44
|
+
|
|
45
|
+
before(:each) { expect(Conjur::Command).not_to receive :api }
|
|
46
|
+
|
|
47
|
+
context 'conjur -' do
|
|
48
|
+
it { expects_completions_for('-').to include '--version' }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'conjur role memberships -' do
|
|
52
|
+
it { expects_completions_for('role memberships -')
|
|
53
|
+
.to include '-s', '--system'}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context 'conjur audit all -' do
|
|
57
|
+
it { expects_completions_for('audit all -s -')
|
|
58
|
+
.to include '-f', '--follow', '-l', '--limit=',
|
|
59
|
+
'-o', '--offset=', '-s', '--short' }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'conjur layer create --as-' do
|
|
63
|
+
it { expects_completions_for('layer create --as-')
|
|
64
|
+
.to include '--as-role=' }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'conjur group create --as-role' do
|
|
68
|
+
it { expects_completions_for('layer create --as-role')
|
|
69
|
+
.to contain_exactly '--as-role=' }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'for arguments' do
|
|
74
|
+
|
|
75
|
+
let (:api) { double('api') }
|
|
76
|
+
before(:each) {
|
|
77
|
+
expect(Conjur::Command).to receive(:api).at_least(:once).and_return api
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe 'expecting a resource' do
|
|
81
|
+
|
|
82
|
+
let (:users) { ['Tweedledum', 'Tweedledee'] }
|
|
83
|
+
let (:groups) { ['sharks', 'jets'] }
|
|
84
|
+
let (:layers) { ['limbo', 'lust', 'gluttony', 'greed',
|
|
85
|
+
'anger', 'heresy', 'violence', 'fraud',
|
|
86
|
+
'treachery'] }
|
|
87
|
+
let (:variables) { ['id/superman', 'id/batman', 'id/spiderman'] }
|
|
88
|
+
let (:hosts) { ['skynet', 'hal9000', 'deep-thought'] }
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def mock_resources
|
|
92
|
+
fake_results = yield.map { |result|
|
|
93
|
+
double('resource', :attributes => { 'id' => result })
|
|
94
|
+
}
|
|
95
|
+
expect(Conjur::Command.api).to receive(:resources)
|
|
96
|
+
.once.and_return fake_results
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'with kind "user"' do
|
|
100
|
+
before(:each) { mock_resources { users.map { |user| "user:#{user}" }}}
|
|
101
|
+
it { expects_completions_for('user show ')
|
|
102
|
+
.to contain_exactly(*users) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'with kind "group"' do
|
|
106
|
+
before(:each) {
|
|
107
|
+
mock_resources { groups.map { |group| "group:#{group}" }}
|
|
108
|
+
}
|
|
109
|
+
context 'for a command' do
|
|
110
|
+
it { expects_completions_for('group show ')
|
|
111
|
+
.to contain_exactly(*groups) }
|
|
112
|
+
end
|
|
113
|
+
context 'for a flag' do
|
|
114
|
+
it { expects_completions_for('group create --as-group=')
|
|
115
|
+
.to contain_exactly(*groups) }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context 'with kind "layer"' do
|
|
120
|
+
before(:each) {
|
|
121
|
+
mock_resources { layers.map { |layer| "layer:#{layer}" }}
|
|
122
|
+
}
|
|
123
|
+
it { expects_completions_for('layer show ')
|
|
124
|
+
.to contain_exactly(*layers) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context 'with kind "variable"' do
|
|
128
|
+
before(:each) {
|
|
129
|
+
mock_resources { variables.map { |variable| "variable:#{variable}" }}
|
|
130
|
+
}
|
|
131
|
+
it { expects_completions_for('variable show ')
|
|
132
|
+
.to contain_exactly(*variables) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context 'with kind "host"' do
|
|
136
|
+
before(:each) {
|
|
137
|
+
mock_resources { hosts.map { |host| "host:#{host}" }}
|
|
138
|
+
}
|
|
139
|
+
it { expects_completions_for('host show ')
|
|
140
|
+
.to contain_exactly(*hosts)
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context 'without kind specified' do
|
|
145
|
+
let (:resources) { (users+groups+layers+variables+hosts)
|
|
146
|
+
.map { |res| "arbitrarykind:#{res}" }}
|
|
147
|
+
before(:each) { mock_resources { resources }}
|
|
148
|
+
it 'should show all resources with their reported kinds' do
|
|
149
|
+
expects_completions_for('resource show ')
|
|
150
|
+
.to contain_exactly(*resources)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe 'expecting a role' do
|
|
156
|
+
let (:roles) { ['layer:population/tire',
|
|
157
|
+
'host:bubs-4k',
|
|
158
|
+
'user:strongbad',
|
|
159
|
+
'user:strongsad',
|
|
160
|
+
'user:strongmad']}
|
|
161
|
+
before(:each) {
|
|
162
|
+
role_doubles = roles.map { |role| double('role', :roleid => role) }
|
|
163
|
+
expect(api).to receive(:current_role).once
|
|
164
|
+
.and_return double('current_role', :all => role_doubles)
|
|
165
|
+
}
|
|
166
|
+
it { expects_completions_for('role memberships ')
|
|
167
|
+
.to contain_exactly(*roles) }
|
|
168
|
+
it 'completes colon separated values per-token' do
|
|
169
|
+
expects_completions_for('layer list --role=host:b')
|
|
170
|
+
.to contain_exactly 'bubs-4k'
|
|
171
|
+
end
|
|
172
|
+
it 'recognizes shell-escaped colons' do
|
|
173
|
+
expects_completions_for('role members layer\:pop')
|
|
174
|
+
.to contain_exactly 'layer:population/tire'
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe 'completes mid-line' do
|
|
180
|
+
it 'completes a subcommand not at the end of a line' do
|
|
181
|
+
expect(described_class.new('conjur gr create dwarves/7', 9).completions)
|
|
182
|
+
.to include 'group '
|
|
183
|
+
end
|
|
184
|
+
it 'tolerates garbage flags and arguments' do
|
|
185
|
+
expect(described_class.new('conjur omg --lol wat pu').completions)
|
|
186
|
+
.to include 'pubkeys '
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe Conjur::CLI::Complete::Resource do
|
|
193
|
+
describe 'splits resource ids' do
|
|
194
|
+
describe '#initialize(resource_string)' do
|
|
195
|
+
describe 'accepts long or brief ids' do
|
|
196
|
+
context 'gratuitous id (4+ tokens)' do
|
|
197
|
+
it 'raises an ArgumentError' do
|
|
198
|
+
expect {
|
|
199
|
+
described_class.new '1:2:3:4'
|
|
200
|
+
}.to raise_error ArgumentError
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
context 'long id (3 tokens)' do
|
|
204
|
+
it 'stores all 3 tokens' do
|
|
205
|
+
dummy_string = 'acct:kind:name'
|
|
206
|
+
dummy = described_class.new dummy_string
|
|
207
|
+
expect(dummy.account).to eq 'acct'
|
|
208
|
+
expect(dummy.kind).to eq 'kind'
|
|
209
|
+
expect(dummy.name).to eq 'name'
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
context 'brief id (2 tokens)' do
|
|
213
|
+
it 'stores tokens in kind and name' do
|
|
214
|
+
dummy = described_class.new 'kind:name'
|
|
215
|
+
expect(dummy.account).to eq nil
|
|
216
|
+
expect(dummy.kind).to eq 'kind'
|
|
217
|
+
expect(dummy.name).to eq 'name'
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
context 'tiny id (1 token)' do
|
|
221
|
+
it 'stores token in name' do
|
|
222
|
+
dummy = described_class.new 'name'
|
|
223
|
+
expect(dummy.account).to eq nil
|
|
224
|
+
expect(dummy.kind).to eq nil
|
|
225
|
+
expect(dummy.name).to eq 'name'
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
it 'hides the account by default' do
|
|
230
|
+
expect(described_class.new('a:b:c').include_account).to eq false
|
|
231
|
+
expect(described_class.new('a:b:c', true).include_account).to eq true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe '#to_ary' do
|
|
236
|
+
context 'account not important' do
|
|
237
|
+
it 'hides account when converting to array' do
|
|
238
|
+
dummy = described_class.new 'a:b:c'
|
|
239
|
+
expect(dummy.to_ary).to eq ['b','c']
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
context 'account is important' do
|
|
243
|
+
it 'includes account when converting to array' do
|
|
244
|
+
dummy = described_class.new 'a:b:c', true
|
|
245
|
+
expect(dummy.to_ary).to eq ['a','b','c']
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
describe '#to_s' do
|
|
251
|
+
context 'account not important' do
|
|
252
|
+
it 'hides account when converting to string' do
|
|
253
|
+
dummy = described_class.new 'test:user:admin'
|
|
254
|
+
expect(dummy.to_s).to eq 'user:admin'
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
context 'account is important' do
|
|
258
|
+
it 'includes account when converting to string' do
|
|
259
|
+
dummy = described_class.new 'test:user:admin', true
|
|
260
|
+
expect(dummy.to_s).to eq 'test:user:admin'
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|