kontena-cli 0.5.0 → 0.6.0

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: 32f7df3c75d9dd5cde332f7cf19267508230be21
4
- data.tar.gz: ed374c9d0f1a69e216ff547d7927b67c9b09ae1b
3
+ metadata.gz: 7da42d25b33946f8887d78d4c1e966ddce0836d4
4
+ data.tar.gz: 9cc5dbbd4eae20285b82d9c06a790560deface6c
5
5
  SHA512:
6
- metadata.gz: 59b51892ecc57e51c305823aa39450a9f07970ac5aa91d23a9a7bec7682f5ec141918f70d87590071c46864e1b46a5b182f055e30bfaebce9e29be88fc548a67
7
- data.tar.gz: 438d344f304461f7d518020125d67882e92067f2dec89a0bd0c4763ad345ff84882abf202a59e5a25a0bd1a2efe61900a8f2968b57894b929230294a612658af
6
+ metadata.gz: 3375614c89cd186046754ae837082edb85c894dabf54ec97ac35a45281f16c173490889ac814bf503e8211baf904b358717707a377f15702e0356bc05388c30e
7
+ data.tar.gz: 967ee20ab75a338ae4a6ca872b193a3fe8087d79c1c20d56a6f9b72cab1f0fa80a131af6f84e34992fe64ddb3b88277345fa05b1a586e0ec65632253bdc1c404
data/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ FROM gliderlabs/alpine:edge
2
+ MAINTAINER jari@kontena.io
3
+
4
+ RUN apk update && \
5
+ apk --update add ruby ruby-json ca-certificates libssl1.0 openssl libstdc++ && \
6
+ gem install kontena-cli --no-rdoc --no-ri
7
+
8
+
9
+ RUN adduser kontena -D -h /home/kontena -s /bin/sh
10
+ RUN chown -R kontena.kontena /home/kontena
11
+
12
+
13
+ VOLUME ["/home/kontena"]
14
+ WORKDIR /home/kontena
15
+ USER kontena
16
+ ENTRYPOINT ["/usr/bin/kontena"]
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in kontena-cli.gemspec
4
4
  gemspec
5
+ group :development, :test do
6
+ gem "rspec"
7
+ end
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+ Dir.glob('tasks/*.rake').each { |r| import r }
4
+
5
+ task :default => :spec
6
+
data/kontena-docker.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+
3
+ docker inspect kontena-cli-data > /dev/null 2>&1 ||
4
+ docker create --name kontena-cli-data kontena/cli:latest > /dev/null
5
+
6
+ docker run -it --rm --volumes-from kontena-cli-data kontena/cli:latest $@
@@ -16,4 +16,5 @@ require_relative 'server/commands'
16
16
  require_relative 'containers/commands'
17
17
  require_relative 'grids/commands'
18
18
  require_relative 'nodes/commands'
19
- require_relative 'services/commands'
19
+ require_relative 'services/commands'
20
+ require_relative 'stacks/commands'
@@ -28,6 +28,10 @@ module Kontena
28
28
  @client
29
29
  end
30
30
 
31
+ def reset_client
32
+ @client = nil
33
+ end
34
+
31
35
  def settings_filename
32
36
  File.join(Dir.home, '/.kontena_client.json')
33
37
  end
@@ -3,6 +3,7 @@ module Kontena::Cli::Grids; end;
3
3
  require_relative 'grids'
4
4
  require_relative 'users'
5
5
  require_relative 'audit_log'
6
+ require_relative 'vpn'
6
7
 
7
8
 
8
9
  command 'grid list' do |c|
@@ -87,3 +88,30 @@ command 'grid remove-user' do |c|
87
88
  Kontena::Cli::Grids::Users.new.remove(args[0])
88
89
  end
89
90
  end
