kontena-cli 1.3.0.rc1 → 1.3.0.rc2

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
  SHA1:
3
- metadata.gz: d1aa18c17b13a8b1887ca08c0a4a97f01d99b6df
4
- data.tar.gz: 396d0b859359808ee9bcc11b0f92c9a6f9c3119b
3
+ metadata.gz: '09472d142a0b905453dae67fd9a5f773d8cacb78'
4
+ data.tar.gz: 0fbe77649ccdb7fcb79a5d15b093235ea1dee186
5
5
  SHA512:
6
- metadata.gz: 6c8e6b7eb2767919326db9290c952457303c7c390622fc6d762d9453df15d9d2e5deb9993e7731aff7d7b310cb325dd178da401051c8088d90472595c159d2e1
7
- data.tar.gz: 02a1e6756340210bd7de9c552b9f67464165db55476c55defe14a907c9da0ca55a7d05921bf4417b752e1e4712f23aab8dfb5589c1afc724c7e7029831096cb6
6
+ metadata.gz: 909d89da90b3b8101329c00bb5971200e8404368457e475620ff1a8f742ed4dcce1b1899fb6859bff861e0d7416f1b691d313ff532eb9675f3e68d2d1faa2b54
7
+ data.tar.gz: 542ec9dce844b9aa9e4e420c3f6dfcf610e65140f144ee531a777f8cdc3aa418eb672894287b2ffd934a2bd8af4456ed01411d00bcb00f0cbf1dee899adad79a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.0.rc1
1
+ 1.3.0.rc2
data/kontena-cli.gemspec CHANGED
@@ -34,5 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency "safe_yaml", "~> 1.0"
35
35
  spec.add_runtime_dependency "liquid", "~> 4.0.0"
36
36
  spec.add_runtime_dependency "tty-table", "~> 0.8.0"
37
- spec.add_runtime_dependency "websocket-client-simple", "~> 0.3.0"
37
+ spec.add_runtime_dependency "websocket-driver-kontena", "0.6.5"
38
38
  end
@@ -1,22 +1,41 @@
1
1
  require 'forwardable'
2
+ require 'kontena_cli'
2
3
 
3
4
  module Kontena
4
5
  module Cli
5
6
  module Common
6
7
  extend Forwardable
7
8
 
8
- def_delegators :Kontena, :pastel, :prompt, :logger
9
9
  def_delegators :prompt, :ask, :yes?
10
10
  def_delegators :config,
11
11
  :current_grid=, :require_current_grid, :current_master,
12
12
  :current_master=, :require_current_master, :require_current_account,
13
13
  :current_account
14
- def_delegator Kontena::Cli::Spinner, :spin, :spinner
15
- def_delegator Kontena::Cli::Config, :instance, :config
16
- def_delegator Kontena::Cli::Config, :instance, :settings
17
14
  def_delegator :config, :config_filename, :settings_filename
18
15
  def_delegator :client, :server_version, :api_url_version
19
16
 
17
+ def logger
18
+ Kontena.logger
19
+ end
20
+
21
+ def prompt
22
+ Kontena.prompt
23
+ end
24
+
25
+ def pastel
26
+ Kontena.pastel
27
+ end
28
+
29
+ def spinner(msg, &block)
30
+ require 'kontena/cli/spinner' unless Kontena::Cli.const_defined?(:Spinner)
31
+ Kontena::Cli::Spinner.spin(msg, &block)
32
+ end
33
+
34
+ def config
35
+ require 'kontena/cli/config' unless Kontena::Cli.const_defined?(:Config)
36
+ Kontena::Cli::Config.instance
37
+ end
38
+
20
39
  # Read from STDIN. If stdin is a console, use prompt to ask.
21
40
  # @param [String] message
22
41
  # @param [Symbol] mode (prompt method: :ask, :multiline, etc)
@@ -13,23 +13,28 @@ module Kontena::Cli::Containers
13
13
  option ["--interactive"], :flag, "Keep stdin open"
14
14
 
15
15
  def execute
16
- require 'websocket-client-simple'
17
-
18
16
  require_api_url
19
17
  token = require_token
20
18
  cmd = JSON.dump({cmd: cmd_list})
21
- base = self
22
- ws = connect(ws_url("#{current_grid}/#{container_id}"), token)
19
+ url = ws_url("#{current_grid}/#{container_id}")
20
+ url << 'interactive=true&' if interactive?
21
+ url << 'shell=true' if shell?
22
+ ws = connect(url, token)
23
+
23
24
  ws.on :message do |msg|
24
- base.handle_message(msg)
25
+ self.handle_message(msg)
25
26
  end
26
27
  ws.on :open do
27
- ws.send(cmd)
28
+ ws.text(cmd)
28
29
  end
29
30
  ws.on :close do |e|
30
- exit 1
31
+ if e.reason.include?('code: 404')
32
+ exit_with_error('Not found')
33
+ else
34
+ exit 1
35
+ end
31
36
  end
32
-
37
+ ws.connect
33
38
  if interactive?
34
39
  stream_stdin_to_ws(ws).join
35
40
  else
@@ -1,13 +1,16 @@
1
+ require_relative '../../websocket/client'
2
+
1
3
  module Kontena::Cli::Helpers
2
4
  module ExecHelper
