enfcli 3.3.2.pre.alpha

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.
data/enfcli.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'enfcli/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "enfcli"
8
+ spec.version = EnfCli::VERSION
9
+ spec.authors = ["Venkatakumar Srinivasan"]
10
+ spec.email = ["venkat@xaptum.com"]
11
+
12
+ spec.summary = %q{Xaptum ENF CLI.}
13
+ spec.homepage = "https://www.xaptum.com"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ #if spec.respond_to?(:metadata)
18
+ # spec.metadata['allowed_push_host'] = "https://gems.xaptum.xyz"
19
+ #else
20
+ # raise "RubyGems 2.0 or newer is required to protect against " \
21
+ # "public gem pushes."
22
+ #end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+ spec.bindir = "bin"
28
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency 'thor', '~> 0.20.0'
32
+ spec.add_dependency "rest-client", "~> 2.0"
33
+ spec.add_dependency "terminal-table"
34
+ #spec.add_dependency "websocket-client-simple"
35
+ #spec.add_dependency "colorize"
36
+
37
+ spec.add_development_dependency "bundler"
38
+ spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "bump"
40
+ spec.add_development_dependency "rspec"
41
+ spec.add_development_dependency "rspec_junit_formatter"
42
+ spec.add_development_dependency "fuubar"
43
+ spec.add_development_dependency "vcr"
44
+ spec.add_development_dependency "webmock"
45
+
46
+ end
data/lib/enfapi.rb ADDED
@@ -0,0 +1,398 @@
1
+ #
2
+ # Copyright 2018 Xaptum,Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ require 'rest-client'
17
+ require 'singleton'
18
+ require 'json'
19
+ require 'date'
20
+ require 'base64'
21
+
22
+ #
23
+ # NOTE: When api's are refactored into individual classes
24
+ # please be sure to override to_json method of the Singleton
25
+ # class OR replace all calls to to_json() with EnfApi::to_json()
26
+ #
27
+
28
+ module EnfApi
29
+
30
+ def self.to_json(hash)
31
+ JSON.generate(hash)
32
+ end
33
+
34
+ class ERROR < StandardError
35
+ def initialize(msg = "ENF Api Error")
36
+ super
37
+ end
38
+ end
39
+
40
+ class Firewall
41
+ include Singleton
42
+
43
+ def list_firewall_rules(network)
44
+ EnfApi::API.instance.get "/api/xfw/v1/#{network}/rule"
45
+ end
46
+
47
+ def add_firewall_rule(network, rule)
48
+ rule_json = EnfApi::to_json(rule)
49
+ EnfApi::API.instance.post "/api/xfw/v1/#{network}/rule", rule_json
50
+ end
51
+
52
+ def delete_firewall_rules(network, id = nil)
53
+ # Same method to call to delete all firewall rules in a network. if id is nil
54
+ EnfApi::API.instance.delete "/api/xfw/v1/#{network}/rule/#{id}"
55
+ end
56
+ end
57
+
58
+ class Iam
59
+ include Singleton
60
+
61
+ ## NO_TEST
62
+ def provision_group(new_group)
63
+ json = EnfApi::to_json(new_group)
64
+ EnfApi::API.instance.post "/api/xiam/v1/groups", json
65
+ end
66
+
67
+ ## NO_TEST
68
+ def update_group(gid, modified_group)
69
+ json = EnfApi::to_json(modified_group)
70
+ EnfApi::API.instance.put "/api/xiam/v1/groups/#{gid}", json
71
+ end
72
+
73
+ ## NO_TEST
74
+ def add_group_to_network(gid, ipv6_network)
75
+ json = EnfApi::to_json(ipv6_network)
76
+ EnfApi::API.instance.post "/api/xiam/v1/groups/#{gid}/networks", json
77
+ end
78
+
79
+ ## NO_TEST
80
+ def list_network_groups(network)
81
+ EnfApi::API.instance.get "/api/xiam/v1/networks/#{network}/groups"
82
+ end
83
+
84
+ ## NO_TEST
85
+ def list_group(gid)
86
+ EnfApi::API.instance.get "/api/xiam/v1/groups/#{gid}"
87
+ end
88
+
89
+ ## NO_TEST
90
+ def provision_endpoint(new_endpoint)
91
+ json = EnfApi::to_json(new_endpoint)
92
+ EnfApi::API.instance.post "/api/xiam/v1/endpoints", json
93
+ end
94
+
95
+ ## NO_TEST
96
+ def update_endpoint_key(ipv6, credentials)
97
+ json = EnfApi::to_json(credentials)
98
+ EnfApi::API.instance.post "/api/xiam/v1/endpoints/#{ipv6}/credentials", json
99
+ end
100
+
101
+ end
102
+
103
+ class NetworkManager
104
+ include Singleton
105
+ end
106
+
107
+ class API
108
+ include Singleton
109
+
110
+ attr_accessor :headers, :api
111
+
112
+ def authenticate(host, user, password)
113
+ # format url
114
+ host = "https://#{host}" unless host =~ /^(http|https):\/\//
115
+
116
+ # chomp trailing '/'
117
+ host = host.chomp("/")
118
+
119
+ # Initalize rest client
120
+ @api = RestClient::Resource.new(host)
121
+
122
+ # Create request json
123
+ hash = { :username => user, :token => password }
124
+ json = to_json( hash )
125
+
126
+ # call api
127
+ @api["/api/xcr/v2/xauth"].post( json, {content_type: :json, accept: :json}) { |response, request, result|
128
+ case response.code
129
+ when 200
130
+ # get resp from json
131
+ resp = from_json(response.body)
132
+
133
+ # set request headers for subsequent api calls
134
+ token = resp[:data][0][:token]
135
+ @headers = {content_type: :json, accept: :json, "Authorization" => "Bearer #{token}"}
136
+
137
+ # return resp
138
+ resp
139
+ else
140
+ raise EnfApi::ERROR, "Login Failed! Invalid user or password!"
141
+ end
142
+ }
143
+ end
144
+
145
+ def get(request_uri)
146
+ @api[request_uri].get(@headers) {|response, request, result|
147
+ process_api_response response, request, result
148
+ }
149
+ end
150
+
151
+ def post(request_uri, request_body = '')
152
+ @api[request_uri].post(request_body, @headers) {|response, request, result|
153
+ process_api_response response, request, result
154
+ }
155
+ end
156
+
157
+ def put(request_uri, request_body = '')
158
+ @api[request_uri].put(request_body, @headers) {|response, request, result|
159
+ process_api_response response, request, result
160
+ }
161
+ end
162
+
163
+ def delete(request_uri)
164
+ @api[request_uri].delete(@headers) {|response, request, result|
165
+ process_api_response response, request, result
166
+ }
167
+ end
168
+
169
+ ############################################################################################################################
170
+ # NOT READY API. MOVE ABOVE THIS LINE AFTER RSPEC OR INTO OWN CLASS
171
+ ############################################################################################################################
172
+ def list_domain_nws(domain_id)
173
+ @api["/api/xcr/v2/domains/#{domain_id}/nws"].get(@headers) {|response, request, result|
174
+ process_api_response response, request, result
175
+ }
176
+ end
177
+
178
+ def list_enfnws
179
+ @api["/api/xcr/v2/enfnws"].get( @headers) {|response, request, result|
180
+ process_api_response response, request, result
181
+ }
182
+ end
183
+
184
+ def list_domains
185
+ @api["/api/xcr/v2/domains"].get( @headers) {|response, request, result|
186
+ process_api_response response, request, result
187
+ }
188
+ end
189
+
190
+ def get_domain(domain_id)
191
+ @api["/api/xcr/v2/domains/#{domain_id}"].get( @headers) {|response, request, result|
192
+ process_api_response response, request, result
193
+ }
194
+ end
195
+
196
+ def create_domain(domain_hash)
197
+ json = to_json( domain_hash )
198
+ @api["/api/xcr/v2/domains"].post( json, @headers) {|response, request, result|
199
+ process_api_response response, request, result
200
+ }
201
+ end
202
+
203
+ def update_domain_rate_limits(domain_network, limit, limits_hash)
204
+ json = to_json( limits_hash )
205
+ @api["/api/xcr/v2/domains/#{domain_network}/ep_rate_limits/#{limit}"].put( json, @headers) {|response, request, result|
206
+ process_api_response response, request, result
207
+ }
208
+ end
209
+
210
+ def update_network_rate_limits(network, limit, limits_hash)
211
+ json = to_json( limits_hash )
212
+ @api["/api/xcr/v2/nws/#{network}/ep_rate_limits/#{limit}"].put( json, @headers) {|response, request, result|
213
+ process_api_response response, request, result
214
+ }
215
+ end
216
+
217
+ def update_ep_rate_limits(ipv6, limit, limits_hash)
218
+ json = to_json( limits_hash )
219
+ @api["/api/xcr/v2/cxns/#{ipv6}/ep_rate_limits/#{limit}"].put( json, @headers) {|response, request, result|
220
+ process_api_response response, request, result
221
+ }
222
+ end
223
+
224
+ def get_domain_rate_limits(domain_network, filter = nil)
225
+ api_url = "/api/xcr/v2/domains/#{domain_network}/ep_rate_limits"
226
+ api_url = "#{api_url}/#{filter}" if filter
227
+
228
+ @api[api_url].get( @headers) {|response, request, result|
229
+ process_api_response response, request, result
230
+ }
231
+ end
232
+
233
+ def get_network_rate_limits(network, filter = nil)
234
+ api_url = "/api/xcr/v2/nws/#{network}/ep_rate_limits"
235
+ api_url = "#{api_url}/#{filter}" if filter
236
+
237
+ @api[api_url].get( @headers) {|response, request, result|
238
+ process_api_response response, request, result
239
+ }
240
+ end
241
+
242
+ def get_ep_rate_limits(ipv6, filter = nil)
243
+ api_url = "/api/xcr/v2/cxns/#{ipv6}/ep_rate_limits"
244
+ api_url = "#{api_url}/#{filter}" if filter
245
+
246
+ @api[api_url].get( @headers) {|response, request, result|
247
+ process_api_response response, request, result
248
+ }
249
+ end
250
+
251
+ def activate_domain(domain_network)
252
+ @api["/api/xcr/v2/domains/#{domain_network}/status"].put( '', @headers) {|response, request, result|
253
+ process_api_response response, request, result
254
+ }
255
+ end
256
+
257
+ def deactivate_domain(domain_network)
258
+ @api["/api/xcr/v2/domains/#{domain_network}/status"].delete( @headers) {|response, request, result|
259
+ process_api_response response, request, result
260
+ }
261
+ end
262
+
263
+ def create_nw(nw_hash)
264
+ json = to_json(nw_hash)
265
+ domain_id = nw_hash[:domain_id]
266
+ @api["/api/xcr/v2/domains/#{domain_id}/nws"].post(json, @headers) {|response, request, result|
267
+ process_api_response response, request, result
268
+ }
269
+ end
270
+
271
+ def list_nw_connections(domain_id, network_cidr, file = nil)
272
+ @api["/api/xcr/v2/domains/#{domain_id}/nws/#{network_cidr}/cxns"].get(@headers) {|response, request, result|
273
+ process_api_response response, request, result
274
+ }
275
+ end
276
+
277
+ def list_endpoint_events(ipv6)
278
+ @api["/api/xcr/v2/cxns/#{ipv6}/events"].get(@headers) {|response, request, result|
279
+ process_api_response response, request, result
280
+ }
281
+ end
282
+
283
+ def list_network_events(network_cidr)
284
+ @api["/api/xcr/v2/nws/#{network_cidr}/events"].get(@headers) {|response, request, result|
285
+ process_api_response response, request, result
286
+ }
287
+ end
288
+
289
+ def get_endpoint(ipv6)
290
+ @api["/api/xcr/v2/cxns/#{ipv6}"].get(@headers) {|response, request, result|
291
+ process_api_response response, request, result
292
+ }
293
+ end
294
+
295
+ def update_endpoint(ipv6, name)
296
+ hash = { :ipv6 => ipv6, :name => name }
297
+ json = to_json( hash )
298
+
299
+ @api["/api/xcr/v2/cxns/#{ipv6}"].put( json, @headers) {|response, request, result|
300
+ process_api_response response, request, result
301
+ }
302
+ end
303
+
304
+ def activate_enfnw(subnet)
305
+ @api["/api/xcr/v2/enfnws/#{subnet}"].put( '', @headers) {|response, request, result|
306
+ process_api_response response, request, result
307
+ }
308
+ end
309
+
310
+ def list_domain_users(domain_network)
311
+ @api["/api/xcr/v2/domains/#{domain_network}/users"].get( @headers) {|response, request, result|
312
+ puts response.code
313
+ process_api_response response, request, result
314
+ }
315
+ end
316
+
317
+ def list_domain_invites(domain_network)
318
+ @api["/api/xcr/v2/domains/#{domain_network}/invites"].get(@headers) {|response, request, result|
319
+ process_api_response response, request, result
320
+ }
321
+ end
322
+
323
+ def invite(domain_network, hash)
324
+ json = to_json( hash )
325
+ @api["/api/xcr/v2/domains/#{domain_network}/invites"].post( json, @headers) { |response, request, result|
326
+ process_api_response response, request, result
327
+ }
328
+ end
329
+
330
+ def cancel_invite(email)
331
+ @api["/api/xcr/v2/invites/#{email}"].delete( @headers) {|response, request, result|
332
+ process_api_response response, request, result
333
+ }
334
+ end
335
+
336
+ def resend_invite(email)
337
+ @api["/api/xcr/v2/invites/#{email}"].put( "{}", @headers) {|response, request, result|
338
+ process_api_response response, request, result
339
+ }
340
+ end
341
+
342
+ ############################################################################################################################
343
+ # Private functions
344
+ ############################################################################################################################
345
+ def process_api_response(response, request, result)
346
+ case response.code
347
+ when 200
348
+ # return json
349
+ from_json(response.body)
350
+
351
+ when 201
352
+ # return response body
353
+ from_json(response.body)
354
+
355
+ when 400
356
+ # api returns and error
357
+ raise EnfApi::ERROR, api_error_msg(from_json(response.body))
358
+
359
+ when 401
360
+ # api returns and error
361
+ raise EnfApi::ERROR, api_error_msg(from_json(response.body))
362
+
363
+ when 403
364
+ # api returns and error
365
+ raise EnfApi::ERROR, "AUTHORIZATION_ERROR: User is not authorized to perform this operation!"
366
+
367
+ else
368
+ raise EnfApi::ERROR, "Unexpected error! Please try again!"
369
+ end
370
+ end
371
+
372
+ def from_json(string)
373
+ begin
374
+ JSON.parse(string, {:symbolize_names => true})
375
+ rescue
376
+ raise EnfApi::ERROR, "Unable to parse api response json!"
377
+ end
378
+ end
379
+
380
+ def to_json(hash)
381
+ JSON.generate(hash)
382
+ end
383
+
384
+ def api_error_msg(err)
385
+ if err.key?(:xiam_error)
386
+ reason = err[:reason]
387
+ reason = Base64.decode64(reason)
388
+ "#{reason}"
389
+ else
390
+ code = err[:error][:code]
391
+ text = err[:error][:text]
392
+ "#{code.upcase}: #{text}"
393
+ end
394
+ end
395
+
396
+ private :from_json, :to_json, :api_error_msg, :process_api_response
397
+ end
398
+ end
@@ -0,0 +1,185 @@
1
+ #
2
+ # Copyright 2018 Xaptum,Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ require 'enfthor'
17
+ require 'enfapi'
18
+
19
+ module EnfCli
20
+
21
+ module Cmd
22
+
23
+ class User < EnfThor
24
+ no_commands {
25
+ def display_invites invites
26
+ headings = ['Id', 'User Name', 'Full Name', 'Invited By', 'Invite Code']
27
+ rows = invites.map{ |hash|
28
+ [ hash[:id], hash[:email], hash[:name], hash[:invited_by], hash[:invite_token] ]
29
+ }
30
+
31
+ render_table(headings, rows)
32
+ end
33
+
34
+ def display_users users
35
+ headings = ['User Name', 'Full Name', 'Last Login', 'Type', 'Reset Code', 'Reset Time']
36
+ rows = users.map{ |hash|
37
+ [ hash[:username], hash[:full_name], hash[:last_login], hash[:type], hash[:reset_code], format_date(hash[:reset_time])]
38
+ }
39
+ render_table(headings, rows)
40
+ end
41
+
42
+ def send_invite options, user_type
43
+ # Get options
44
+ domain_network = options.domain
45
+
46
+ # Get user role
47
+ user_role = EnfCli::CTX.instance.session[:type]
48
+
49
+ # check user roles
50
+ if user_role == "XAPTUM_ADMIN"
51
+ # make sure that a domain is specified in domain option for XAPTUM_ADMIN
52
+ raise EnfCli::ERROR, "No domain specified. Please use --domain option!" unless domain_network
53
+ else
54
+ # use the domain network of the user
55
+ domain_network = EnfCli::CTX.instance.session[:domain_network]
56
+ raise EnfCli::ERROR, "User not in a valid domain!" unless domain_network
57
+ end
58
+
59
+ # get params
60
+ case user_type
61
+ when "DOMAIN_USER"
62
+ name = options[:'name'].join(" ").gsub(/\A"+(.*?)"+\Z/m, '\1')
63
+ email = options[:'email']
64
+
65
+ when "DOMAIN_ADMIN"
66
+ name = options[:'admin-name'].join(" ").gsub(/\A"+(.*?)"+\Z/m, '\1')
67
+ email = options[:'admin-email']
68
+
69
+ end
70
+
71
+ # call api
72
+ hash = { :email => email, :full_name => name, :welcome_text => "", :user_type => user_type }
73
+ data = EnfApi::API.instance.invite domain_network, hash
74
+ invite = data[:data]
75
+ display_invites invite
76
+ end
77
+ }
78
+
79
+ desc "invite-read-only-user", "Invite a domain user"
80
+ method_option :domain, :default => nil, :type => :string, :aliases => "-d"
81
+ method_option :'name', :type => :array, :required => true, :banner => "NAME"
82
+ method_option :'email', :type => :string, :required => true, :banner => "EMAIL"
83
+ def invite_read_only_user
84
+ try_with_rescue_in_session do
85
+ send_invite options, "DOMAIN_USER"
86
+ end
87
+ end
88
+
89
+ desc "invite-admin-user", "Invite a domain administrator"
90
+ method_option :domain, :default => nil, :type => :string, :aliases => "-d"
91
+ method_option :'admin-name', :type => :array, :required => true, :banner => "NAME"
92
+ method_option :'admin-email', :type => :string, :required => true, :banner => "EMAIL"
93
+ def invite_admin_user
94
+ try_with_rescue_in_session do
95
+ send_invite options, "DOMAIN_ADMIN"
96
+ end
97
+ end
98
+
99
+
100
+ desc "cancel-user-invite", "Cancel an invite"
101
+ method_option :email, :type => :string, :required => true
102
+ def cancel_user_invite
103
+ try_with_rescue_in_session do
104
+ # call api
105
+ EnfApi::API.instance.cancel_invite options.email
106
+
107
+ # print success
108
+ say "Invite Canceled!", :green
109
+ end
110
+ end
111
+
112
+ desc "resend-user-invite", "Resend an invite"
113
+ method_option :email, :type => :string, :required => true
114
+ def resend_user_invite
115
+ try_with_rescue_in_session do
116
+ # call api
117
+ EnfApi::API.instance.resend_invite options.email
118
+
119
+ # print success
120
+ say "Resent invite email!", :green
121
+ end
122
+ end
123
+
124
+ desc "list-domain-invites", "List domain invites"
125
+ method_option :domain, :default => nil, :type => :string, :aliases => "-d"
126
+ def list_domain_invites
127
+ try_with_rescue_in_session do
128
+ # Get options
129
+ domain_network = options.domain
130
+
131
+ # Get user role
132
+ user_role = EnfCli::CTX.instance.session[:type]
133
+
134
+ # check user roles
135
+ if user_role == "XAPTUM_ADMIN"
136
+ # make sure that a domain is specified in domain option for XAPTUM_ADMIN
137
+ raise EnfCli::ERROR, "No domain specified. Please use --domain option!" unless domain_network
138
+ else
139
+ # use the domain network of the user
140
+ domain_network = EnfCli::CTX.instance.session[:domain_network]
141
+ raise EnfCli::ERROR, "User not in a valid domain!" unless domain_network
142
+ end
143
+
144
+ # call the api
145
+ data = EnfApi::API.instance.list_domain_invites domain_network
146
+ invites = data[:data]
147
+
148
+ display_invites invites
149
+ end
150
+ end
151
+
152
+ desc "list-domain-users", "List domain users"
153
+ method_option :domain, :default => nil, :type => :string, :aliases => "-d"
154
+ def list_domain_users
155
+ try_with_rescue_in_session do
156
+ # Get options
157
+ domain_network = options.domain
158
+
159
+ # Get user role
160
+ user_role = EnfCli::CTX.instance.session[:type]
161
+
162
+ # check user roles
163
+ if user_role == "XAPTUM_ADMIN"
164
+ # make sure that a domain is specified in domain option for XAPTUM_ADMIN
165
+ raise EnfCli::ERROR, "No domain specified. Please use --domain option!" unless domain_network
166
+ else
167
+ # use the domain network of the user
168
+ domain_network = EnfCli::CTX.instance.session[:domain_network]
169
+ raise EnfCli::ERROR, "User not in a valid domain!" unless domain_network
170
+ end
171
+
172
+ # call the api
173
+ data = EnfApi::API.instance.list_domain_users domain_network
174
+ users = data[:data]
175
+
176
+ display_users users
177
+ end
178
+ end
179
+
180
+
181
+ end
182
+
183
+ end
184
+
185
+ end