kontena-cli 0.6.1 → 0.7.0

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.
@@ -5,23 +5,24 @@ module Kontena::Cli::Server
5
5
  class User
6
6
  include Kontena::Cli::Common
7
7
 
8
- def login
9
- require_api_url
10
- username = ask("Email: ")
11
- password = password("Password: ")
12
- params = {
13
- username: username,
14
- password: password,
15
- grant_type: 'password',
16
- scope: 'user'
17
- }
8
+ def login(api_url = nil)
9
+ until !api_url.nil? && !api_url.empty?
10
+ api_url = ask('Kontena Master Node URL: ')
11
+ end
12
+ update_api_url(api_url)
18
13
 
19
- response = client.post('auth', params)
14
+ unless request_server_info
15
+ print color('Could not connect to server', :red)
16
+ return false
17
+ end
18
+
19
+ email = ask("Email: ")
20
+ password = password("Password: ")
21
+ response = do_login(email, password)
20
22
 
21
23
  if response
22
- settings['server']['token'] = response['access_token']
23
- save_settings
24
- puts ''
24
+ update_access_token(response['access_token'])
25
+ display_logo
25
26
  puts "Welcome #{response['user']['name'].green}"
26
27
  puts ''
27
28
  reset_client
@@ -44,9 +45,14 @@ module Kontena::Cli::Server
44
45
  save_settings
45
46
  end
46
47
 
47
- def whoami
48
+ def whoami(options)
49
+ if options.bash_completion_path
50
+ puts File.realpath(File.join(__dir__, '../../scripts/init'))
51
+ exit 0
52
+ end
53
+
48
54
  require_api_url
49
- puts "Server: #{settings['server']['url']}"
55
+ puts "Master: #{settings['server']['url']}"
50
56
  token = require_token
51
57
  response = client(token).get('user')
52
58
  puts "User: #{response['email']}"
@@ -60,8 +66,11 @@ module Kontena::Cli::Server
60
66
  puts 'User invited' if response
61
67
  end
62
68
 
63
- def register
64
- require_api_url
69
+ def register(api_url = nil, options)
70
+ auth_api_url = api_url || 'https://auth.kontena.io'
71
+ if !auth_api_url.start_with?('http://') && !auth_api_url.start_with?('https://')
72
+ auth_api_url = "https://#{auth_api_url}"
73
+ end
65
74
  email = ask("Email: ")
66
75
  password = password("Password: ")
67
76
  password2 = password("Password again: ")
@@ -69,7 +78,8 @@ module Kontena::Cli::Server
69
78
  raise ArgumentError.new("Passwords don't match")
70
79
  end
71
80
  params = {email: email, password: password}
72
- client.post('users/register', params)
81
+ auth_client = Kontena::Client.new(auth_api_url)
82
+ auth_client.post('users', params)
73
83
  end
74
84
 
75
85
  def verify_account(token)
@@ -100,22 +110,65 @@ module Kontena::Cli::Server
100
110
  puts 'Password is now changed. To login with the new password, please run: kontena login'
101
111
  end
102
112
 
103
- def add_registry
104
- default_url = 'https://index.docker.io/v1/'
105
- require_api_url
106
- username = ask("Username: ")
107
- password = password("Password: ")
108
- email = ask("Email: ")
109
- url = ask("URL [#{default_url}]: ")
110
- url = default_url if url.strip == ''
111
- data = { username: username, password: password, email: email, url: url }
112
- client(token).post("user/registries", data)
113
- end
114
-
115
113
  private
116
114
 
117
115
  def token
118
116
  @token ||= require_token
119
117
  end