3
5
 
4
6
  # @param [WebSocket::Client::Simple] ws
5
7
  # @return [Thread]
6
8
  def stream_stdin_to_ws(ws)
9
+ require 'io/console'
7
10
  Thread.new {
8
11
  STDIN.raw {
9
12
  while char = STDIN.readpartial(1024)
10
- ws.send(JSON.dump({ stdin: char }))
13
+ ws.text(JSON.dump({ stdin: char }))
11
14
  end
12
15
  }
13
16
  }
@@ -43,19 +46,22 @@ module Kontena::Cli::Helpers
43
46
  def ws_url(container_id)
44
47
  url = require_current_master.url
45
48
  url << '/' unless url.end_with?('/')
46
- "#{url.sub('http', 'ws')}v1/containers/#{container_id}/exec"
49
+ "#{url.sub('http', 'ws')}v1/containers/#{container_id}/exec?"
47
50
  end
48
51
 
49
52
  # @param [String] url
50
53
  # @param [String] token
51
54
  # @return [WebSocket::Client::Simple]
52
55
  def connect(url, token)
53
- WebSocket::Client::Simple.connect(url, {
56
+ options = {
54
57
  headers: {
55
- 'Authorization' => "Bearer #{token.access_token}",
56
- 'Accept' => 'application/json'
58
+ 'Authorization' => "Bearer #{token.access_token}"
57
59
  }
58
- })
60
+ }
61
+ if ENV['SSL_IGNORE_ERRORS'].to_s == 'true'
62
+ options[:verify_mode] = ::OpenSSL::SSL::VERIFY_NONE
63
+ end
64
+ Kontena::Websocket::Client.new(url, options)
59
65
  end
60
66
  end
61
67
  end
@@ -9,9 +9,9 @@ module Kontena::Cli::Master
9
9
 
10
10
  def execute
11
11
  params = []
12
- params << "--join #{self.invite_code.shellescape}"
12
+ params += ["--join", self.invite_code]
13
13
  params << "--remote" if self.remote?
14
- params << "--name #{self.name.shellescape}" if self.name
14
+ params += ["--name", self.name] if self.name
15
15
  params << "--verbose" if self.verbose?
16
16
 
17
17
  cmd = ['master', 'login'] + params
@@ -2,9 +2,20 @@ module Kontena::Cli::Master
2
2
  class UseCommand < Kontena::Command
3
3
  include Kontena::Cli::Common
4
4
 
5
- parameter "NAME", "Master name to use"
5
+ parameter "[NAME]", "Master name to use"
6
+
7
+ option '--clear', :flag, "Clear current master setting"
6
8
 
7
9
  def execute
10
+ if clear?
11
+ config.current_master = nil
12
+ config.write
13
+ exit 0
14
+ elsif name.nil?
15
+ signal_usage_error Clamp.message(:parameter_argument_error, :param => 'NAME', :message => "missing")
16
+ exit 1
17
+ end
18
+
8
19
  master = config.find_server(name)
9
20
  if master.nil?
10
21
  exit_with_error p"Could not resolve master by name '#{name}'." +
@@ -34,7 +34,6 @@ module Kontena::Cli::Nodes
34
34
  def fields
35
35
  return ['name'] if quiet?