91
+
92
+
93
+ command 'vpn create' do |c|
94
+ c.syntax = 'kontena vpn create'
95
+ c.description = 'Create vpn service'
96
+ c.option '--node STRING', String, 'Node name'
97
+ c.option '--ip STRING', String, 'Node ip'
98
+ c.action do |args, options|
99
+ Kontena::Cli::Grids::Vpn.new.create(options)
100
+ end
101
+ end
102
+
103
+ command 'vpn delete' do |c|
104
+ c.syntax = 'kontena vpn delete'
105
+ c.description = 'Delete vpn service'
106
+ c.action do |args, options|
107
+ Kontena::Cli::Grids::Vpn.new.delete
108
+ end
109
+ end
110
+
111
+ command 'vpn config' do |c|
112
+ c.syntax = 'kontena vpn config'
113
+ c.description = 'Show vpn client config'
114
+ c.action do |args, options|
115
+ Kontena::Cli::Grids::Vpn.new.config
116
+ end
117
+ end
@@ -0,0 +1,71 @@
1
+ require 'kontena/client'
2
+ require_relative '../common'
3
+
4
+ module Kontena::Cli::Grids
5
+ class Vpn
6
+ include Kontena::Cli::Common
7
+
8
+ def create(opts)
9
+ require_api_url
10
+ token = require_token
11
+ preferred_node = opts.node
12
+
13
+ vpn = client(token).get("services/vpn") rescue nil
14
+ raise ArgumentError.new('Vpn already exists') if vpn
15
+
16
+ nodes = client(token).get("grids/#{current_grid}/nodes")
17
+ if preferred_node.nil?
18
+ node = nodes['nodes'].find{|n| n['connected']}
19
+ raise ArgumentError.new('Cannot find any online nodes') if node.nil?
20
+ else
21
+ node = nodes['nodes'].find{|n| n['connected'] && n['name'] == preferred_node }
22
+ raise ArgumentError.new('Node not found') if node.nil?
23
+ end
24
+
25
+ public_ip = opts.ip || node['public_ip']
26
+
27
+ data = {
28
+ name: 'vpn',
29
+ stateful: true,
30
+ image: 'kontena/openvpn:latest',
31
+ ports: [
32
+ {
33
+ container_port: '1194',
34
+ node_port: '1194',
35
+ protocol: 'udp'
36
+ }
37
+ ],
38
+ cap_add: ['NET_ADMIN'],
39
+ env: ["OVPN_SERVER_URL=udp://#{public_ip}:1194"],
40
+ affinity: ["node==#{node['name']}"]
41
+ }
42
+ client(token).post("grids/#{current_grid}/services", data)
43
+ result = client(token).post("services/vpn/deploy", {})
44
+ print 'deploying '
45
+ until client(token).get("services/vpn")['state'] != 'deploying' do
46
+ print '.'
47
+ sleep 1
48
+ end
49
+ puts ' done'
50
+ puts "OpenVPN service is now started (udp://#{public_ip}:1194)."
51
+ puts "Use 'kontena vpn config' to fetch OpenVPN client config to your machine (it takes a while until config is ready)."
52
+ end
53
+
54
+ def delete
55
+ require_api_url
56
+ token = require_token
57
+
58
+ vpn = client(token).get("services/vpn") rescue nil
59
+ raise ArgumentError.new("VPN service does not exist") if vpn.nil?
60
+
61
+ client(token).delete("services/vpn")
62
+ end
63
+
64
+ def config
65
+ require_api_url
66
+ payload = {cmd: ['/usr/local/bin/ovpn_getclient', 'KONTENA_VPN_CLIENT']}
67
+ stdout, stderr = client(require_token).post("containers/vpn-1/exec", payload)
68
+ puts stdout
69
+ end
70
+ end
71
+ end
@@ -38,6 +38,7 @@ module Kontena::Cli::Nodes
38
38
  puts " id: #{node['id']}"
39
39
  puts " connected: #{node['connected'] ? 'yes': 'no'}"
40
40
  puts " last connect: #{node['updated_at']}"
41
+ puts " public ip: #{node['public_ip']}"
41
42
  puts " os: #{node['os']}"
42
43
  puts " driver: #{node['driver']}"
43
44
  puts " kernel: #{node['kernel_version']}"
@@ -21,7 +21,17 @@ module Kontena::Cli::Server
21
21
  if response
22
22
  settings['server']['token'] = response['access_token']
23
23
  save_settings
24
- print color('Login Successful', :green)
24
+ puts ''
25
+ puts "Welcome #{response['user']['name'].green}"
26
+ puts ''
27
+ reset_client
28
+ grid = client(require_token).get('grids')['grids'][0]
29
+ if grid
30
+ self.current_grid = grid
31
+ puts "Using grid: #{grid['name'].cyan}"
32
+ else
33
+ clear_current_grid
34
+ end
25
35
  true
26
36
  else
27
37
  print color('Login Failed', :red)
@@ -15,12 +15,30 @@ module Kontena::Cli::Services
15
15
  query_params = last_id.nil? ? '' : "from=#{last_id}"
16
16
  result = client(token).get("services/#{service_id}/container_logs?#{query_params}")
17
17
  result['logs'].each do |log|
18
- puts log['data']
18
+ color = color_for_container(log['container_id'])
19
+ puts "#{log['container_id'][0..12].colorize(color)} | #{log['data']}"
19
20
  last_id = log['id']
20
21
  end
21
22
  break unless options.follow
22
23
  sleep(2)
23
24
  end
24
25
  end