118
+
119
+ def do_login(email, password)
120
+ params = {
121
+ username: email,
122
+ password: password,
123
+ grant_type: 'password',
124
+ scope: 'user'
125
+ }
126
+ client.post('auth', params)
127
+ end
128
+
129
+ def request_server_info
130
+ valid = true
131
+ begin
132
+ client.get('ping') # test server connection
133
+ rescue OpenSSL::SSL::SSLError => _
134
+ raise 'Could not connect to server because of SSL problem. If you want to ignore SSL errors, set SSL_IGNORE_ERRORS=true environment variable'
135
+ rescue => exc
136
+ valid = false
137
+ end
138
+ valid
139
+ end
140
+
141
+ ##
142
+ # Store access token to config file
143
+ #
144
+ # @param [String] access_token
145
+ def update_access_token(access_token)
146
+ settings['server']['token'] = access_token
147
+ save_settings
148
+ end
149
+
150
+ ##
151
+ # Store api_url to config file
152
+ #
153
+ # @param [String] api_url
154
+ def update_api_url(api_url)
155
+ settings['server']['url'] = api_url
156
+ save_settings
157
+ end
158
+
159
+ def display_logo
160
+ logo = <<LOGO
161
+ _ _
162
+ | | _____ _ __ | |_ ___ _ __ __ _
163
+ | |/ / _ \\| '_ \\| __/ _ \\ '_ \\ / _` |
164
+ | < (_) | | | | || __/ | | | (_| |
165
+ |_|\\_\\___/|_| |_|\\__\\___|_| |_|\\__,_|
166
+ -------------------------------------
167
+ Copyright (c)2015 Kontena, Inc.
168
+
169
+ LOGO
170
+ puts logo
171
+ end
172
+
120
173
  end
121
174
  end
@@ -83,7 +83,7 @@ command 'service update' do |c|
83
83
  c.syntax = 'kontena service update <service_id>'
84
84
  c.description = 'Update service'
85
85
  c.option '-p', '--ports Array', Array, 'Exposed ports'
86
- c.option '-e', '--env Array', Array, 'Environment variables'
86
+ c.option '-e', '--env Array', Array, 'Environment variables separated with commas'
87
87
  c.option '--image STRING', String, 'Service image'
88
88
  c.option '--instances INTEGER', Integer, 'How many instances should be deployed'
89
89
  c.option '--cmd STRING', String, 'Command to execute'
@@ -11,7 +11,7 @@ module Kontena::Cli::Services
11
11
  require_api_url
12
12
  token = require_token
13
13
 
14
- result = client(token).get("services/#{service_id}/containers")
14
+ result = client(token).get("services/#{current_grid}/#{service_id}/containers")
15
15
  result['containers'].each do |container|
16
16
  puts "#{container['id']}:"
17
17
  puts " node: #{container['node']['name']}"
@@ -21,4 +21,4 @@ module Kontena::Cli::Services
21
21
  end
22
22
  end
23
23
  end
24
- end
24
+ end
@@ -13,10 +13,10 @@ module Kontena::Cli::Services
13
13
  last_id = nil
14
14
  loop do
15
15
  query_params = last_id.nil? ? '' : "from=#{last_id}"
16
- result = client(token).get("services/#{service_id}/container_logs?#{query_params}")
16
+ result = client(token).get("services/#{current_grid}/#{service_id}/container_logs?#{query_params}")
17
17
  result['logs'].each do |log|
18
- color = color_for_container(log['container_id'])
19
- puts "#{log['container_id'][0..12].colorize(color)} | #{log['data']}"
18
+ color = color_for_container(log['name'])
19
+ puts "#{log['name'][0..12].colorize(color)} | #{log['data']}"
20
20
  last_id = log['id']
21
21
  end
22
22
  break unless options.follow
@@ -50,12 +50,12 @@ module Kontena::Cli::Services
50
50
  end
51
51
  end
52
52
  puts " containers:"
53
- result = client(token).get("services/#{service_id}/containers")
53
+ result = client(token).get("services/#{current_grid}/#{service_id}/containers")
54
54
  result['containers'].each do |container|
55
- puts " #{container['id']}:"
55
+ puts " #{container['name']}:"
56
56
  puts " rev: #{container['deploy_rev']}"
57
57
  puts " node: #{container['node']['name']}"
58
- puts " dns: #{container['id']}.kontena.local"
58
+ puts " dns: #{container['name']}.kontena.local"
59
59
  puts " ip: #{container['network_settings']['ip_address']}"
60
60
  puts " public ip: #{container['node']['public_ip']}"
61
61
  if container['status'] == 'unknown'
@@ -67,7 +67,7 @@ module Kontena::Cli::Services
67
67
  end
68
68
 
69
69
  def scale(service_id, count, options)
70
- client(require_token).put("services/#{service_id}", {container_count: count})
70
+ client(require_token).put("services/#{current_grid}/#{service_id}", {container_count: count})
71
71
  self.deploy(service_id, options)
72
72
  end
73
73
 
@@ -85,20 +85,20 @@ module Kontena::Cli::Services
85
85
  def restart(service_id)
86
86
  require_api_url
87
87
  token = require_token
88
- result = client(token).post("services/#{service_id}/restart", {})
88
+ result = client(token).post("services/#{current_grid}/#{service_id}/restart", {})
89
89
  end
90
90
 
91
91
  def stop(service_id)
92
92
  require_api_url
93
93
  token = require_token
94
- result = client(token).post("services/#{service_id}/stop", {})
94
+ result = client(token).post("services/#{current_grid}/#{service_id}/stop", {})
95
95
  end
96
96
 
97
97
  def start(service_id)
98
98
  require_api_url
99
99
  token = require_token
100
100
 
101
- result = client(token).post("services/#{service_id}/start", {})
101
+ result = client(token).post("services/#{current_grid}/#{service_id}/start", {})
102
102
  end
103
103
 
104
104
  def create(name, image, options)
@@ -126,7 +126,7 @@ module Kontena::Cli::Services
126
126
  require_api_url
127
127
  token = require_token
128
128
 
129
- result = client(token).delete("services/#{service_id}")
129
+ result = client(token).delete("services/#{current_grid}/#{service_id}")
130
130
  end
131
131
 
132
132
  private
@@ -144,7 +144,7 @@ module Kontena::Cli::Services
144
144
  data[:memory_swap] = parse_memory(options.memory_swap) if options.memory_swap
145
145
  data[:cpu_shares] = options.cpu_shares if options.cpu_shares
146
146
  data[:affinity] = options.affinity if options.affinity
147
- data[:env] = options.env if options.env
147
+ data[:env] = parse_env_options(options.env) if options.env
148
148
  data[:container_count] = options.instances if options.instances
149
149
  data[:cmd] = options.cmd.split(" ") if options.cmd
150
150
  data[:user] = options.user if options.user
@@ -153,5 +153,23 @@ module Kontena::Cli::Services
153
153
  data[:cap_drop] = options.cap_drop if options.cap_drop
154
154
  data
155
155
  end
156
+
157
+ ##
158
+ # @param [Array<String>] values
159
+ # @return [Array<String>]
160
+ def parse_env_options(values)
161
+ copy = values.dup
162
+ copy.each_index do |i|
163
+ key, value = copy[i].split("=", 2)
164
+ if value.nil?
165
+ copy[i] = "#{values[i - 1]},#{values[i]}"
166
+ elsif key != key.upcase
167
+ copy[i - 1] = "#{values[i - 1]},#{values[i]}"
168
+ copy.delete_at(i)
169
+ end
170
+ end
171
+
172
+ copy
173
+ end
156
174
  end
157
175
  end
@@ -12,17 +12,17 @@ module Kontena
12
12
  end
13
13
 
14
14
  def update_service(token, service_id, data)
15
- client(token).put("services/#{service_id}", data)
15
+ client(token).put("services/#{current_grid}/#{service_id}", data)
16
16
  end
17
17
 
18
18
  def get_service(token, service_id)
19
- client(token).get("services/#{service_id}")
19
+ client(token).get("services/#{current_grid}/#{service_id}")
20
20
  end
21
21
 
22
22
  def deploy_service(token, service_id, data)
23
- client(token).post("services/#{service_id}/deploy", data)
23
+ client(token).post("services/#{current_grid}/#{service_id}/deploy", data)
24
24
  print 'deploying '
25
- until client(token).get("services/#{service_id}")['state'] != 'deploying' do
25
+ until client(token).get("services/#{current_grid}/#{service_id}")['state'] != 'deploying' do
26
26
  print '.'
27
27
  sleep 1
28
28
  end
@@ -69,9 +69,7 @@ module Kontena
69
69
  memory.to_i
70
70
  end
71
71
  end
72
-
73
-
74
72
  end
75
73
  end
76
74
  end
77
- end
75
+ end
@@ -24,7 +24,7 @@ module Kontena::Cli::Services
24
24
  private
25
25
 
26
26
  def fetch_stats(token, service_id, follow)
27
- result = client(token).get("services/#{service_id}/stats")
27
+ result = client(token).get("services/#{current_grid}/#{service_id}/stats")
28
28
  system('clear') if follow
29
29
  render_header
30
30
  result['stats'].each do |stat|
@@ -57,9 +57,11 @@ module Kontena::Cli::Services
57
57
  # @return [String]
58
58
  def filesize_to_human(size)
59
59
  units = %w{B K M G T}
60
- e = (Math.log(size)/Math.log(1000)).floor
60
+ e = (Math.log(size) / Math.log(1000)).floor
61
61
  s = '%.2f' % (size.to_f / 1000**e)
62
62
  s.sub(/\.?0*$/, units[e])
63
+ rescue FloatDomainError
64
+ 'N/A'
63
65
  end
64
66
  end
65
- end
67
+ end
@@ -6,6 +6,7 @@ command 'deploy' do |c|
6
6
  c.description = 'Create and deploy multiple services from YAML file'
7
7
  c.option '-f', '--file String', 'path to kontena.yml file, default: current directory'
8
8
  c.option '-p', '--prefix String', 'prefix of service names, default: name of the current directory'
9
+ c.option '-s', '--service Array', Array, 'Services to deploy'
9
10
  c.action do |args, options|
10
11
  Kontena::Cli::Stacks::Stacks.new.deploy(options)
11
12
  end
@@ -18,9 +18,11 @@ module Kontena::Cli::Stacks
18
18
  require_token
19
19
 
20
20
  filename = options.file || './kontena.yml'
21
- @services = YAML.load(File.read(filename))
22
21
  @service_prefix = options.prefix || current_dir
23
22
 
23
+ @services = YAML.load(File.read(filename) % {prefix: service_prefix})
24
+ @services = @services.delete_if { |name, service| !options.service.include?(name)} if options.service
25
+
24
26
  Dir.chdir(File.dirname(filename))
25
27
  init_services(services)
26
28
  deploy_services(deploy_queue)
@@ -30,7 +32,7 @@ module Kontena::Cli::Stacks
30
32
 
31
33
  def init_services(services)
32
34
  services.each do |name, config|
33
- create_or_update_service(prefixed_name(name), config)
35
+ create_or_update_service(name, config)
34
36
  end
35
37
  end
36
38
 
@@ -47,8 +49,9 @@ module Kontena::Cli::Stacks
47
49
  end
48
50
 
49
51
  def create_or_update_service(name, options)
50
- # skip if service is already created or updated
51
- return nil if in_deploy_queue?(name)
52
+
53
+ # skip if service is already created or updated or it's not present
54
+ return nil if in_deploy_queue?(name) || !services.keys.include?(name)
52
55
 
53
56
  # create/update linked services recursively before continuing
54
57
  unless options['links'].nil?
@@ -56,7 +59,7 @@ module Kontena::Cli::Stacks
56
59
  # change prefixed service name also to links options
57
60
  options['links'][index] = "#{prefixed_name(linked_service[:name])}:#{linked_service[:alias]}"
58
61
 
59
- create_or_update_service(prefixed_name(linked_service[:name]), services[linked_service[:name]]) unless in_deploy_queue?(prefixed_name(linked_service[:name]))
62
+ create_or_update_service(linked_service[:name], services[linked_service[:name]]) unless in_deploy_queue?(linked_service[:name])
60
63
  end
61
64
  end
62
65
 
@@ -75,10 +78,11 @@ module Kontena::Cli::Stacks
75
78
  end
76
79
 
77
80
  def find_service_by_name(name)
78
- get_service(token, name) rescue nil
81
+ get_service(token, prefixed_name(name)) rescue nil
79
82
  end
80
83
 
81
84
  def create(name, options)
85
+ name = prefixed_name(name)
82
86
  puts "creating #{name}"
83
87
  data = {name: name}
84
88
  data.merge!(parse_data(options))
@@ -86,13 +90,14 @@ module Kontena::Cli::Stacks
86
90
  end
87
91
 
88
92
  def update(id, options)
93
+ id = prefixed_name(id)
89
94
  data = parse_data(options)
90
95
  puts "updating #{id}"
91
96
  update_service(token, id, data)
92
97
  end
93
98
 
94
99
  def in_deploy_queue?(name)
95
- deploy_queue.find {|service| service['id'] == name} != nil
100
+ deploy_queue.find {|service| service['id'] == prefixed_name(name)} != nil
96
101
  end
97
102
 
98
103
  def prefixed_name(name)
@@ -120,6 +125,8 @@ module Kontena::Cli::Stacks
120
125
  File.readlines(path).delete_if { |line| line.start_with?('#') || line.empty? }
121
126
  end
122
127
 
128
+ ##
129
+ # @param [Hash] options
123
130
  def parse_data(options)
124
131
  data = {}
125
132
  data[:image] = options['image']
@@ -1,5 +1,5 @@
1
1
  module Kontena
2
2
  module Cli
3
- VERSION = "0.6.1"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # resolve bin path, ignoring symlinks
5
+ require 'pathname'
6
+ bin_file = Pathname.new(__FILE__).realpath
7
+
8
+ # add self to libpath
9
+ $:.unshift File.expand_path('../../lib', bin_file)
10
+
11
+ require_relative '../client'
12
+ require_relative '../cli/common'
13
+
14
+ class Helper
15
+ include Kontena::Cli::Common
16
+
17
+ def client
18
+ token = require_token
19
+ super(token)
20
+ end
21
+
22
+ def grids
23
+ client.get("grids")['grids'].map{|grid| grid['id']}
24
+ rescue
25
+ []
26
+ end
27
+
28
+ def nodes
29
+ client.get("grids/#{current_grid}/nodes")['nodes'].map{|node| node['name']}
30
+ rescue
31
+ []
32
+ end
33
+
34
+ def services
35
+ services = client.get("grids/#{current_grid}/services")['services']
36
+ results = []
37
+ results.push services.map{|s| s['name']}
38
+ results.push services.map{|s| s['id']}
39
+
40
+ results
41
+ rescue
42
+ []
43
+ end
44
+
45
+ def containers
46
+ results = []
47
+ client.get("grids/#{current_grid}/services")['services'].each do |service|
48
+ containers = client.get("services/#{service['id']}/containers")['containers']
49
+ results.push(containers.map{|c| c['name'] })
50
+ results.push(containers.map{|c| c['id'] })
51
+ end
52
+ results
53
+ rescue
54
+ []
55
+ end
56
+ end
57
+
58
+ helper = Helper.new
59
+
60
+ words = ARGV
61
+ words.delete_at(0)
62
+
63
+ completion = []
64
+ completion.push %w(deploy forgot node grid invite service container vpn external-registry registry login logout whoami) if words.size < 2
65
+ if words.size > 0
66
+ case words[0]
67
+ when 'forgot'
68
+ completion.clear
69
+ sub_commands = %w(password)
70
+ when 'grid'
71
+ completion.clear
72
+ sub_commands = %w(add-user audit-log create current list list-users remove remove-user show use)
73
+ if words[1]
74
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
75
+ completion.push helper.grids
76
+ else
77
+ completion.push sub_commands
78
+ end
79
+ when 'node'
80
+ completion.clear
81
+ sub_commands = %w(list show remove)
82
+ if words[1]
83
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
84
+ completion.push helper.nodes
85
+ else
86
+ completion.push sub_commands
87
+ end
88
+ when 'service'
89
+ completion.clear
90
+ sub_commands = %w(containers create delete deploy list logs restart scale show start stats stop update)
91
+ if words[1]
92
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
93
+ completion.push helper.services
94
+ else
95
+ completion.push sub_commands
96
+ end
97
+ when 'container'
98
+ completion.clear
99
+ sub_commands = %w(exec)
100
+ if words[1]
101
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
102
+ completion.push helper.containers
103
+ else
104
+ completion.push sub_commands
105
+ end
106
+ when 'vpn'
107
+ completion.clear
108
+ completion.push %w(config create delete)
109
+ when 'external-registry'
110
+ completion.clear
111
+ completion.push %w(add list delete)
112
+ end
113
+ end
114
+
115
+ puts completion