kontena-cli 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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