26
+
27
+ def color_for_container(container_id)
28
+ color_maps[container_id] = colors.shift unless color_maps[container_id]
29
+ color_maps[container_id].to_sym
30
+ end
31
+
32
+ def color_maps
33
+ @color_maps ||= {}
34
+ end
35
+
36
+ def colors
37
+ if(@colors.nil? || @colors.size == 0)
38
+ @colors = [:green, :yellow, :magenta, :cyan, :red,
39
+ :light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
40
+ end
41
+ @colors
42
+ end
25
43
  end
26
- end
44
+ end
@@ -1,9 +1,11 @@
1
1
  require 'kontena/client'
2
2
  require_relative '../common'
3
+ require_relative 'services_helper'
3
4
 
4
5
  module Kontena::Cli::Services
5
6
  class Services
6
7
  include Kontena::Cli::Common
8
+ include Kontena::Cli::Services::ServicesHelper
7
9
 
8
10
  def list
9
11
  require_api_url
@@ -21,7 +23,7 @@ module Kontena::Cli::Services
21
23
  require_api_url
22
24
  token = require_token
23
25
 
24
- service = client(token).get("services/#{service_id}")
26
+ service = get_service(token, service_id)
25
27
  puts "#{service['id']}:"
26
28
  puts " status: #{service['state'] }"
27
29
  puts " stateful: #{service['stateful'] == true ? 'yes' : 'no' }"
@@ -43,8 +45,8 @@ module Kontena::Cli::Services
43
45
  end
44
46
  puts " links: "
45
47
  if service['links']
46
- service['links'].each do |p|
47
- puts " - #{p['alias']}"
48
+ service['links'].each do |l|
49
+ puts " - #{l['alias']}"
48
50
  end
49
51
  end
50
52
  puts " containers:"
@@ -55,6 +57,7 @@ module Kontena::Cli::Services
55
57
  puts " node: #{container['node']['name']}"
56
58
  puts " dns: #{container['id']}.kontena.local"
57
59
  puts " ip: #{container['network_settings']['ip_address']}"
60
+ puts " public ip: #{container['node']['public_ip']}"
58
61
  if container['status'] == 'unknown'
59
62
  puts " status: #{container['status'].colorize(:yellow)}"
60
63
  else
@@ -71,33 +74,23 @@ module Kontena::Cli::Services
71
74
  def deploy(service_id, options)
72
75
  require_api_url
73
76
  token = require_token
74
-
75
77
  data = {}
76
78
  data[:strategy] = options.strategy if options.strategy
77
79
  data[:wait_for_port] = options.wait_for_port if options.wait_for_port
78
- result = client(token).post("services/#{service_id}/deploy", data)
79
-
80
- print 'deploying '
81
- until client(token).get("services/#{service_id}")['state'] != 'deploying' do
82
- print '.'
83
- sleep 1
84
- end
85
- puts ' done'
86
- puts ''
80
+ deploy_service(token, service_id, data)
87
81
  self.show(service_id)
88
82
  end
89
83
 
84
+
90
85
  def restart(service_id)
91
86
  require_api_url
92
87
  token = require_token
93
-
94
88
  result = client(token).post("services/#{service_id}/restart", {})
95
89
  end
96
90
 
97
91
  def stop(service_id)
98
92
  require_api_url
99
93
  token = require_token
100
-
101
94
  result = client(token).post("services/#{service_id}/stop", {})
102
95
  end
103
96
 
@@ -111,55 +104,22 @@ module Kontena::Cli::Services
111
104
  def create(name, image, options)
112
105
  require_api_url
113
106
  token = require_token
114
- if options.ports
115
- ports = parse_ports(options.ports)
116
- end
117
107
  data = {
118
108
  name: name,
119
109
  image: image,
120
110
  stateful: !!options.stateful
121
111
  }
122
- if options.link
123
- links = parse_links(options.link)
124
- end
125
- data[:ports] = ports if options.ports
126
- data[:links] = links if options.link
127
- data[:volumes] = options.volume if options.volume
128
- data[:volumes_from] = options.volumes_from if options.volumes_from
129
- data[:memory] = parse_memory(options.memory) if options.memory
130
- data[:memory_swap] = parse_memory(options.memory_swap) if options.memory_swap
131
- data[:cpu_shares] = options.cpu_shares if options.cpu_shares
132
- data[:affinity] = options.affinity if options.affinity
133
- data[:env] = options.env if options.env
134
- data[:container_count] = options.instances if options.instances
135
- data[:cmd] = options.cmd.split(" ") if options.cmd
136
- data[:user] = options.user if options.user
137
- data[:cpu] = options.cpu if options.cpu
138
- data[:cap_add] = options.cap_add if options.cap_add
139
- data[:cap_drop] = options.cap_drop if options.cap_drop
140
- if options.memory
141
- memory = human_size_to_number(options.memory)
142
- raise ArgumentError.new('Invalid --memory')
143
- data[:memory] = memory
144
- end
145
- data[:memory] = options.memory if options.memory
146
- client(token).post("grids/#{current_grid}/services", data)
112
+ data.merge!(parse_data_from_options(options))
113
+ create_service(token, current_grid, data)
147
114
  end
148
115
 
116
+
149
117
  def update(service_id, options)
150
118
  require_api_url
151
119
  token = require_token
152
120
 
153
- data = {}
154
- data[:env] = options.env if options.env
155
- data[:container_count] = options.instances if options.instances
156
- data[:cmd] = options.cmd.split(" ") if options.cmd
157
- data[:ports] = parse_ports(options.ports) if options.ports
158
- data[:image] = options.image if options.image
159
- data[:cap_add] = options.cap_add if options.cap_add
160
- data[:cap_drop] = options.cap_drop if options.cap_drop
161
-
162
- client(require_token).put("services/#{service_id}", data)
121
+ data = parse_data_from_options(options)
122
+ update_service(token, service_id, data)
163
123
  end
164
124
 
165
125
  def destroy(service_id)
@@ -171,31 +131,27 @@ module Kontena::Cli::Services
171
131
 
172
132
  private
173
133
 
174
- def parse_ports(port_options)
175
- port_options.map{|p|
176
- node_port, container_port, protocol = p.split(':')
177
- if node_port.nil? || container_port.nil?
178
- raise ArgumentError.new("Invalid port value #{p}")
179
- end
180
- {
181
- container_port: container_port,
182
- node_port: node_port,
183
- protocol: protocol || 'tcp'
184
- }
185
- }
186
- end
187
-
188
- def parse_links(link_options)
189
- link_options.map{|l|
190
- service_name, alias_name = l.split(':')
191
- if service_name.nil? || alias_name.nil?
192
- raise ArgumentError.new("Invalid link value #{l}")
193
- end
194
- {
195
- name: service_name,
196
- alias: alias_name
197
- }
198
- }
134
+ ##
135
+ # parse given options to hash
136
+ # @return [Hash]
137
+ def parse_data_from_options(options)
138
+ data = {}
139
+ data[:ports] = parse_ports(options.ports) if options.ports
140
+ data[:links] = parse_links(options.link) if options.link
141
+ data[:volumes] = options.volume if options.volume
142
+ data[:volumes_from] = options.volumes_from if options.volumes_from
143
+ data[:memory] = parse_memory(options.memory) if options.memory
144
+ data[:memory_swap] = parse_memory(options.memory_swap) if options.memory_swap
145
+ data[:cpu_shares] = options.cpu_shares if options.cpu_shares
146
+ data[:affinity] = options.affinity if options.affinity
147
+ data[:env] = options.env if options.env
148
+ data[:container_count] = options.instances if options.instances
149
+ data[:cmd] = options.cmd.split(" ") if options.cmd
150
+ data[:user] = options.user if options.user
151
+ data[:image] = options.image if options.image
152
+ data[:cap_add] = options.cap_add if options.cap_add
153
+ data[:cap_drop] = options.cap_drop if options.cap_drop
154
+ data
199
155
  end
200
156
  end
201
157
  end
@@ -0,0 +1,77 @@
1
+ require 'kontena/client'
2
+ require_relative '../common'
3
+
4
+ module Kontena
5
+ module Cli
6
+ module Services
7
+ module ServicesHelper
8
+ include Kontena::Cli::Common
9
+
10
+ def create_service(token, grid_id, data)
11
+ client(token).post("grids/#{grid_id}/services", data)
12
+ end
13
+
14
+ def update_service(token, service_id, data)
15
+ client(token).put("services/#{service_id}", data)
16
+ end
17
+
18
+ def get_service(token, service_id)
19
+ client(token).get("services/#{service_id}")
20
+ end
21
+
22
+ def deploy_service(token, service_id, data)
23
+ client(token).post("services/#{service_id}/deploy", data)
24
+ print 'deploying '
25
+ until client(token).get("services/#{service_id}")['state'] != 'deploying' do
26
+ print '.'
27
+ sleep 1
28
+ end
29
+ puts ' done'
30
+ puts ''
31
+ end
32
+
33
+ def parse_ports(port_options)
34
+ port_options.map{|p|
35
+ node_port, container_port, protocol = p.split(':')
36
+ if node_port.nil? || container_port.nil?
37
+ raise ArgumentError.new("Invalid port value #{p}")
38
+ end
39
+ {
40
+ container_port: container_port,
41
+ node_port: node_port,
42
+ protocol: protocol || 'tcp'
43
+ }
44
+ }
45
+ end
46
+
47
+ def parse_links(link_options)
48
+ link_options.map{|l|
49
+ service_name, alias_name = l.split(':')
50
+ if service_name.nil?
51
+ raise ArgumentError.new("Invalid link value #{l}")
52
+ end
53
+ alias_name = service_name if alias_name.nil?
54
+ {
55
+ name: service_name,
56
+ alias: alias_name
57
+ }
58
+ }
59
+ end
60
+
61
+ def parse_memory(memory)
62
+ if memory.end_with?('k')
63
+ memory.to_i * 1000
64
+ elsif memory.end_with?('m')
65
+ memory.to_i * 1000000
66
+ elsif memory.end_with?('g')
67
+ memory.to_i * 1000000000
68
+ else
69
+ memory.to_i
70
+ end
71
+ end
72
+
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -51,20 +51,6 @@ module Kontena::Cli::Services
51
51
  network_out = stat['network'].nil? ? 'N/A' : filesize_to_human(stat['network']['tx_bytes'])
52
52
  puts '%-30.30s %-15s %-20s %-15s %-15s' % [ stat['container_id'], "#{cpu}%", "#{memory} / #{memory_limit}", "#{memory_pct}", "#{network_in}/#{network_out}"]
53
53
  end
54
- ##
55
- # @param [String] memory
56
- # @return [Integer]
57
- def parse_memory(memory)
58
- if memory.end_with?('k')
59
- memory.to_i * 1000
60
- elsif memory.end_with?('m')
61
- memory.to_i * 1000000
62
- elsif memory.end_with?('g')
63
- memory.to_i * 1000000000
64
- else
65
- memory.to_i
66
- end
67
- end
68
54
 
69
55
  ##
70
56
  # @param [Integer] size
@@ -0,0 +1,12 @@
1
+ module Kontena::Cli::Stacks; end;
2
+ require_relative 'stacks'
3
+
4
+ command 'deploy' do |c|
5
+ c.syntax = 'kontena deploy'
6
+ c.description = 'Create and deploy multiple services from YAML file'
7
+ c.option '-f', '--file String', 'path to kontena.yml file, default: current directory'
8
+ c.option '-p', '--prefix String', 'prefix of service names, default: name of the current directory'
9
+ c.action do |args, options|
10
+ Kontena::Cli::Stacks::Stacks.new.deploy(options)
11
+ end
12
+ end
@@ -0,0 +1,148 @@
1
+ require 'kontena/client'
2
+ require 'yaml'
3
+ require_relative '../common'
4
+ require_relative '../services/services_helper'
5
+
6
+ module Kontena::Cli::Stacks
7
+ class Stacks
8
+ include Kontena::Cli::Common
9
+ include Kontena::Cli::Services::ServicesHelper
10
+
11
+ attr_reader :services, :service_prefix, :deploy_queue
12
+ def initialize
13
+ @deploy_queue = []
14
+ end
15
+
16
+ def deploy(options)
17
+ require_api_url
18
+ require_token
19
+
20
+ filename = options.file || './kontena.yml'
21
+ @services = YAML.load(File.read(filename))
22
+ @service_prefix = options.prefix || current_dir
23
+
24
+ Dir.chdir(File.dirname(filename))
25
+ init_services(services)
26
+ deploy_services(deploy_queue)
27
+ end
28
+
29
+ private
30
+
31
+ def init_services(services)
32
+ services.each do |name, config|
33
+ create_or_update_service(prefixed_name(name), config)
34
+ end
35
+ end
36
+
37
+ def deploy_services(queue)
38
+ queue.each do |service|
39
+ puts "deploying #{service['id']}"
40
+ data = {}
41
+ if service['deploy']
42
+ data[:strategy] = service['deploy']['strategy'] if service['deploy']['strategy']
43
+ data[:wait_for_port] = service['deploy']['wait_for_port'] if service['deploy']['wait_for_port']
44
+ end
45
+ deploy_service(token, service['id'], data)
46
+ end
47
+ end
48
+
49
+ 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
+ # create/update linked services recursively before continuing
54
+ unless options['links'].nil?
55
+ parse_links(options['links']).each_with_index do |linked_service, index|
56
+ # change prefixed service name also to links options
57
+ options['links'][index] = "#{prefixed_name(linked_service[:name])}:#{linked_service[:alias]}"
58
+
59
+ create_or_update_service(prefixed_name(linked_service[:name]), services[linked_service[:name]]) unless in_deploy_queue?(prefixed_name(linked_service[:name]))
60
+ end
61
+ end
62
+
63
+ merge_env_vars(options)
64
+
65
+ if find_service_by_name(name)
66
+ service = update(name, options)
67
+ else
68
+ service = create(name, options)
69
+ end
70
+
71
+ # add deploy options to service
72
+ service['deploy'] = options['deploy']
73
+
74
+ deploy_queue.push service
75
+ end
76
+
77
+ def find_service_by_name(name)
78
+ get_service(token, name) rescue nil
79
+ end
80
+
81
+ def create(name, options)
82
+ puts "creating #{name}"
83
+ data = {name: name}
84
+ data.merge!(parse_data(options))
85
+ create_service(token, current_grid, data)
86
+ end
87
+
88
+ def update(id, options)
89
+ data = parse_data(options)
90
+ puts "updating #{id}"
91
+ update_service(token, id, data)
92
+ end
93
+
94
+ def in_deploy_queue?(name)
95
+ deploy_queue.find {|service| service['id'] == name} != nil
96
+ end
97
+
98
+ def prefixed_name(name)
99
+ "#{service_prefix}-#{name}"
100
+ end
101
+
102
+ def current_dir
103
+ File.basename(Dir.getwd)
104
+ end
105
+
106
+ def merge_env_vars(options)
107
+ return unless options['env_file']
108
+
109
+ options['env_file'] = [options['env_file']] if options['env_file'].is_a?(String)
110
+ options['environment'] = [] unless options['environment']
111
+
112
+ options['env_file'].each do |env_file|
113
+ options['environment'].concat(read_env_file(env_file))
114
+ end
115
+
116
+ options['environment'].uniq! {|s| s.split('=').first}
117
+ end
118
+
119
+ def read_env_file(path)
120
+ File.readlines(path).delete_if { |line| line.start_with?('#') || line.empty? }
121
+ end
122
+
123
+ def parse_data(options)
124
+ data = {}
125
+ data[:image] = options['image']
126
+ data[:env] = options['environment']
127
+ data[:container_count] = options['instances']
128
+ data[:links] = parse_links(options['links']) if options['links']
129
+ data[:ports] = parse_ports(options['ports']) if options['ports']
130
+ data[:memory] = parse_memory(options['mem_limit']) if options['mem_limit']
131
+ data[:memory_swap] = parse_memory(options['memswap_limit']) if options['memswap_limit']
132
+ data[:cpu_shares] = options['cpu_shares'] if options['cpu_shares']
133
+ data[:volumes] = options['volume'] if options['volume']
134
+ data[:volumes_from] = options['volumes_from'] if options['volumes_from']
135
+ data[:cmd] = options['command'].split(" ") if options['command']
136
+ data[:affinity] = options['affinity'] if options['affinity']
137
+ data[:user] = options['user'] if options['user']
138
+ data[:stateful] = options['stateful'] == true
139
+ data[:cap_add] = options['cap_add'] if options['cap_add']
140
+ data[:cap_drop] = options['cap_drop'] if options['cap_drop']
141
+ data
142
+ end
143
+
144
+ def token
145
+ @token ||= require_token
146
+ end
147
+ end
148
+ end
@@ -1,5 +1,5 @@
1
1
  module Kontena
2
2
  module Cli
3
- VERSION = "0.5.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -171,7 +171,11 @@ module Kontena
171
171
  end
172
172
 
173
173
  def handle_error_response(response)
174
- raise Kontena::Errors::StandardError.new(response.status, response.body)
174
+ message = response.body
175
+ if response.status == 404 && message == ''
176
+ message = 'Not found'
177
+ end
178
+ raise Kontena::Errors::StandardError.new(response.status, message)
175
179
  end
176
180
  end
177
181
  end
@@ -0,0 +1,103 @@
1
+ require_relative "../../../spec_helper"
2
+ require "kontena/cli/services/services_helper"
3
+
4
+ module Kontena::Cli::Services
5
+ describe ServicesHelper do
6
+ subject{klass.new}
7
+
8
+ let(:klass) { Class.new { include ServicesHelper } }
9
+
10
+ let(:client) do
11
+ double
12
+ end
13
+
14
+ let(:token) do
15
+ 'token'
16
+ end
17
+
18
+ before(:each) do
19
+ allow(subject).to receive(:client).with(token).and_return(client)
20
+ end
21
+
22
+ describe '#create_service' do
23
+ it 'creates POST grids/:id/services request to Kontena Server' do
24
+ expect(client).to receive(:post).with('grids/1/services', {'name' => 'test-service'})
25
+ subject.create_service(token, '1', {'name' => 'test-service'})
26
+ end
27
+ end
28
+
29
+ describe '#update_service' do
30
+ it 'creates PUT services/:id request to Kontena Server' do
31
+ expect(client).to receive(:put).with('services/1', {'name' => 'test-service'})
32
+ subject.update_service(token, '1', {'name' => 'test-service'})
33
+ end
34
+ end
35
+
36
+ describe '#get_service' do
37
+ it 'creates GET services/:id request to Kontena Server' do
38
+ expect(client).to receive(:get).with('services/test-service')
39
+ subject.get_service(token, 'test-service')
40
+ end
41
+ end
42
+
43
+ describe '#deploy_service' do
44
+ it 'creates POST services/:id/deploy request to Kontena Server' do
45
+ allow(client).to receive(:get).with('services/1').and_return({'state' => 'running'})
46
+ expect(client).to receive(:post).with('services/1/deploy', {'strategy' => 'ha'})
47
+ subject.deploy_service(token, '1', {'strategy' => 'ha'})
48
+ end
49
+
50
+ it 'polls Kontena Server until service is running' do
51
+ allow(client).to receive(:post).with('services/1/deploy', anything)
52
+ expect(client).to receive(:get).with('services/1').twice.and_return({'state' => 'deploying'}, {'state' => 'running'})
53
+
54
+ subject.deploy_service(token, '1', {'strategy' => 'ha'})
55
+ end
56
+ end
57
+
58
+ describe '#parse_ports' do
59
+ it 'raises error if node_port is missing' do
60
+ expect{
61
+ subject.parse_ports(["80"])
62
+ }.to raise_error(ArgumentError)
63
+ end
64
+
65
+ it 'raises error if container_port is missing' do
66
+ expect{
67
+ subject.parse_ports(["80:"])
68
+ }.to raise_error(ArgumentError)
69
+ end
70
+
71
+ it 'returns hash of port options' do
72
+ valid_result = [{
73
+ container_port: '80',
74
+ node_port: '80',
75
+ protocol: 'tcp'
76
+ }]
77
+ port_options = subject.parse_ports(['80:80'])
78
+
79
+ expect(port_options).to eq(valid_result)
80
+
81
+ end
82
+ end
83
+
84
+ describe '#parse_links' do
85
+ it 'raises error if service name is missing' do
86
+ expect{
87
+ subject.parse_links([""])
88
+ }.to raise_error(ArgumentError)
89
+ end
90
+
91
+ it 'returns hash of link options' do
92
+ valid_result = [{
93
+ name: 'db',
94
+ alias: 'mysql',
95
+ }]
96
+ link_options = subject.parse_links(['db:mysql'])
97
+
98
+ expect(link_options).to eq(valid_result)
99
+
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,179 @@
1
+ require_relative "../../../spec_helper"
2
+ require "kontena/cli/stacks/stacks"
3
+
4
+ module Kontena::Cli::Stacks
5
+ describe Stacks do
6
+ let(:settings) do
7
+ {'server' => {'url' => 'http://kontena.test', 'token' => token}}
8
+ end
9
+
10
+ let(:token) do
11
+ '1234567'
12
+ end
13
+
14
+
15
+ let(:services) do
16
+ {
17
+ 'wordpress' => {
18
+ 'image' => 'wordpress:latest',
19
+ 'links' => ['mysql:db'],
20
+ 'ports' => ['80:80'],
21
+ 'instances' => 2,
22
+ 'deploy' => {
23
+ 'strategy' => 'ha'
24
+ }
25
+ },
26
+ 'mysql' => {
27
+ 'image' => 'mysql:5.6',
28
+ 'stateful' => true
29
+ }
30
+ }
31
+ end
32
+
33
+ let(:client) do
34
+ double
35
+ end
36
+
37
+ let(:options) do
38
+ options = double({prefix: false, file: false})
39
+ end
40
+
41
+ let(:env_vars) do
42
+ ["#comment line", "TEST_ENV_VAR=test", "MYSQL_ADMIN_PASSWORD=abcdef"]
43
+ end
44
+
45
+ let(:dot_env) do
46
+ ["TEST_ENV_VAR=test2","", "TEST_ENV_VAR2=test3"]
47
+ end
48
+
49
+ describe '#deploy' do
50
+ context 'when api_url is nil' do
51
+ it 'raises error' do
52
+ allow(subject).to receive(:settings).and_return({'server' => {}})
53
+ expect{subject.deploy({})}.to raise_error(ArgumentError)
54
+ end
55
+ end
56
+
57
+ context 'when token is nil' do
58
+ it 'raises error' do
59
+ allow(subject).to receive(:settings).and_return({'server' => {'url' => 'http://kontena.test'}})
60
+ expect{subject.deploy({})}.to raise_error(ArgumentError)
61
+ end
62
+ end
63
+
64
+ context 'when api url and token are valid' do
65
+ before(:each) do
66
+ allow(subject).to receive(:settings).and_return(settings)
67
+ allow(YAML).to receive(:load).and_return(services)
68
+ allow(File).to receive(:read)
69
+ allow(subject).to receive(:get_service).and_raise(Kontena::Errors::StandardError.new(404, 'Not Found'))
70
+ allow(subject).to receive(:create_service).and_return({'id' => 'kontena-test-mysql'},{'id' => 'kontena-test-wordpress'})
71
+ allow(subject).to receive(:current_grid).and_return('1')
72
+ allow(subject).to receive(:deploy_service).and_return(nil)
73
+ end
74
+
75
+ it 'reads ./kontena.yml file by default' do
76
+ allow(subject).to receive(:settings).and_return(settings)
77
+
78
+ expect(File).to receive(:read).with('./kontena.yml')
79
+ expect(options).to receive(:file).once.and_return(false)
80
+ subject.deploy(options)
81
+ end
82
+
83
+ it 'reads given yml file' do
84
+ expect(options).to receive(:file).once.and_return('custom.yml')
85
+ expect(File).to receive(:read).with('custom.yml')
86
+ subject.deploy(options)
87
+ end
88
+
89
+ it 'uses current directory as service name prefix by default' do
90
+ current_dir = '/kontena/tests/stacks'
91
+ allow(Dir).to receive(:getwd).and_return(current_dir)
92
+ expect(File).to receive(:basename).with(current_dir)
93
+ subject.deploy(options)
94
+ end
95
+
96
+ context 'when yml file has multiple env files' do
97
+ it 'merges environment variables correctly' do
98
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
99
+ services['wordpress']['environment'] = ['MYSQL_ADMIN_PASSWORD=password']
100
+ services['wordpress']['env_file'] = %w(/path/to/env_file .env)
101
+
102
+ expect(File).to receive(:readlines).with('/path/to/env_file').and_return(env_vars)
103
+ expect(File).to receive(:readlines).with('.env').and_return(dot_env)
104
+
105
+ data = {
106
+ :name =>"kontena-test-wordpress",
107
+ :image=>"wordpress:latest",
108
+ :env=>["MYSQL_ADMIN_PASSWORD=password", "TEST_ENV_VAR=test", "TEST_ENV_VAR2=test3"],
109
+ :container_count=>2,
110
+ :stateful=>false,
111
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"db"}],
112
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
113
+ }
114
+
115
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
116
+ subject.deploy(options)
117
+ end
118
+ end
119
+
120
+ context 'when yml file has one env file' do
121
+ it 'merges environment variables correctly' do
122
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
123
+ services['wordpress']['environment'] = ['MYSQL_ADMIN_PASSWORD=password']
124
+ services['wordpress']['env_file'] = '/path/to/env_file'
125
+
126
+ expect(File).to receive(:readlines).with('/path/to/env_file').and_return(env_vars)
127
+
128
+ data = {
129
+ :name =>"kontena-test-wordpress",
130
+ :image=>"wordpress:latest",
131
+ :env=>["MYSQL_ADMIN_PASSWORD=password", "TEST_ENV_VAR=test"],
132
+ :container_count=>2,
133
+ :stateful=>false,
134
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"db"}],
135
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
136
+ }
137
+
138
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
139
+ subject.deploy(options)
140
+ end
141
+ end
142
+
143
+ it 'creates mysql service before wordpress' do
144
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
145
+ data = {:name =>"kontena-test-mysql", :image=>'mysql:5.6', :env=>nil, :container_count=>nil, :stateful=>true}
146
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
147
+
148
+ subject.deploy(options)
149
+ end
150
+
151
+ it 'creates wordpress service' do
152
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
153
+
154
+ data = {
155
+ :name =>"kontena-test-wordpress",
156
+ :image=>"wordpress:latest",
157
+ :env=>nil,
158
+ :container_count=>2,
159
+ :stateful=>false,
160
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"db"}],
161
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
162
+ }
163
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
164
+
165
+ subject.deploy(options)
166
+ end
167
+
168
+ it 'deploys services' do
169
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
170
+ expect(subject).to receive(:deploy_service).with('1234567', 'kontena-test-mysql', {})
171
+ expect(subject).to receive(:deploy_service).with('1234567', 'kontena-test-wordpress', {:strategy => 'ha'})
172
+ subject.deploy(options)
173
+ end
174
+
175
+
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,19 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+
13
+ # Run specs in random order to surface order dependencies. If you find an
14
+ # order dependency and want to debug it, you can fix the order by providing
15
+ # the seed, which is printed after each run.
16
+ # --seed 1234
17
+ config.order = 'random'
18
+
19
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,5 @@
1
+ begin
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ rescue LoadError
5
+ end
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: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kontena, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-02 00:00:00.000000000 Z
11
+ date: 2015-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -89,12 +89,14 @@ extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
91
  - ".gitignore"
