kontena-cli 1.3.0.rc1 → 1.3.0.rc2

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 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