36
36
  {
37
- ' ' => 'health_icon',
38
37
  name: 'name',
39
38
  version: 'agent_version',
40
39
  status: 'status',
@@ -67,7 +66,7 @@ module Kontena::Cli::Nodes
67
66
  unless quiet?
68
67
  grid_health = grid_health(grid, grid_nodes)
69
68
  grid_nodes.each do |node|
70
- node['health_icon'] = health_icon(node_health(node, grid_health))
69
+ node['name'] = health_icon(node_health(node, grid_health)) + " " + node['name']
71
70
  end
72
71
  end
73
72
 
@@ -24,8 +24,6 @@ module Kontena::Cli::Services
24
24
  requires_current_grid
25
25
 
26
26
  def execute
27
- require 'websocket-client-simple'
28
-
29
27
  exit_with_error "--interactive cannot be used with --all" if all? && interactive?
30
28
 
31
29
  service_containers = client.get("services/#{parse_service_id(name)}/containers")['containers']
@@ -92,7 +90,7 @@ module Kontena::Cli::Services
92
90
  exit_status = nil
93
91
  token = require_token
94
92
  url = ws_url(container['id'])
95
- url << '?shell=true' if shell?
93
+ url << 'shell=true' if shell?
96
94
  ws = connect(url, token)
97
95
  ws.on :message do |msg|
98
96
  data = base.parse_message(msg)
@@ -107,11 +105,12 @@ module Kontena::Cli::Services
107
105
  end
108
106
  end
109
107
  ws.on :open do
110
- ws.send(cmd)
108
+ ws.text(cmd)
111
109
  end
112
- ws.on :close do |e|
113
- exit_status = 1
110
+ ws.on :close do |e|s
111
+ exit_status = 1 if exit_status.nil? && e.code != 1000
114
112
  end
113
+ ws.connect
115
114
 
116
115
  sleep 0.01 until !exit_status.nil?
117
116
 
@@ -120,23 +119,22 @@ module Kontena::Cli::Services
120
119
 
121
120
  # @param [Hash] container
122
121
  def interactive_exec(container)
123
- require 'io/console'
124
-
125
122
  token = require_token
126
123
  cmd = JSON.dump({ cmd: cmd_list })
127
124
  base = self
128
- url = ws_url(container['id']) << '?interactive=true'
125
+ url = ws_url(container['id']) << 'interactive=true'
129
126
  url << '&shell=true' if shell?
130
127
  ws = connect(url, token)
131
128
  ws.on :message do |msg|
132
129
  base.handle_message(msg)
133
130
  end
134
131
  ws.on :open do
135
- ws.send(cmd)
132
+ ws.text(cmd)
136
133
  end
137
134
  ws.on :close do |e|
138
- exit 1
135
+ exit 1 if e.code != 1000
139
136
  end
137
+ ws.connect
140
138
 
141
139
  stream_stdin_to_ws(ws).join
142
140
  end
@@ -17,7 +17,7 @@ module Kontena::Cli::Services
17
17
  end
18
18
 
19
19
  def fields
20
- quiet? ? ['name'] : {' ' => 'health_icon', name: 'name', instances: 'instances', stateful: 'stateful', state: 'state', "exposed ports" => 'ports' }
20
+ quiet? ? ['name'] : {name: 'name', instances: 'instances', stateful: 'stateful', state: 'state', "exposed ports" => 'ports' }
21
21
  end
22
22
 
23
23
  def service_port(port)
@@ -49,9 +49,8 @@ module Kontena::Cli::Services
49
49
 
50
50
  def execute
51
51
  print_table(services) do |row|
52
- row['name'] = service_name(row)
52
+ row['name'] = quiet? ? service_name(row) : health_status_icon(health_status(row)) + " " + service_name(row)
53
53
  next if quiet?
54
- row['health_icon'] = health_status_icon(health_status(row))
55
54
  row['stateful'] = row['stateful'] ? pastel.green('yes') : 'no'
56
55
  row['ports'] = row['ports'].map(&method(:service_port)).join(',')
57
56
  row['state'] = pastel.send(state_color(row['state']), row['state'])
@@ -26,7 +26,6 @@ module Kontena::Cli::Stacks
26
26
  def fields
27
27
  return ['name'] if quiet?
28
28
  {
29
- ' ' => 'health_icon',
30
29
  name: 'name',
31
30
  stack: 'stack',
32
31
  services: 'services_count',
@@ -38,7 +37,7 @@ module Kontena::Cli::Stacks
38
37
  def execute
39
38
  print_table(stacks) do |row|
40
39
  next if quiet?
41
- row['health_icon'] = health_icon(stack_health(row))
40
+ row['name'] = health_icon(stack_health(row)) + " " + row['name']
42
41
  row['stack'] = "#{row['stack']}:#{row['version']}"
43
42
  row['services_count'] = row['services'].size
44
43
  row['ports'] = stack_ports(row).join(',')
@@ -11,6 +11,8 @@ module Kontena
11
11
 
12
12
  DEFAULT_HEADER_FORMAT_PROC = lambda { |header| header.to_s.capitalize }
13
13
 
14
+ DEFAULT_RENDER_OPTS = {padding: [0,2,0,0]}
15
+
14
16
  module Helper
15
17
  def self.included(base)
16
18
  if base.respond_to?(:option)
@@ -28,8 +30,8 @@ module Kontena
28
30
  array,
29
31
  fields,
30
32
  row_format_proc: block_given? ? block.to_proc : nil,
31
- header_format_proc: lambda { |item| pastel.blue(item.to_s.capitalize) },
32
- render_options: self.respond_to?(:render_options) ? self.render_options : nil
33
+ header_format_proc: lambda { |item| pastel.bold(item.to_s.upcase) },
34
+ render_options: self.respond_to?(:render_options) ? DEFAULT_RENDER_OPTS.merge(self.render_options) : DEFAULT_RENDER_OPTS
33
35
  ).render
34
36
  end
35
37
 
@@ -68,7 +70,7 @@ module Kontena
68
70
  if data.empty?
69
71
  fields.map(&method(:format_header_item)).join(' ')
70
72
  else
71
- table.render(render_mode, render_options)
73
+ table.render(render_mode, render_options).gsub(/\s+$/, '')
72
74
  end
73
75
  end
74
76
 
@@ -24,6 +24,7 @@ module Kontena
24
24
 
25
25
  # Render as indented YAML
26
26
  def errors_message(indent: "\t")
27
+ require 'yaml'
27
28
  @errors.to_yaml.lines[1..-1].map{|line| "#{indent}#{line}" }.join
28
29
  end
29
30
 
@@ -1,18 +1,42 @@
1
- require 'kontena/client'
2
1
  require 'kontena/cli/common'
3
- require 'yaml'
4
2
 
5
3
  class Helper
6
4
  include Kontena::Cli::Common
7
5
 
6
+ def client_config
7
+ require 'json'
8
+ config_file = File.expand_path('~/.kontena_client.json')
9
+ if(File.exist?(config_file))
10
+ JSON.parse(File.read(config_file))
11
+ else
12
+ {}
13
+ end
14
+ rescue => ex
15
+ logger.debug ex
16
+ {}
17
+ end
18
+
19
+ def current_grid
20
+ client_config['servers'].find { |s| s['name'] == client_config['current_server']}['grid']
21
+ rescue => ex
22
+ logger.debug ex
23
+ nil
24
+ end
25
+
26
+ def current_master_name
27
+ client_config['current_server']
28
+ end
29
+
8
30
  def client
31
+ $VERSION_WARNING_ADDED=true
9
32
  token = require_token
10
33
  super(token)
11
34
  end
12
35
 
13
36
  def grids
14
37
  client.get("grids")['grids'].map{|grid| grid['id']}
15
- rescue
38
+ rescue => ex
39
+ logger.debug ex
16
40
  []
17
41
  end
18
42
 
@@ -28,7 +52,8 @@ class Helper
28
52
  results.push stacks.map{|s| s['name']}
29
53
  results.delete('null')
30
54
  results
31
- rescue
55
+ rescue => ex
56
+ logger.debug ex
32
57
  []
33
58
  end
34
59
 
@@ -44,7 +69,8 @@ class Helper
44
69
  end
45
70
  }
