nex_client 0.17.0 → 0.18.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -42,22 +42,6 @@ module NexClient
42
42
  self.display_cubes(e)
43
43
  end
44
44
 
45
- # Retrieve application logs from all containers
46
- def self.logs(args,opts)
47
- name = args.first
48
- e = NexClient::CubeInstance.find(uuid: name).first
49
-
50
- # Display error
51
- unless e
52
- error("Error! Could not find cube: #{name}")
53
- return false
54
- end
55
-
56
- # Retrieve logs and display them
57
- logs = e.logs(tail: opts.tail).first
58
- self.display_logs(logs.log_ret)
59
- end
60
-
61
45
  def self.snapshots(args,opts)
62
46
  name = args.first
63
47
  e = NexClient::CubeInstance.find(uuid: name).first
@@ -232,11 +216,6 @@ module NexClient
232
216
  "#{o.type.singularize}:#{o.name}"
233
217
  end
234
218
 
235
- def self.display_logs(logs)
236
- puts logs
237
- puts "\n"
238
- end
239
-
240
219
  def self.display_snapshots(snapshots)
241
220
  table = Terminal::Table.new title: SNAPSHOTS_TITLE, headings: SNAPSHOTS_HEADERS do |t|
242
221
  [snapshots].flatten.compact.each do |e|
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NexClient
4
+ module Commands
5
+ module Events
6
+ def self.configure(c, klass)
7
+ entity = klass.entity_name
8
+ entities = klass.entities_name
9
+
10
+ c.syntax = "nex-cli #{entities}:events APP_NAME [options]"
11
+ c.summary = "Gather system events for a given #{entity}"
12
+ c.description = "Gather system events for a given #{entity}"
13
+ c.example "display events for my#{entity}", "nex-cli #{entities}:events my#{entity}"
14
+ c.example "display 100 events for my#{entity}", "nex-cli #{entities}:events --tail 100 my#{entity}"
15
+ c.option '--tail NUMBER', String, 'number of events to retrieve (default: 50)'
16
+ c.option '--type TYPE', String, 'filter events on type (e.g. status, container)'
17
+ c.action do |args, options|
18
+ manage(args, options, klass)
19
+ end
20
+ end
21
+
22
+ # Retrieve resource events
23
+ def self.manage(args, opts, _klass)
24
+ filters = {}
25
+ filters[:'source.name'] = args.first
26
+ filters[:event] = opts.type if opts.type
27
+ tail_size = (opts.tail || 50).to_i
28
+
29
+ events = NexClient::Event.where(filters).order(id: :desc)
30
+ list = events.to_a
31
+ while list.count < tail_size
32
+ events = events.pages.next
33
+ break unless events
34
+ list |= events.to_a
35
+ end
36
+ list = list.first(tail_size)
37
+
38
+ display_events(list.to_a)
39
+ end
40
+
41
+ # Display a list of events
42
+ def self.display_events(events)
43
+ # Content
44
+ events.sort_by { |e| e.id.to_i }.each do |e|
45
+ username = e&.username || 'system'
46
+ session_id = e&.session_id || '-'
47
+ puts [
48
+ e.created_at,
49
+ e.event.ljust(12,' '),
50
+ username.ljust(15,' '),
51
+ session_id.ljust(6,' '),
52
+ e.level.ljust(6,' '),
53
+ e.message
54
+ ].join(" | ")
55
+ end
56
+ puts "\n"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -3,7 +3,6 @@
3
3
  module NexClient
4
4
  module Commands
5
5
  module Helpers
6
- LOG_COLORS = [:cyan,:green,:red,:yellow,:magenta]
7
6
  VARS_TITLE = "Environment Variables".colorize(:blue)
8
7
  VARS_HEADERS = ['key','value'].map(&:upcase)
9
8
 
@@ -21,55 +20,6 @@ module NexClient
21
20
  puts msg.colorize(:red)
22
21
  end
23
22
 