92
+ - Dockerfile
92
93
  - Gemfile
93
94
  - LICENSE.txt
94
95
  - README.md
95
96
  - Rakefile
96
97
  - bin/kontena
97
98
  - kontena-cli.gemspec
99
+ - kontena-docker.sh
98
100
  - lib/kontena/cli/commands.rb
99
101
  - lib/kontena/cli/common.rb
100
102
  - lib/kontena/cli/containers/commands.rb
@@ -103,6 +105,7 @@ files:
103
105
  - lib/kontena/cli/grids/commands.rb
104
106
  - lib/kontena/cli/grids/grids.rb
105
107
  - lib/kontena/cli/grids/users.rb
108
+ - lib/kontena/cli/grids/vpn.rb
106
109
  - lib/kontena/cli/nodes/commands.rb
107
110
  - lib/kontena/cli/nodes/nodes.rb
108
111
  - lib/kontena/cli/server/commands.rb
@@ -112,10 +115,17 @@ files:
112
115
  - lib/kontena/cli/services/containers.rb
113
116
  - lib/kontena/cli/services/logs.rb
114
117
  - lib/kontena/cli/services/services.rb
118
+ - lib/kontena/cli/services/services_helper.rb
115
119
  - lib/kontena/cli/services/stats.rb
120
+ - lib/kontena/cli/stacks/commands.rb
121
+ - lib/kontena/cli/stacks/stacks.rb
116
122
  - lib/kontena/cli/version.rb
117
123
  - lib/kontena/client.rb
118
124
  - lib/kontena/errors.rb
125
+ - spec/kontena/cli/services/services_helper_spec.rb
126
+ - spec/kontena/cli/stacks/stacks_spec.rb
127
+ - spec/spec_helper.rb
128
+ - tasks/rspec.rake
119
129
  homepage: http://www.kontena.io
120
130
  licenses:
121
131
  - Apache-2.0
@@ -140,4 +150,7 @@ rubygems_version: 2.2.2
140
150
  signing_key:
141
151
  specification_version: 4
142
152
  summary: Kontena command line tool
143
- test_files: []
153
+ test_files:
154
+ - spec/kontena/cli/services/services_helper_spec.rb
155
+ - spec/kontena/cli/stacks/stacks_spec.rb
156
+ - spec/spec_helper.rb