46
71
  results
47
- rescue
72
+ rescue => ex
73
+ logger.debug ex
48
74
  []
49
75
  end
50
76
 
@@ -56,33 +82,34 @@ class Helper
56
82
  results.push(containers.map{|c| c['id'] })
57
83
  end
58
84
  results
59
- rescue
85
+ rescue => ex
86
+ logger.debug ex
60
87
  []
61
88
  end
62
89
 
63
90
  def yml_services
91
+ require 'yaml'
64
92
  if File.exist?('kontena.yml')
65
93
  yaml = YAML.safe_load(File.read('kontena.yml'))
66
94
  services = yaml['services']
67
95
  services.keys
68
96
  end
69
- rescue
97
+ rescue => ex
98
+ logger.debug ex
70
99
  []
71
100
  end
72
101
 
73
102
  def yml_files
74
103
  Dir["./*.yml"].map{|file| file.sub('./', '')}
75
- rescue
104
+ rescue => ex
105
+ logger.debug ex
76
106
  []
77
107
  end
78
108
 
79
109
  def master_names
80
- config_file = File.expand_path('~/.kontena_client.json')
81
- if(File.exist?(config_file))
82
- config = JSON.parse(File.read(config_file))
83
- return config['servers'].map{|s| s['name']}
84
- end
85
- rescue
110
+ client_config['servers'].map{|s| s['name']}
111
+ rescue => ex
112
+ logger.debug ex
86
113
  []
87
114
  end
88
115
 
@@ -108,138 +135,152 @@ end
108
135
 
109
136
  words.delete_at(0)
110
137
 
111
- completion = []
112
- completion.push %w(cloud logout grid app service stack vault certificate node master vpn registry container etcd external-registry whoami plugin version) if words.size < 2
113
- if words.size > 0
114
- case words[0]
115
- when 'plugin'
116
- completion.clear
117
- sub_commands = %w(list ls search install uninstall)
118
- if words[1]
119
- completion.push(sub_commands) unless sub_commands.include?(words[1])
120
- else
121
- completion.push sub_commands
122
- end
123
- when 'etcd'
124
- completion.clear
125
- sub_commands = %w(get set mkdir mk list ls rm)
126
- if words[1]
127
- completion.push(sub_commands) unless sub_commands.include?(words[1])
128
- else
129
- completion.push sub_commands
130
- end
131
- when 'registry'
132
- completion.clear
133
- sub_commands = %w(create remove rm)
134
- if words[1]
135
- completion.push(sub_commands) unless sub_commands.include?(words[1])
136
- else
137
- completion.push sub_commands
138
- end
139
- when 'grid'
140
- completion.clear
141
- sub_commands = %w(add-user audit-log create current list user remove show use)
142
- if words[1]
143
- completion.push(sub_commands) unless sub_commands.include?(words[1])
144
- completion.push helper.grids
145
- else
146
- completion.push sub_commands
147
- end
148
- when 'node'
149
- completion.clear
150
- sub_commands = %w(list show remove)
151
- if words[1]
152
- completion.push(sub_commands) unless sub_commands.include?(words[1])
153
- completion.push helper.nodes
154
- else
155
- completion.push sub_commands
156
- end
157
- when 'master'
158
- completion.clear
159
- sub_commands = %w(list use users current remove rm config cfg login logout token join audit-log init-cloud)
160
- if words[1] && words[1] == 'use'
161
- completion.push helper.master_names
162
- elsif words[1] && words[1] == 'users'
163
- users_sub_commands = %(invite list role)
164
- completion.push users_sub_commands
165
- elsif words[1] && ['config', 'cfg'].include?(words[1])
166
- config_sub_commands = %(set get dump load import export unset)
167
- completion.push config_sub_commands
168
- elsif words[1] && words[1] == 'token'
169
- token_sub_commands = %(list ls rm remove show current create)
170
- completion.push token_sub_commands
171
- elsif words[1]
172
- completion.push(sub_commands) unless sub_commands.include?(words[1])
173
- else
174
- completion.push sub_commands
175
- end
176
- when 'cloud'
177
- completion.clear
178
- sub_commands = %w(login logout master)
179
- if words[1] && words[1] == 'master'
180
- cloud_master_sub_commands = %(list ls remove rm add show update)
181
- completion.push cloud_master_sub_commands
182
- elsif words[1]
183
- completion.push(sub_commands) unless sub_commands.include?(words[1])
184
- else
185
- completion.push sub_commands
186
- end
187
- when 'service'
188
- completion.clear
189
- sub_commands = %w(containers create delete deploy list logs restart
190
- scale show start stats stop update monitor env
191
- secret link unlink)
192
- if words[1]
193
- completion.push(sub_commands) unless sub_commands.include?(words[1])
194
- completion.push helper.services
195
- else
196
- completion.push sub_commands
197
- end
198
- when 'container'
199
- completion.clear
200
- sub_commands = %w(exec inspect logs)
201
- if words[1]
202
- completion.push(sub_commands) unless sub_commands.include?(words[1])
203
- completion.push helper.containers
204
- else
205
- completion.push sub_commands
206
- end
207
- when 'vpn'
208
- completion.clear
209
- completion.push %w(config create delete)
210
- when 'external-registry'
211
- completion.clear
212
- completion.push %w(add list delete)
213
- when 'app'
214
- completion.clear
215
- sub_commands = %w(init build config deploy start stop remove rm ps list
216
- logs monitor show)
217
- if words[1]
218
- completion.push(sub_commands) unless sub_commands.include?(words[1])
219
- completion.push helper.yml_services
220
- else
221
- completion.push sub_commands
222
- end
223
- when 'stack'
224
- completion.clear
225
- sub_commands = %w(build install upgrade deploy start stop remove rm ls list
226
- logs monitor show registry)
227
- if words[1]
228
- if words[1] == 'registry'
229
- registry_sub_commands = %(push pull search show rm)
230
- completion.push registry_sub_commands
231
- elsif %w(install).include?(words[1])
232
- completion.push helper.yml_files
233
- elsif words[1] == 'upgrade' && words[3]
234
- completion.push helper.yml_files
138
+ helper.logger.debug { "Completing #{words.inspect}" }
139
+
140
+ begin
141
+ completion = []
142
+ completion.push %w(cloud logout grid app service stack vault certificate node master vpn registry container etcd external-registry whoami plugin version) if words.size < 2
143
+ if words.size > 0
144
+ case words[0]
145
+ when 'plugin'
146
+ completion.clear
147
+ sub_commands = %w(list ls search install uninstall)
148
+ if words[1]
149
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
235
150
  else