24
- def display_logs(logs)
25
- color_index = 0
26
- logs.each do |container_id,log_lines|
27
- color_index = (color_index + 1) % LOG_COLORS.size
28
- puts "\n"
29
- puts "Node: #{container_id}".colorize(LOG_COLORS[color_index])
30
- puts "-"*50
31
- puts log_lines.join("\n")
32
- end
33
- puts "\n"
34
- end
35
-
36
- # Retrieve resource events
37
- def events(args,opts)
38
- filters = {}
39
- filters[:'source.name'] = args.first
40
- filters[:event] = opts.type if opts.type
41
- tail_size = (opts.tail || 50).to_i
42
-
43
- events = NexClient::Event.where(filters).order(id: :desc)
44
- list = events.to_a
45
- while list.count < tail_size
46
- events = events.pages.next
47
- break unless events
48
- list |= events.to_a
49
- end
50
- list = list.first(tail_size)
51
-
52
- self.display_events(list.to_a)
53
- end
54
-
55
- # Display a list of events
56
- def display_events(events)
57
- # Content
58
- events.sort_by { |e| e.id.to_i }.each do |e|
59
- username = e&.username || 'system'
60
- session_id = e&.session_id || '-'
61
- puts [
62
- e.created_at,
63
- e.event.ljust(12,' '),
64
- username.ljust(15,' '),
65
- session_id.ljust(6,' '),
66
- e.level.ljust(6,' '),
67
- e.message
68
- ].join(" | ")
69
- end
70
- puts "\n"
71
- end
72
-
73
23
  def with_cert_identity
74
24
  # Fetch user
