mtncloud-cli 1.0.0 → 1.0.2
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/.dockerignore +20 -20
- data/.github/pull_request_template.md +14 -14
- data/.github/workflows/ci.yml +56 -56
- data/.gitignore +24 -24
- data/CLAUDE.md +24 -24
- data/CONTRIBUTING.md +91 -91
- data/Dockerfile +19 -19
- data/Gemfile +4 -4
- data/README.md +194 -187
- data/Rakefile +26 -26
- data/bin/mcs +3 -3
- data/bin/mtncloud +0 -0
- data/lib/mtncloud/cli/cli_command.rb +2281 -2281
- data/lib/mtncloud/cli/commands/login.rb +8 -3
- data/lib/mtncloud/cli/credentials.rb +5 -1
- data/lib/mtncloud/cli/version.rb +1 -1
- data/lib/mtncloud/ext/hash.rb +109 -109
- data/lib/mtncloud/ext/nil_class.rb +5 -5
- data/lib/mtncloud/ext/rest_client.rb +37 -37
- data/lib/mtncloud/ext/string.rb +66 -66
- data/mtncloud-cli.gemspec +38 -38
- data/test/cli/login_help_test.rb +47 -0
- metadata +11 -4
|
@@ -25,7 +25,7 @@ class MtnCloud::Cli::Login
|
|
|
25
25
|
username, password = nil, nil
|
|
26
26
|
optparse = MtnCloud::Cli::OptionParser.new do |opts|
|
|
27
27
|
opts.banner = usage
|
|
28
|
-
opts.on( '-u', '--username USERNAME', "Username. Sub-tenant users
|
|
28
|
+
opts.on( '-u', '--username USERNAME', "Username. Sub-tenant users prefix it with their subdomain or account ID: {subdomain}\\{username} or {accountID}\\{username}" ) do |val|
|
|
29
29
|
username = val
|
|
30
30
|
end
|
|
31
31
|
opts.on( '-p', '--password PASSWORD', "Password" ) do |val|
|
|
@@ -54,8 +54,13 @@ Login to a remote appliance with a username and password or using an access toke
|
|
|
54
54
|
Logging in with username and password will make an authentication api request to obtain an access token.
|
|
55
55
|
[username] is required, this is the username of the MTN Cloud user
|
|
56
56
|
[password] is required, this is the password of the MTN Cloud user
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
Username format:
|
|
58
|
+
Master tenant users enter only their username. Example: neo
|
|
59
|
+
Sub-tenant users prefix their username with their tenant's subdomain or account ID:
|
|
60
|
+
{subdomain}\\{username} Example: acme\\neo
|
|
61
|
+
{accountID}\\{username} Example: 2\\neo
|
|
62
|
+
A tenant's subdomain is its account ID unless a custom subdomain is set.
|
|
63
|
+
In bash or zsh, quote a username that contains a backslash: '2\\neo'
|
|
59
64
|
The --token option can be used to login with a valid access token instead of username and password.
|
|
60
65
|
The specified token will be verified by making a whoami api request
|
|
61
66
|
If successful, the access token will be saved with the active session for the remote appliance.
|
|
@@ -11,6 +11,10 @@ module MtnCloud
|
|
|
11
11
|
class Credentials
|
|
12
12
|
include MtnCloud::Cli::PrintHelper
|
|
13
13
|
|
|
14
|
+
# How sub-tenant users format their username, shown in the login prompt.
|
|
15
|
+
# login --help explains the same formats in more detail.
|
|
16
|
+
USERNAME_FORMAT = "sub-tenant users: subdomain\\username or accountID\\username"
|
|
17
|
+
|
|
14
18
|
@@appliance_credentials_map = nil
|
|
15
19
|
|
|
16
20
|
def initialize(appliance_name, appliance_url)
|
|
@@ -118,7 +122,7 @@ module MtnCloud
|
|
|
118
122
|
Readline.basic_word_break_characters = ''
|
|
119
123
|
Readline.completer_word_break_characters = '' rescue nil
|
|
120
124
|
Readline.completion_proc = nil
|
|
121
|
-
username = Readline.readline("Username: #{required_blue_prompt} ", false).to_s.chomp
|
|
125
|
+
username = Readline.readline("Username (#{USERNAME_FORMAT}): #{required_blue_prompt} ", false).to_s.chomp
|
|
122
126
|
else
|
|
123
127
|
print "Username: #{required_blue_prompt} #{username}\n"
|
|
124
128
|
end
|
data/lib/mtncloud/cli/version.rb
CHANGED
data/lib/mtncloud/ext/hash.rb
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
# Provide deep_merge.
|
|
2
|
-
# Borrowed from rails active_support
|
|
3
|
-
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
|
4
|
-
#
|
|
5
|
-
class Hash
|
|
6
|
-
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
|
7
|
-
#
|
|
8
|
-
# h1 = { a: true, b: { c: [1, 2, 3] } }
|
|
9
|
-
# h2 = { a: false, b: { x: [3, 4, 5] } }
|
|
10
|
-
#
|
|
11
|
-
# h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
|
|
12
|
-
#
|
|
13
|
-
# Like with Hash#merge in the standard library, a block can be provided
|
|
14
|
-
# to merge values:
|
|
15
|
-
#
|
|
16
|
-
# h1 = { a: 100, b: 200, c: { c1: 100 } }
|
|
17
|
-
# h2 = { b: 250, c: { c1: 200 } }
|
|
18
|
-
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
|
19
|
-
# # => { a: 100, b: 450, c: { c1: 300 } }
|
|
20
|
-
def deep_merge(other_hash, &block)
|
|
21
|
-
dup.deep_merge!(other_hash, &block)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Same as +deep_merge+, but modifies +self+.
|
|
25
|
-
def deep_merge!(other_hash, &block)
|
|
26
|
-
other_hash.each_pair do |current_key, other_value|
|
|
27
|
-
this_value = self[current_key]
|
|
28
|
-
|
|
29
|
-
self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
|
|
30
|
-
this_value.deep_merge(other_value, &block)
|
|
31
|
-
else
|
|
32
|
-
if block_given? && key?(current_key)
|
|
33
|
-
block.call(current_key, this_value, other_value)
|
|
34
|
-
else
|
|
35
|
-
other_value
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
self
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def deep_compact!
|
|
44
|
-
self.each_pair do |k, v|
|
|
45
|
-
if v.is_a?(Hash)
|
|
46
|
-
self[k].deep_compact!
|
|
47
|
-
elsif v.is_a?(Array)
|
|
48
|
-
self[k].each do |it|
|
|
49
|
-
if it.is_a?(Hash)
|
|
50
|
-
it.deep_compact!
|
|
51
|
-
elsif self[k] == nil || self[k] == ''
|
|
52
|
-
# meh, preserve 'empty' array elements
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
else
|
|
56
|
-
if self[k] == nil || self[k] == ''
|
|
57
|
-
self.delete(k)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
self
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# convert recognizable strings to booleans
|
|
65
|
-
def booleanize!(true_values=['true','on'], false_values=['false','off'])
|
|
66
|
-
self.each_pair do |k, v|
|
|
67
|
-
if v.is_a?(Hash)
|
|
68
|
-
self[k].booleanize!
|
|
69
|
-
elsif v.is_a?(Array)
|
|
70
|
-
self[k].each do |it|
|
|
71
|
-
if it.is_a?(Hash)
|
|
72
|
-
it.booleanize!
|
|
73
|
-
elsif self[k] == nil || self[k] == ''
|
|
74
|
-
# meh, preserve 'empty' array elements
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
else
|
|
78
|
-
if true_values.include?(v)
|
|
79
|
-
self[k] = true
|
|
80
|
-
elsif false_values.include?(v)
|
|
81
|
-
self[k] = false
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
self
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def upcase_keys!
|
|
89
|
-
self.keys.each do |k|
|
|
90
|
-
self[k.to_s.upcase] = self.delete(k)
|
|
91
|
-
end
|
|
92
|
-
self
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def downcase_keys!
|
|
96
|
-
self.keys.each do |k|
|
|
97
|
-
self[k.to_s.downcase] = self.delete(k)
|
|
98
|
-
end
|
|
99
|
-
self
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def capitalize_keys!
|
|
103
|
-
self.keys.each do |k|
|
|
104
|
-
self[k.to_s.capitalize] = self.delete(k)
|
|
105
|
-
end
|
|
106
|
-
self
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
end
|
|
1
|
+
# Provide deep_merge.
|
|
2
|
+
# Borrowed from rails active_support
|
|
3
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
|
4
|
+
#
|
|
5
|
+
class Hash
|
|
6
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
|
7
|
+
#
|
|
8
|
+
# h1 = { a: true, b: { c: [1, 2, 3] } }
|
|
9
|
+
# h2 = { a: false, b: { x: [3, 4, 5] } }
|
|
10
|
+
#
|
|
11
|
+
# h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
|
|
12
|
+
#
|
|
13
|
+
# Like with Hash#merge in the standard library, a block can be provided
|
|
14
|
+
# to merge values:
|
|
15
|
+
#
|
|
16
|
+
# h1 = { a: 100, b: 200, c: { c1: 100 } }
|
|
17
|
+
# h2 = { b: 250, c: { c1: 200 } }
|
|
18
|
+
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
|
19
|
+
# # => { a: 100, b: 450, c: { c1: 300 } }
|
|
20
|
+
def deep_merge(other_hash, &block)
|
|
21
|
+
dup.deep_merge!(other_hash, &block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Same as +deep_merge+, but modifies +self+.
|
|
25
|
+
def deep_merge!(other_hash, &block)
|
|
26
|
+
other_hash.each_pair do |current_key, other_value|
|
|
27
|
+
this_value = self[current_key]
|
|
28
|
+
|
|
29
|
+
self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
|
|
30
|
+
this_value.deep_merge(other_value, &block)
|
|
31
|
+
else
|
|
32
|
+
if block_given? && key?(current_key)
|
|
33
|
+
block.call(current_key, this_value, other_value)
|
|
34
|
+
else
|
|
35
|
+
other_value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deep_compact!
|
|
44
|
+
self.each_pair do |k, v|
|
|
45
|
+
if v.is_a?(Hash)
|
|
46
|
+
self[k].deep_compact!
|
|
47
|
+
elsif v.is_a?(Array)
|
|
48
|
+
self[k].each do |it|
|
|
49
|
+
if it.is_a?(Hash)
|
|
50
|
+
it.deep_compact!
|
|
51
|
+
elsif self[k] == nil || self[k] == ''
|
|
52
|
+
# meh, preserve 'empty' array elements
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
if self[k] == nil || self[k] == ''
|
|
57
|
+
self.delete(k)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# convert recognizable strings to booleans
|
|
65
|
+
def booleanize!(true_values=['true','on'], false_values=['false','off'])
|
|
66
|
+
self.each_pair do |k, v|
|
|
67
|
+
if v.is_a?(Hash)
|
|
68
|
+
self[k].booleanize!
|
|
69
|
+
elsif v.is_a?(Array)
|
|
70
|
+
self[k].each do |it|
|
|
71
|
+
if it.is_a?(Hash)
|
|
72
|
+
it.booleanize!
|
|
73
|
+
elsif self[k] == nil || self[k] == ''
|
|
74
|
+
# meh, preserve 'empty' array elements
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
if true_values.include?(v)
|
|
79
|
+
self[k] = true
|
|
80
|
+
elsif false_values.include?(v)
|
|
81
|
+
self[k] = false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def upcase_keys!
|
|
89
|
+
self.keys.each do |k|
|
|
90
|
+
self[k.to_s.upcase] = self.delete(k)
|
|
91
|
+
end
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def downcase_keys!
|
|
96
|
+
self.keys.each do |k|
|
|
97
|
+
self[k.to_s.downcase] = self.delete(k)
|
|
98
|
+
end
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def capitalize_keys!
|
|
103
|
+
self.keys.each do |k|
|
|
104
|
+
self[k.to_s.capitalize] = self.delete(k)
|
|
105
|
+
end
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
class NilClass
|
|
2
|
-
def empty?
|
|
3
|
-
true
|
|
4
|
-
end
|
|
5
|
-
end
|
|
1
|
+
class NilClass
|
|
2
|
+
def empty?
|
|
3
|
+
true
|
|
4
|
+
end
|
|
5
|
+
end
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# this does some monkey patching of third party RestClient
|
|
2
|
-
# to modify the logging output a bit
|
|
3
|
-
require 'rest-client'
|
|
4
|
-
|
|
5
|
-
module RestClient
|
|
6
|
-
class Request
|
|
7
|
-
|
|
8
|
-
alias_method :original_log_request, :log_request
|
|
9
|
-
def log_request
|
|
10
|
-
begin
|
|
11
|
-
return unless RestClient.log
|
|
12
|
-
out = []
|
|
13
|
-
# out << "RestClient.#{method} #{redacted_url.inspect}"
|
|
14
|
-
out << "#{method.to_s.upcase} #{redacted_url.inspect}"
|
|
15
|
-
out << payload.short_inspect if payload
|
|
16
|
-
out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
|
|
17
|
-
RestClient.log << out.join(', ') + "\n"
|
|
18
|
-
rescue
|
|
19
|
-
# something went wrong, wrong gem version maybe...above is from rest-client 2.0.2
|
|
20
|
-
# do it the old way
|
|
21
|
-
original_log_request()
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
=begin
|
|
25
|
-
def log_response res
|
|
26
|
-
return unless RestClient.log
|
|
27
|
-
size = if @raw_response
|
|
28
|
-
File.size(@tf.path)
|
|
29
|
-
else
|
|
30
|
-
res.body.nil? ? 0 : res.body.size
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
|
34
|
-
end
|
|
35
|
-
=end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
1
|
+
# this does some monkey patching of third party RestClient
|
|
2
|
+
# to modify the logging output a bit
|
|
3
|
+
require 'rest-client'
|
|
4
|
+
|
|
5
|
+
module RestClient
|
|
6
|
+
class Request
|
|
7
|
+
|
|
8
|
+
alias_method :original_log_request, :log_request
|
|
9
|
+
def log_request
|
|
10
|
+
begin
|
|
11
|
+
return unless RestClient.log
|
|
12
|
+
out = []
|
|
13
|
+
# out << "RestClient.#{method} #{redacted_url.inspect}"
|
|
14
|
+
out << "#{method.to_s.upcase} #{redacted_url.inspect}"
|
|
15
|
+
out << payload.short_inspect if payload
|
|
16
|
+
out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
|
|
17
|
+
RestClient.log << out.join(', ') + "\n"
|
|
18
|
+
rescue
|
|
19
|
+
# something went wrong, wrong gem version maybe...above is from rest-client 2.0.2
|
|
20
|
+
# do it the old way
|
|
21
|
+
original_log_request()
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
=begin
|
|
25
|
+
def log_response res
|
|
26
|
+
return unless RestClient.log
|
|
27
|
+
size = if @raw_response
|
|
28
|
+
File.size(@tf.path)
|
|
29
|
+
else
|
|
30
|
+
res.body.nil? ? 0 : res.body.size
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
|
34
|
+
end
|
|
35
|
+
=end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/mtncloud/ext/string.rb
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
class String
|
|
2
|
-
|
|
3
|
-
def pluralize
|
|
4
|
-
# hacky version of this until we want to depend on ActiveSupport
|
|
5
|
-
value = self.dup
|
|
6
|
-
if value == ""
|
|
7
|
-
value
|
|
8
|
-
elsif value[-1].chr == "y"
|
|
9
|
-
value[0..-2] + "ies"
|
|
10
|
-
elsif value[-1].chr == "s"
|
|
11
|
-
if value[-2..-1] == "ss"
|
|
12
|
-
value + "es"
|
|
13
|
-
else
|
|
14
|
-
value
|
|
15
|
-
end
|
|
16
|
-
else
|
|
17
|
-
value + "s"
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def singularize
|
|
22
|
-
# hacky version of this until we want to depend on ActiveSupport
|
|
23
|
-
value = self.dup
|
|
24
|
-
if value == ""
|
|
25
|
-
value
|
|
26
|
-
elsif value.size > 3 && value[-3..-1] == "ies"
|
|
27
|
-
value[0..-4] + "y"
|
|
28
|
-
elsif value.size > 2 && value[-3..-1] == "ses"
|
|
29
|
-
value[0..-2]
|
|
30
|
-
elsif value[-1] == "s"
|
|
31
|
-
value[0..-2]
|
|
32
|
-
else
|
|
33
|
-
value
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def underscore
|
|
38
|
-
value = self.dup
|
|
39
|
-
value.gsub!(/::/, '/')
|
|
40
|
-
value.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
41
|
-
value.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
|
42
|
-
value.tr!("-", "_")
|
|
43
|
-
value.tr!(" ", "_")
|
|
44
|
-
value.downcase!
|
|
45
|
-
value
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def camelcase
|
|
49
|
-
value = self.underscore.gsub(/\_([a-z])/) do $1.upcase end
|
|
50
|
-
value = value[0, 1].downcase + value[1..-1]
|
|
51
|
-
value
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def upcamelcase
|
|
55
|
-
self.camelcase.capitalize
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def titleize
|
|
59
|
-
self.underscore.split("_").map(&:capitalize).join(" ")
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def dasherize
|
|
63
|
-
self.underscore.gsub("_", "-")
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
end
|
|
1
|
+
class String
|
|
2
|
+
|
|
3
|
+
def pluralize
|
|
4
|
+
# hacky version of this until we want to depend on ActiveSupport
|
|
5
|
+
value = self.dup
|
|
6
|
+
if value == ""
|
|
7
|
+
value
|
|
8
|
+
elsif value[-1].chr == "y"
|
|
9
|
+
value[0..-2] + "ies"
|
|
10
|
+
elsif value[-1].chr == "s"
|
|
11
|
+
if value[-2..-1] == "ss"
|
|
12
|
+
value + "es"
|
|
13
|
+
else
|
|
14
|
+
value
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
value + "s"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def singularize
|
|
22
|
+
# hacky version of this until we want to depend on ActiveSupport
|
|
23
|
+
value = self.dup
|
|
24
|
+
if value == ""
|
|
25
|
+
value
|
|
26
|
+
elsif value.size > 3 && value[-3..-1] == "ies"
|
|
27
|
+
value[0..-4] + "y"
|
|
28
|
+
elsif value.size > 2 && value[-3..-1] == "ses"
|
|
29
|
+
value[0..-2]
|
|
30
|
+
elsif value[-1] == "s"
|
|
31
|
+
value[0..-2]
|
|
32
|
+
else
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def underscore
|
|
38
|
+
value = self.dup
|
|
39
|
+
value.gsub!(/::/, '/')
|
|
40
|
+
value.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
41
|
+
value.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
|
42
|
+
value.tr!("-", "_")
|
|
43
|
+
value.tr!(" ", "_")
|
|
44
|
+
value.downcase!
|
|
45
|
+
value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def camelcase
|
|
49
|
+
value = self.underscore.gsub(/\_([a-z])/) do $1.upcase end
|
|
50
|
+
value = value[0, 1].downcase + value[1..-1]
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def upcamelcase
|
|
55
|
+
self.camelcase.capitalize
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def titleize
|
|
59
|
+
self.underscore.split("_").map(&:capitalize).join(" ")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def dasherize
|
|
63
|
+
self.underscore.gsub("_", "-")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
data/mtncloud-cli.gemspec
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require 'mtncloud/cli/version'
|
|
5
|
-
|
|
6
|
-
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name = "mtncloud-cli"
|
|
8
|
-
spec.version = MtnCloud::Cli::VERSION
|
|
9
|
-
spec.authors = ["
|
|
10
|
-
spec.summary = "Provides CLI Interface to MTN Cloud"
|
|
11
|
-
spec.description = "Cloud application management & orchestration CLI for MTN Cloud. Easily manage and orchestrate VMs on private or public infrastructure and containerized architectures."
|
|
12
|
-
spec.license = "MIT"
|
|
13
|
-
|
|
14
|
-
spec.files = `git ls-files -z`.split("\x0")
|
|
15
|
-
|
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
-
spec.require_paths = ["lib"]
|
|
19
|
-
|
|
20
|
-
spec.required_ruby_version = '>= 2.5.1' # according to http.rb doc
|
|
21
|
-
spec.add_dependency "tins", '~> 1.43.0'
|
|
22
|
-
spec.add_dependency "public_suffix", "4.0.7" # https://github.com/weppos/publicsuffix-ruby/blob/main/CHANGELOG.md
|
|
23
|
-
spec.add_dependency "ffi", "1.15.5" # Fix for mismatched deps: https://github.com/ffi/ffi/issues/1103
|
|
24
|
-
spec.add_development_dependency "bundler", ">= 2.2"
|
|
25
|
-
spec.add_development_dependency "rake"
|
|
26
|
-
spec.add_dependency 'term-ansicolor', '~> 1.3.0'
|
|
27
|
-
spec.add_dependency "rest-client", "2.0.2"
|
|
28
|
-
spec.add_dependency 'multipart-post'
|
|
29
|
-
spec.add_dependency "filesize"
|
|
30
|
-
spec.add_dependency 'mime-types'
|
|
31
|
-
spec.add_dependency "http", "5.3.1"
|
|
32
|
-
spec.add_dependency "rubyzip", '~> 2.3.2'
|
|
33
|
-
spec.add_dependency "test-unit"
|
|
34
|
-
# No longer part of Ruby's default libraries as of Ruby 4.0
|
|
35
|
-
spec.add_dependency "readline"
|
|
36
|
-
spec.add_dependency "logger"
|
|
37
|
-
spec.add_dependency "ostruct"
|
|
38
|
-
end
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'mtncloud/cli/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "mtncloud-cli"
|
|
8
|
+
spec.version = MtnCloud::Cli::VERSION
|
|
9
|
+
spec.authors = ["Joseph Henry", "Joshua Ukpozi", "MTN Cloud"]
|
|
10
|
+
spec.summary = "Provides CLI Interface to MTN Cloud"
|
|
11
|
+
spec.description = "Cloud application management & orchestration CLI for MTN Cloud. Easily manage and orchestrate VMs on private or public infrastructure and containerized architectures."
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
|
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
15
|
+
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.required_ruby_version = '>= 2.5.1' # according to http.rb doc
|
|
21
|
+
spec.add_dependency "tins", '~> 1.43.0'
|
|
22
|
+
spec.add_dependency "public_suffix", "4.0.7" # https://github.com/weppos/publicsuffix-ruby/blob/main/CHANGELOG.md
|
|
23
|
+
spec.add_dependency "ffi", "1.15.5" # Fix for mismatched deps: https://github.com/ffi/ffi/issues/1103
|
|
24
|
+
spec.add_development_dependency "bundler", ">= 2.2"
|
|
25
|
+
spec.add_development_dependency "rake"
|
|
26
|
+
spec.add_dependency 'term-ansicolor', '~> 1.3.0'
|
|
27
|
+
spec.add_dependency "rest-client", "2.0.2"
|
|
28
|
+
spec.add_dependency 'multipart-post'
|
|
29
|
+
spec.add_dependency "filesize"
|
|
30
|
+
spec.add_dependency 'mime-types'
|
|
31
|
+
spec.add_dependency "http", "5.3.1"
|
|
32
|
+
spec.add_dependency "rubyzip", '~> 2.3.2'
|
|
33
|
+
spec.add_dependency "test-unit"
|
|
34
|
+
# No longer part of Ruby's default libraries as of Ruby 4.0
|
|
35
|
+
spec.add_dependency "readline"
|
|
36
|
+
spec.add_dependency "logger"
|
|
37
|
+
spec.add_dependency "ostruct"
|
|
38
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
require 'mtncloud'
|
|
4
|
+
|
|
5
|
+
# Offline tests for how login explains the username format.
|
|
6
|
+
# Sub-tenant users log in as {subdomain}\{username} or {accountID}\{username}.
|
|
7
|
+
class LoginHelpTest < Test::Unit::TestCase
|
|
8
|
+
|
|
9
|
+
def capture_stdout
|
|
10
|
+
saved = $stdout
|
|
11
|
+
$stdout = StringIO.new
|
|
12
|
+
yield
|
|
13
|
+
$stdout.string
|
|
14
|
+
ensure
|
|
15
|
+
$stdout = saved
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Captured once: the CLI keeps the stdout it first printed to, so a second
|
|
19
|
+
# --help in the same process would print to the first test's StringIO.
|
|
20
|
+
def login_help
|
|
21
|
+
@@login_help ||= capture_stdout do
|
|
22
|
+
error = assert_raise(SystemExit) { MtnCloud::Cli::Login.new.handle(['--help']) }
|
|
23
|
+
assert_equal 0, error.status
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_help_documents_both_sub_tenant_username_formats
|
|
28
|
+
output = login_help
|
|
29
|
+
assert_include output, '{subdomain}\\{username}'
|
|
30
|
+
assert_include output, '{accountID}\\{username}'
|
|
31
|
+
assert_include output, 'Example: acme\\neo'
|
|
32
|
+
assert_include output, 'Example: 2\\neo'
|
|
33
|
+
assert_include output, 'Master tenant users enter only their username.'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_username_option_mentions_subdomain_and_account_id
|
|
37
|
+
output = login_help
|
|
38
|
+
assert_include output, '--username USERNAME'
|
|
39
|
+
assert_include output, 'Sub-tenant users prefix it with their subdomain or account ID'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_prompt_hint_names_both_formats
|
|
43
|
+
hint = MtnCloud::Cli::Credentials::USERNAME_FORMAT
|
|
44
|
+
assert_include hint, 'subdomain\\username'
|
|
45
|
+
assert_include hint, 'accountID\\username'
|
|
46
|
+
end
|
|
47
|
+
end
|