151
+ completion.push sub_commands
152
+ end
153
+ when 'etcd'
154
+ completion.clear
155
+ sub_commands = %w(get set mkdir mk list ls rm)
156
+ if words[1]
236
157
  completion.push(sub_commands) unless sub_commands.include?(words[1])
237
- completion.push helper.stacks
158
+ else
159
+ completion.push sub_commands
238
160
  end
239
- else
240
- completion.push sub_commands
241
- end
161
+ when 'registry'
162
+ completion.clear
163
+ sub_commands = %w(create remove rm)
164
+ if words[1]
165
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
166
+ else
167
+ completion.push sub_commands
168
+ end
169
+ when 'grid'
170
+ completion.clear
171
+ sub_commands = %w(add-user audit-log create current list user remove show use)
172
+ if words[1] && words[1] == 'use'
173
+ completion.push helper.grids.reject { |g| g == helper.current_grid }
174
+ elsif words[1] && %w(update show rm remove env cloud-config health).include?(words[1])
175
+ completion.push helper.grids
176
+ else
177
+ completion.push sub_commands
178
+ end
179
+ when 'node'
180
+ completion.clear
181
+ sub_commands = %w(list show remove)
182
+ if words[1] && sub_commands.include?(words[1])
183
+ completion.push helper.nodes
184
+ else
185
+ completion.push sub_commands
186
+ end
187
+ when 'master'
188
+ completion.clear
189
+ sub_commands = %w(list use user current remove rm config cfg login logout token join audit-log init-cloud)
190
+ if words[1] && words[1] == 'use'
191
+ completion.push helper.master_names.reject { |n| n == helper.current_master_name }
192
+ elsif words[1] && %w(remove rm).include?(words[1])
193
+ completion.push helper.master_names
194
+ elsif words[1] && words[1] == 'user'
195
+ users_sub_commands = %w(invite list role)
196
+ if words[2] == 'role'
197
+ role_subcommands = %w(add remove rm)
198
+ if !words[3] || !role_subcommands.include?(words[3])
199
+ completion.push role_subcommands
200
+ end
201
+ else
202
+ completion.push users_sub_commands
203
+ end
204
+ elsif words[1] && ['config', 'cfg'].include?(words[1])
205
+ config_sub_commands = %(set get dump load import export unset)
206
+ completion.push config_sub_commands
207
+ elsif words[1] && words[1] == 'token'
208
+ token_sub_commands = %(list ls rm remove show current create)
209
+ completion.push token_sub_commands
210
+ elsif words[1]
211
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
212
+ else
213
+ completion.push sub_commands
214
+ end
215
+ when 'cloud'
216
+ completion.clear
217
+ sub_commands = %w(login logout master)
218
+ if words[1] && words[1] == 'master'
219
+ cloud_master_sub_commands = %(list ls remove rm add show update)
220
+ completion.push cloud_master_sub_commands
221
+ elsif words[1]
222
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
223
+ else
224
+ completion.push sub_commands
225
+ end
226
+ when 'service'
227
+ completion.clear
228
+ sub_commands = %w(containers create delete deploy list logs restart
229
+ scale show start stats stop update monitor env
230
+ secret link unlink)
231
+ if words[1] && sub_commands.include?(words[1])
232
+ completion.push helper.services
233
+ else
234
+ completion.push sub_commands
235
+ end
236
+ when 'container'
237
+ completion.clear
238
+ sub_commands = %w(exec inspect logs)
239
+ if words[1] && sub_commands.include?(words[1])
240
+ completion.push helper.containers
241
+ else
242
+ completion.push sub_commands
243
+ end
244
+ when 'vpn'
245
+ completion.clear
246
+ completion.push %w(config create delete)
247
+ when 'external-registry'
248
+ completion.clear
249
+ completion.push %w(add list delete)
250
+ when 'app'
251
+ completion.clear
252
+ sub_commands = %w(init build config deploy start stop remove rm ps list
253
+ logs monitor show)
254
+ if words[1] && sub_commands.include?(words[1])
255
+ completion.push helper.yml_services
256
+ else
257
+ completion.push sub_commands
258
+ end
259
+ when 'stack'
260
+ completion.clear
261
+ sub_commands = %w(build install upgrade deploy start stop remove rm ls list
262
+ logs monitor show registry)
263
+ if words[1]
264
+ if words[1] == 'registry'
265
+ registry_sub_commands = %(push pull search show rm)
266
+ completion.push registry_sub_commands
267
+ elsif %w(install).include?(words[1])
268
+ completion.push helper.yml_files
269
+ elsif words[1] == 'upgrade' && words[3]
270
+ completion.push helper.yml_files
271
+ elsif words[1] && sub_commands.include?(words[1])
272
+ completion.push helper.stacks
273
+ else
274
+ completion.push(sub_commands)
275
+ end
276
+ else
277
+ completion.push sub_commands
278
+ end
279
+ end
242
280
  end