75
25
  me = NexClient::Me.find.first
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NexClient
4
+ module Commands
5
+ module IpWhitelisting
6
+ extend Helpers
7
+
8
+ IP_RULES_TITLE = "IP Rules".colorize(:cyan)
9
+ IP_RULES_HEADERS = ['Pattern', 'Action', 'CIDRS'].map(&:upcase)
10
+
11
+ def self.configure(c)
12
+ c.syntax = 'nex-cli apps:ip-rules APP_NAME [options]'
13
+ c.summary = 'Manage Application Endpoint IP whitelisting'
14
+ c.description = <<~HEREDOC
15
+ Manage Application Endpoint IP whitelisting
16
+
17
+ Specify a JSON (.json) or YAML (.yml) file describing the security rules to apply/ignore. If no files are specified you will be prompted to
18
+ paste rules in JSON format in the terminal.
19
+
20
+ Security rules are a list of regex patterns allowing or denying access to a specific endpoint based on an IP range (CIDR) in the following format:
21
+ { "pattern": "<regex_matching_a_path>", "cidrs": ["<ip_range_1>","<ip_range_2>"], "action": "<allow|deny>" }
22
+
23
+ Rules must be matched:
24
+ ----------------------
25
+ Rules must be matched (path + cidr) to be applied. Therefore you may want to have a "deny all" rule as a last rule if your application is private.
26
+
27
+ Rules order matter:
28
+ -------------------
29
+ Rules are matched in order. The first rule matched for a given [path, cidr] will be applied. A "deny all" rule should always be last.
30
+
31
+ Regex Patterns:
32
+ ---------------
33
+ Regex patterns must be Lua-compatible because these patterns ultimately get parsed by the routing infrastructure (nginx/lua). You can test your lua
34
+ pattern in the Lua online console to confirm it does what you want.
35
+ 1) Go to: https://www.lua.org/cgi-bin/demo
36
+ 2) Run the following in the console (example): return string.match("/api/v1/ping","^/api/v1")
37
+
38
+ Ruleset Example:
39
+ ----------------
40
+ Make your application only accessible from specific IP addresses but leave the /ping endpoint public
41
+ [
42
+ # Allow ALL IP addresses to access the ping endpoint
43
+ { "pattern": "^/ping", "cidrs": ["0.0.0.0/0"], "action": "allow" },
44
+
45
+ # Allow specific IP addresses and IP ranges to access your application
46
+ { "pattern": ".*", "cidrs": ["55.54.52.53/32", "80.70.60.0/24"], "action": "allow" },
47
+
48
+ # "Deny all" rule - All other IPs will be rejected on all endpoints
49
+ { "pattern": ".*", "cidrs": ["0.0.0.0/0"], "action": "deny" }
50
+ ]
51
+
52
+ HEREDOC
53
+ c.example 'update IP rules via command line prompt', 'nex-cli apps:ip-rules myapp --ruleset'
54
+ c.example 'update IP rules via file input', 'nex-cli apps:ip-rules myapp --ruleset /tmp/myruleset.json'
55
+ c.option '--ruleset [PATH]', String, 'specify web application IP rules using JSON or YAML file (prompt will appear otherwise). [restart required]'
56
+ c.option '--clear-ruleset', String, 'remove all IP rules'
57
+
58
+ c.action do |args, options|
59
+ manage(args, options)
60
+ end
61
+ end
62
+
63
+ def self.manage(args, opts)
64
+ name = args.first
65
+ app = NexClient::App.find(name: name).first
66
+
67
+ # Display error
68
+ unless app
69
+ error("Error! Could not find app: #{name}")
70
+ return false
71
+ end
72
+
73
+ a = update(app, opts)
74
+ show(a)
75
+ end
76
+
77
+ def self.update(app, opts)
78
+ # Deep duplicate
79
+ app_opts = Marshal.load(Marshal.dump((app.opts || {})))
80
+
81
+ # Clear all constraints
82
+ if opts.clear_ruleset
83
+ app_opts.delete('ip_whitelisting_rules')
84
+ end
85
+
86
+ # Add IP rules
87
+ if opts.ruleset.present?
88
+ ip_rules = begin
89
+ if opts.ruleset.is_a?(String)
90
+ hash_from_file(opts.ruleset)
91
+ else
92
+ val = ask("Copy/paste your IP rules below in JSON format:") { |q| q.gather = "" }
93
+ JSON.parse(val.join(""))
94
+ end
95
+ end
96
+
97
+ app_opts['ip_whitelisting_rules'] = ip_rules
98
+ end
99
+
100
+ # Update policies
101
+ if app.opts != app_opts
102
+ app.update_attributes({ opts: app_opts })
103
+ success("Successfully updated IP rules. Please restart your app to apply these changes...")
104
+ end
105
+
106
+ # Return updated app
107
+ app
108
+ end
109
+
110
+ def self.show(app)
111
+ ruleset = (app.opts['ip_whitelisting_rules'] || [])
112
+
113
+ table = Terminal::Table.new title: IP_RULES_TITLE, headings: IP_RULES_HEADERS do |t|
114
+ ruleset.each do |rule|
115
+ t.add_row([rule['pattern'], rule['action'], rule['cidrs']&.join(' , ')])
116
+ end
117
+ end
118
+ puts table
119
+ puts "\n"
120
+ end
121
+
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NexClient
4
+ module Commands
5
+ module Logs
6
+ LOG_COLORS = [:cyan, :green, :red, :yellow, :magenta].freeze
7
+
8
+ def self.configure(c, klass)
9
+ entity = klass.entity_name
10
+ entities = klass.entities_name
11
+
12
+ c.syntax = "nex-cli #{entities}:logs APP_NAME [options]"
13
+ c.summary = "Gather #{entity} logs"
14
+ c.description = "Gather container logs for a given #{entity}"
15
+ c.example "gather logs for my#{entity}", "nex-cli #{entities}:logs my#{entity}"
16
+ c.example "gather logs for my#{entity} with a tail of 50", "nex-cli #{entities}:logs --tail 50 my#{entity}"
17
+ c.option '--tail NUMBER', String, 'number of lines to retrieve for each container'
18
+ c.action do |args, options|
19
+ manage(args, options, klass)
20
+ end
21
+ end
22
+
23
+ # Retrieve resource events
24
+ def self.manage(args, opts, klass)
25
+ name = args.first
26
+ cluster = klass.find(klass.main_key => name).first
27
+
28
+ # Display error
29
+ unless cluster
30
+ error("Error! Could not find #{klass.entity_name}: #{name}")
31
+ return false
32
+ end
33
+
34
+ # Retrieve logs and display them
35
+ logs = cluster.logs(tail: opts.tail).first
36
+ self.display_logs(logs.log_ret)
37
+ end
38
+
39
+ def display_logs(logs)
40
+ color_index = 0
41
+
42
+ if logs.is_a?(Hash)
43
+ logs.each do |container_id,log_lines|
44
+ color_index = (color_index + 1) % LOG_COLORS.size
45
+ puts "\n"
46
+ puts "Node: #{container_id}".colorize(LOG_COLORS[color_index])
47
+ puts "-"*50
48
+ puts log_lines.join("\n")
49
+ end
50
+ else
51
+ puts logs
52
+ end
53
+
54
+ puts "\n"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NexClient
4
+ module Commands
5
+ module Policies
6
+ REGION_BALANCING_TITLE = "Region Balancing".colorize(:cyan)
7
+ REGION_BALANCING_HEADERS = ['Region','Rule'].map(&:upcase)
8
+
9
+ CONSTRAINTS_TITLE = "Constraints".colorize(:red)
10
+ CONSTRAINTS_HEADERS = ['Type','Attribute','Value'].map(&:upcase)
11
+
12
+ def self.configure(c, klass)
13
+ entity = klass.entity_name
14
+ entities = klass.entities_name
15
+
16
+ c.syntax = "nex-cli #{entities}:policies #{entity.upcase}_NAME [options]"
17
+ c.summary = "Display and manage #{entity} region-balancing policies and deployment constraints"
18
+ c.description = <<~HEREDOC
19
+ Constraints:
20
+ ------------
21
+ Use: --constraints CONSTRAINTS
22
+ Use: --clear-constraints
23
+ Specify compute or storage attributes to constrain the deployment of #{entity} containers to specific racks.
24
+
25
+ For example if you wish to restrict the deployment of #{entity} containers to servers using a specific security group,
26
+ you can run the following command:
27
+ E.g.:
28
+ nex-cli #{entities}:policies my#{entity} --constraints compute.network_security_group=my-security-group
29
+
30
+ You can define multiple constraints - in which case the rules will be additive (logical AND). If you wish to rebuild the constraints
31
+ from scratch you can clear the constraints (--clear-constraints) then re-add constraints (--rack-contraint)
32
+ E.g.:
33
+ nex-cli #{entities}:policies my#{entity} --clear-constraints
34
+ nex-cli #{entities}:policies my#{entity} --constraints compute.machine_type=m4.2xlarge,compute.network_security_group=my-security-group
35
+
36
+ If the orchestrator is unable to find a rack matching the defined constraints then the launch of new containers will fail. An error
37
+ will appear in the #{entities}:events logs telling you so.
38
+
39
+ NOTE for storage constraints:
40
+ - only applicable if your application has persistence enabled
41
+ - storage constraints are only applied when provisioning the first container as storage racks are permanently assigned.
42
+
43
+ The list of attributes that can be used is (examples are based on AWS):
44
+ - id - e.g. 653
45
+ - network_zone - e.g. ap-southeast-1
46
+ - machine_image - e.g. ami-123
47
+ - machine_type - e.g. m4.2xlarge
48
+ - network_security_group - e.g. my-compute-security-group
49
+ - private_ip_address - e.g. 10.1.1.1
50
+ - vpc_id - e.g. vpc-234
51
+ - network_subnet_id - e.g. subnet-123
52
+ - platform_provider - e.g. aws (provider name depends on orchestrator configuration)
53
+
54
+ Server regions are not available through rack constraints. If you wish to lock you application to a certain region,
55
+ use region-balancing policies.
56
+
57
+ Region-Balancing:
58
+ -----------------
59
+ Use: --region-balancing REGIONS
60
+ Use: --clear-region-balancing
61
+ Specify how your containers should be distributed across the available Nex!™ regions. This is only relevant when the Nex!™ platform has been deployed
62
+ in multiple regions.
63
+
64
+ Options are the following:
65
+ - evenly distribute across all regions: --region-balancing=all
66
+ - eventy distribute across specified regions: --region-balancing="ap-southeast-1,us-west-2"
67
+ - distribute with relative weights: --region-balancing="ap-southeast-1=1,us-west-2=2"
68
+ - Remove all region-balancing constraints: --clear-region-balancing
69
+ HEREDOC
70
+
71
+ c.example 'display all region policies and constraints', "nex-cli #{entities}:policies my#{entity}"
72
+ c.example 'balance containers equally between regions', "nex-cli #{entities}:policies my#{entity} --region-balancing=\"ap-southeast-1,us-west-2\""
73
+ c.example 'deploy more containers in one region', "nex-cli #{entities}:policies my#{entity} --region-balancing=\"ap-southeast-1=1,us-west-2=2\""
74
+ c.example 'scale containers across all regions', "nex-cli #{entities}:policies my#{entity} --region-balancing=all"
75
+
76
+ c.option '--region-balancing REGIONS', String, "specify how the #{entity} should be proportionally distributed geographically. E.g. \"all\" or \"ap-southeast-1,us-west-2\" or \"ap-southeast-1=1,us-west-2=2\". [restart required]"
77
+ c.option '--clear-region-balancing', String, 'remove region balancing policy and revert to default region'
78
+ c.option '--constraints CONSTRAINT1=value,CONSTRAINT1=value,...', Array, 'restrict deployment of containers to servers having specific attributes [restart required]'
79
+ c.option '--clear-constraints', String, 'remove all constraints'
80
+
81
+ c.action do |args, options|
82
+ manage(args, options, klass)
83
+ end
84
+ end
85
+
86
+ def self.manage(args, opts, klass)
87
+ name = args.first
88
+ cluster = klass.find(name: name).first
89
+
90
+ # Display error
91
+ unless cluster
92
+ error("Error! Could not find #{entity}: #{name}")
93
+ return false
94
+ end
95
+
96
+ c = update(cluster, opts)
97
+ show(c)
98
+ end
99
+
100
+ def self.show(cluster)
101
+ display_region_balancing(cluster)
102
+ display_constraints(cluster)
103
+ end
104
+
105
+ def self.update(cluster, opts)
106
+ # Deep duplicate
107
+ cluster_opts = Marshal.load(Marshal.dump((cluster.opts || {})))
108
+
109
+ # Clear all constraints
110
+ if opts.clear_constraints
111
+ cluster_opts.delete('constraints')
112
+ end
113
+
114
+ # Clear region balancing policies
115
+ if opts.clear_region_balancing
116
+ cluster_opts.delete('region_balancing')
117
+ end
118
+
119
+ # Add list of constraints
120
+ if opts.constraints.present?
121
+ cluster_opts['constraints'] ||= {}
122
+
123
+ # Transform compute.server_type=foo
124
+ # into
125
+ # { 'compute' => { 'server_type' => 'foo' } }
126
+ Array(opts.constraints).each do |constraint|
127
+ key,val = constraint.split('=')
128
+ server_type,rule = key.split('.')
129
+ cluster_opts['constraints'][server_type] ||= {}
130
+ cluster_opts['constraints'][server_type][rule] = val
131
+ end
132
+ end
133
+
134
+ # Add list of constraints
135
+ if opts.region_balancing.present?
136
+ cluster_opts['region_balancing'] = opts.region_balancing
137
+ end
138
+
139
+ # Update policies
140
+ cluster.update_attributes({ opts: cluster_opts }) if cluster.opts != cluster_opts
141
+ cluster
142
+ end
143
+
144
+ def self.display_region_balancing(cluster)
145
+ policies = cluster.opts.dig('region_balancing')
146
+ default_region = cluster.preferred_region
147
+
148
+ table = Terminal::Table.new title: REGION_BALANCING_TITLE, headings: REGION_BALANCING_HEADERS do |t|
149
+ if policies.blank?
150
+ t.add_row(["#{default_region} (default)", "All containers will be deployed in #{default_region} (preferred_region)"])
151
+ elsif policies == 'all'
152
+ t.add_row(['All regions', 'Containers will be spread evenly across all available regions'])
153
+ else
154
+ policies.split(',').each do |policy|
155
+ region,multiplier = policy.split('=')
156
+ t.add_row([region, "Containers will be deployed in #{region} with a ratio #{multiplier}:1"])
157
+ end
158
+ end
159
+ end
160
+ puts table
161
+ puts "\n"
162
+ end
163
+
164
+ def self.display_constraints(cluster)
165
+ constraints = cluster.opts&.dig('constraints')
166
+
167
+ table = Terminal::Table.new title: CONSTRAINTS_TITLE, headings: CONSTRAINTS_HEADERS do |t|
168
+ if constraints.blank?
169
+ t.add_row(["None (default)", "None", "Containers will use all available servers in selected regions"])
170
+ else
171
+ constraints.each do |server_type,rules|
172
+ rules.each do |rule,val|
173
+ t.add_row([server_type, rule, val])
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ puts table
180
+ puts "\n"
181
+ end
182
+ end
183
+ end
184
+ end