281
+ rescue => ex
282
+ helper.logger.debug ex
243
283
  end
284
+ helper.logger.debug { "Returning completions: #{completion.inspect}" }
244
285
 
245
286
  puts completion
@@ -0,0 +1,12 @@
1
+ require 'websocket/driver'
2
+ module Kontena
3
+ module Websocket
4
+ module Client
5
+ def self.new(*args)
6
+ Connection.new(*args)
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ require_relative 'client/connection'
@@ -0,0 +1,65 @@
1
+ require 'forwardable'
2
+ require 'socket'
3
+ require 'openssl'
4
+
5
+ module Kontena
6
+ module Websocket
7
+ module Client
8
+ class Connection
9
+ extend Forwardable
10
+
11
+ FRAME_SIZE = 1024
12
+
13
+ attr_reader :url
14
+
15
+ # @param [String] url
16
+ # @param [Hash] options
17
+ def initialize(url, options = {})
18
+ @options = options
19
+ @url = url
20
+ @client = ::WebSocket::Driver.client(self)
21
+ if headers = options[:headers]
22
+ headers.each do |k, v|
23
+ @client.set_header(k, v)
24
+ end
25
+ end
26
+ end
27
+
28
+ def connect
29
+ uri = URI.parse(@url)
30
+ port = uri.port || (uri.scheme == "ws" ? 80 : 443)
31
+ @socket = ::TCPSocket.new(uri.host, port)
32
+ if uri.scheme == "wss"
33
+ ctx = ::OpenSSL::SSL::SSLContext.new
34
+ ctx.ssl_version = @options[:ssl_version] if @options[:ssl_version]
35
+ ctx.verify_mode = @options[:verify_mode] if @options[:verify_mode]
36
+ cert_store = ::OpenSSL::X509::Store.new
37
+ cert_store.set_default_paths
38
+ ctx.cert_store = cert_store
39
+ @socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ctx)
40
+ @socket.connect
41
+ end
42
+ @client.start
43
+ Thread.new { self.read_socket }
44
+ end
45
+
46
+ def_delegators :@client, :text, :binary, :ping, :close, :protocol, :on
47
+
48
+ def write(buffer)
49
+ @socket.write buffer
50
+ end
51
+
52
+ def read_socket
53
+ loop do
54
+ begin
55
+ @client.parse(@socket.readpartial(FRAME_SIZE))
56
+ rescue EOFError
57
+ break
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
data/lib/kontena_cli.rb CHANGED
@@ -173,8 +173,7 @@ end
173
173
  require 'retriable'
174
174
  Retriable.configure do |c|
175
175
  c.on_retry = Proc.new do |exception, try, elapsed_time, next_interval|
176
- return true unless ENV["DEBUG"]
177
- puts "Retriable retry: #{try} - Exception: #{exception.class.name} - #{exception.message}. Elapsed: #{elapsed_time} Next interval: #{next_interval}"
176
+ Kontena.logger.debug { "Retriable retry: #{try} - Exception: #{exception.class.name} - #{exception.message}. Elapsed: #{elapsed_time} Next interval: #{next_interval}" }
178
177
  end
179
178
  end
180
179
 
@@ -19,20 +19,8 @@ describe Kontena::Cli::Apps::BuildCommand do
19
19
  fixture('docker-compose.yml')
20
20
  end
21
21
 
22
- let(:settings) do
23
- {'current_server' => 'alias',
24
- 'servers' => [
25
- {
26
- 'name' => 'some_master',
27
- 'url' => 'some_master'
28
- }
29
- ]
30
- }
31
- end
32
-
33
22
  describe '#execute' do
34
23
  before(:each) do
35
- allow(subject).to receive(:settings).and_return(settings)
36
24
  allow(subject).to receive(:current_dir).and_return("kontena-test")
37
25
  allow(File).to receive(:exists?).and_return(true)
38
26
  allow(File).to receive(:read).with("#{Dir.getwd}/kontena.yml").and_return(kontena_yml)
@@ -0,0 +1,33 @@
1
+ require 'kontena/cli/master/join_command'
2
+ require 'kontena/cli/localhost_web_server'
3
+ require 'launchy'
4
+ require 'ostruct'
5
+
6
+ describe Kontena::Cli::Master::JoinCommand do
7
+
8
+ include ClientHelpers
9
+
10
+ let(:subject) do
11
+ described_class.new(File.basename($0))
12
+ end
13
+
14
+ it 'calls master login with proper join options' do
15
+ expect(Kontena).to receive(:run!).with(%w(master login --join xyz someurl))
16
+ subject.run(%w(someurl xyz))
17
+ end
18
+
19
+ it 'calls master login with remote option' do
20
+ expect(Kontena).to receive(:run!).with(%w(master login --join xyz --remote someurl))
21
+ subject.run(%w(--remote someurl xyz))
22
+ end
23
+
24
+ it 'calls master login with name option' do
25
+ expect(Kontena).to receive(:run!).with(%w(master login --join xyz --name somename someurl))
26
+ subject.run(%w(--name somename someurl xyz))
27
+ end
28
+
29
+ it 'calls master login with verbose option' do
30
+ expect(Kontena).to receive(:run!).with(%w(master login --join xyz --verbose someurl))
31
+ subject.run(%w(--verbose someurl xyz))
32
+ end
33
+ end
@@ -18,5 +18,15 @@ describe Kontena::Cli::Master::UseCommand do
18
18
  it 'should abort with error message if master is not configured' do
19
19
  expect { subject.run(['not_existing']) }.to output(/Could not resolve master with name: 'not_existing'/).to_stderr
20
20
  end
21
+
22
+ it 'should abort with error message if master is not given' do
23
+ expect { subject.run([]) }.to raise_error Clamp::UsageError
24
+ end
25
+
26
+ it 'should clear current master when --clear given' do
27
+ expect(subject.config).to receive(:write).and_return(true)
28
+ subject.run(['--clear'])
29
+ expect(subject.config.current_server).to be_nil
30
+ end
21
31
  end
22
32
  end
@@ -1,4 +1,4 @@
1
- require 'websocket-client-simple'
1
+ require 'kontena/websocket/client'
2
2
  require 'kontena/cli/services/exec_command'
3
3
 
4
4
  describe Kontena::Cli::Services::ExecCommand do
@@ -24,6 +24,8 @@ describe Kontena::Cli::Services::ExecCommand do
24
24
  end
25
25
  end
26
26
 
27
+ def connect ; end
28
+
27
29
  def receive_message(msg)
28
30
  @callbacks[:message].call(Event.new(JSON.dump(msg)))
29
31
  rescue => exc
@@ -67,8 +69,8 @@ describe Kontena::Cli::Services::ExecCommand do
67
69
  end
68
70
 
69
71
  it "Executes on the running container by default" do
70
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
71
- expect(ws_client).to receive(:send) do |foo|
72
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec?", anything).and_return(ws_client)
73
+ expect(ws_client).to receive(:text) do |foo|
72
74
  ws_client.receive_message({'stream' => 'stdout', 'chunk' => "ok\n"})
73
75
  ws_client.receive_message({'exit' => 0})
74
76
  end
@@ -105,8 +107,8 @@ describe Kontena::Cli::Services::ExecCommand do
105
107
 
106
108
  it "Executes on the first running container by default" do
107
109
  expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
108
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
109
- expect(ws_client).to receive(:send) do
110
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec?", anything).and_return(ws_client)
111
+ expect(ws_client).to receive(:text) do
110
112
  respond_ok(ws_client)
111
113
  end
112
114
  expect {
@@ -116,8 +118,8 @@ describe Kontena::Cli::Services::ExecCommand do
116
118
 
117
119
  it "Executes on the first running container, even if they are ordered differently" do
118
120
  expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return({'containers' => service_containers['containers'].reverse })
119
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
120
- expect(ws_client).to receive(:send) do
121
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec?", anything).and_return(ws_client)
122
+ expect(ws_client).to receive(:text) do
121
123
  respond_ok(ws_client)
122
124
  end
123
125
  expect {
@@ -127,8 +129,8 @@ describe Kontena::Cli::Services::ExecCommand do
127
129
 
128
130
  it "Executes on the first running container if given" do
129
131
  expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
130
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
131
- expect(ws_client).to receive(:send) do
132
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec?", anything).and_return(ws_client)
133
+ expect(ws_client).to receive(:text) do
132
134
  respond_ok(ws_client)
133
135
  end
134
136
  expect {
@@ -138,8 +140,8 @@ describe Kontena::Cli::Services::ExecCommand do
138
140
 
139
141
  it "Executes on the second running container if given" do
140
142
  expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
141
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-2/exec", anything).and_return(ws_client)
142
- expect(ws_client).to receive(:send) do
143
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-2/exec?", anything).and_return(ws_client)
144
+ expect(ws_client).to receive(:text) do
143
145
  respond_ok(ws_client)
144
146
  end
145
147
  expect {
@@ -158,8 +160,8 @@ describe Kontena::Cli::Services::ExecCommand do
158
160
 
159
161
  3.times do |i|
160
162
  ws_client = ws_client_class.new
161
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i + 1}/exec", anything).and_return(ws_client)
162
- expect(ws_client).to receive(:send) do
163
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i + 1}/exec?", anything).and_return(ws_client)
164
+ expect(ws_client).to receive(:text) do
163
165
  ws_client.receive_message({'stream' => 'stdout', 'chunk' => "test#{i + 1}\n"})
164
166
  ws_client.receive_message({'exit' => 0})
165
167
  end
@@ -172,8 +174,8 @@ describe Kontena::Cli::Services::ExecCommand do
172
174
 
173
175
  it "Stops if the first container fails" do
174
176
  expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
175
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
176
- expect(ws_client).to receive(:send) do
177
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec?", anything).and_return(ws_client)
178
+ expect(ws_client).to receive(:text) do
177
179
  respond_error(ws_client)
178
180
  end
179
181
  expect {
@@ -186,8 +188,8 @@ describe Kontena::Cli::Services::ExecCommand do
186
188
  i = 1
187
189
  [:ok, :err].each do |status|
188
190
  ws_client = ws_client_class.new
189
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
190
- expect(ws_client).to receive(:send) do
191
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec?", anything).and_return(ws_client)
192
+ expect(ws_client).to receive(:text) do
191
193
  if status == :ok
192
194
  respond_ok(ws_client)
193
195
  else
@@ -207,8 +209,8 @@ describe Kontena::Cli::Services::ExecCommand do
207
209
  i = 1
208
210
  [:ok, :err, :ok].each do |status|
209
211
  ws_client = ws_client_class.new
210
- expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
211
- expect(ws_client).to receive(:send) do
212
+ expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec?", anything).and_return(ws_client)
213
+ expect(ws_client).to receive(:text) do
212
214
  if status == :ok
213
215
  respond_ok(ws_client)
214
216
  else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kontena-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0.rc1
4
+ version: 1.3.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kontena, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-31 00:00:00.000000000 Z
11
+ date: 2017-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -207,19 +207,19 @@ dependencies:
207
207
  - !ruby/object:Gem::Version
208
208
  version: 0.8.0
209
209
  - !ruby/object:Gem::Dependency
210
- name: websocket-client-simple
210
+ name: websocket-driver-kontena
211
211
  requirement: !ruby/object:Gem::Requirement
212
212
  requirements:
213
- - - "~>"
213
+ - - '='
214
214
  - !ruby/object:Gem::Version
215
- version: 0.3.0
215
+ version: 0.6.5
216
216
  type: :runtime
217
217
  prerelease: false
218
218
  version_requirements: !ruby/object:Gem::Requirement
219
219
  requirements:
220
- - - "~>"
220
+ - - '='
221
221
  - !ruby/object:Gem::Version
222
- version: 0.3.0
222
+ version: 0.6.5
223
223
  description: Command-line client for the Kontena container and microservices platform
224
224
  email:
225
225
  - info@kontena.io
@@ -526,6 +526,8 @@ files:
526
526
  - lib/kontena/stacks_cache.rb
527
527
  - lib/kontena/stacks_client.rb
528
528
  - lib/kontena/util.rb
529
+ - lib/kontena/websocket/client.rb
530
+ - lib/kontena/websocket/client/connection.rb
529
531
  - lib/kontena_cli.rb
530
532
  - omnibus/.gitignore
531
533
  - omnibus/.kitchen.yml
@@ -610,6 +612,7 @@ files:
610
612
  - spec/kontena/cli/main_command_spec.rb
611
613
  - spec/kontena/cli/master/current_command_spec.rb
612
614
  - spec/kontena/cli/master/init_cloud_command_spec.rb
615
+ - spec/kontena/cli/master/join_command_spec.rb
613
616
  - spec/kontena/cli/master/login_command_spec.rb
614
617
  - spec/kontena/cli/master/logout_command_spec.rb
615
618
  - spec/kontena/cli/master/use_command_spec.rb
@@ -762,6 +765,7 @@ test_files:
762
765
  - spec/kontena/cli/main_command_spec.rb
763
766
  - spec/kontena/cli/master/current_command_spec.rb
764
767
  - spec/kontena/cli/master/init_cloud_command_spec.rb
768
+ - spec/kontena/cli/master/join_command_spec.rb
765
769
  - spec/kontena/cli/master/login_command_spec.rb
766
770
  - spec/kontena/cli/master/logout_command_spec.rb
767
771
  - spec/kontena/cli/master/use_command_spec.rb