kontena-cli 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/kontena +64 -2
  4. data/kontena-cli.gemspec +4 -2
  5. data/lib/kontena/cli/app_command.rb +20 -0
  6. data/lib/kontena/cli/apps/common.rb +53 -0
  7. data/lib/kontena/cli/{stacks/stacks.rb → apps/deploy_command.rb} +17 -31
  8. data/lib/kontena/cli/apps/init_command.rb +101 -0
  9. data/lib/kontena/cli/apps/list_command.rb +41 -0
  10. data/lib/kontena/cli/apps/logs_command.rb +58 -0
  11. data/lib/kontena/cli/apps/remove_command.rb +64 -0
  12. data/lib/kontena/cli/apps/start_command.rb +38 -0
  13. data/lib/kontena/cli/apps/stop_command.rb +38 -0
  14. data/lib/kontena/cli/container_command.rb +9 -0
  15. data/lib/kontena/cli/containers/{containers.rb → exec_command.rb} +5 -5
  16. data/lib/kontena/cli/deploy_command.rb +159 -0
  17. data/lib/kontena/cli/external_registries/add_command.rb +22 -0
  18. data/lib/kontena/cli/external_registries/delete_command.rb +13 -0
  19. data/lib/kontena/cli/external_registries/list_command.rb +16 -0
  20. data/lib/kontena/cli/external_registry_command.rb +14 -0
  21. data/lib/kontena/cli/forgot_password_command.rb +13 -0
  22. data/lib/kontena/cli/grid_command.rb +27 -0
  23. data/lib/kontena/cli/grids/add_user_command.rb +17 -0
  24. data/lib/kontena/cli/grids/{audit_log.rb → audit_log_command.rb} +7 -6
  25. data/lib/kontena/cli/grids/common.rb +24 -0
  26. data/lib/kontena/cli/grids/create_command.rb +26 -0
  27. data/lib/kontena/cli/grids/current_command.rb +18 -0
  28. data/lib/kontena/cli/grids/list_command.rb +26 -0
  29. data/lib/kontena/cli/grids/list_users_command.rb +18 -0
  30. data/lib/kontena/cli/grids/remove_command.rb +26 -0
  31. data/lib/kontena/cli/grids/remove_user_command.rb +16 -0
  32. data/lib/kontena/cli/grids/show_command.rb +19 -0
  33. data/lib/kontena/cli/grids/use_command.rb +21 -0
  34. data/lib/kontena/cli/invite_command.rb +13 -0
  35. data/lib/kontena/cli/login_command.rb +114 -0
  36. data/lib/kontena/cli/logout_command.rb +8 -0
  37. data/lib/kontena/cli/node_command.rb +21 -0
  38. data/lib/kontena/cli/nodes/digital_ocean/create_command.rb +31 -0
  39. data/lib/kontena/cli/nodes/digital_ocean/restart_command.rb +26 -0
  40. data/lib/kontena/cli/nodes/digital_ocean/terminate_command.rb +18 -0
  41. data/lib/kontena/cli/nodes/digital_ocean_command.rb +15 -0
  42. data/lib/kontena/cli/nodes/list_command.rb +28 -0
  43. data/lib/kontena/cli/nodes/remove_command.rb +15 -0
  44. data/lib/kontena/cli/nodes/show_command.rb +31 -0
  45. data/lib/kontena/cli/nodes/update_command.rb +18 -0
  46. data/lib/kontena/cli/nodes/vagrant/create_command.rb +26 -0
  47. data/lib/kontena/cli/nodes/vagrant/restart_command.rb +25 -0
  48. data/lib/kontena/cli/nodes/vagrant/ssh_command.rb +20 -0
  49. data/lib/kontena/cli/nodes/vagrant/start_command.rb +25 -0
  50. data/lib/kontena/cli/nodes/vagrant/stop_command.rb +25 -0
  51. data/lib/kontena/cli/nodes/vagrant/terminate_command.rb +16 -0
  52. data/lib/kontena/cli/nodes/vagrant_command.rb +21 -0
  53. data/lib/kontena/cli/register_command.rb +21 -0
  54. data/lib/kontena/cli/{grids/registry.rb → registry/create_command.rb} +32 -35
  55. data/lib/kontena/cli/registry/delete_command.rb +15 -0
  56. data/lib/kontena/cli/registry_command.rb +11 -0
  57. data/lib/kontena/cli/reset_password_command.rb +17 -0
  58. data/lib/kontena/cli/service_command.rb +33 -0
  59. data/lib/kontena/cli/services/container_command.rb +9 -0
  60. data/lib/kontena/cli/services/containers_command.rb +31 -0
  61. data/lib/kontena/cli/services/create_command.rb +62 -0
  62. data/lib/kontena/cli/services/delete_command.rb +17 -0
  63. data/lib/kontena/cli/services/deploy_command.rb +23 -0
  64. data/lib/kontena/cli/services/list_command.rb +20 -0
  65. data/lib/kontena/cli/services/logs_command.rb +51 -0
  66. data/lib/kontena/cli/services/restart_command.rb +16 -0
  67. data/lib/kontena/cli/services/scale_command.rb +20 -0
  68. data/lib/kontena/cli/services/services_helper.rb +94 -0
  69. data/lib/kontena/cli/services/show_command.rb +17 -0
  70. data/lib/kontena/cli/services/start_command.rb +16 -0
  71. data/lib/kontena/cli/services/{stats.rb → stats_command.rb} +11 -10
  72. data/lib/kontena/cli/services/stop_command.rb +16 -0
  73. data/lib/kontena/cli/services/update_command.rb +51 -0
  74. data/lib/kontena/cli/verify_account_command.rb +13 -0
  75. data/lib/kontena/cli/version_command.rb +8 -0
  76. data/lib/kontena/cli/vpn/config_command.rb +12 -0
  77. data/lib/kontena/cli/{grids/vpn.rb → vpn/create_command.rb} +12 -29
  78. data/lib/kontena/cli/vpn/delete_command.rb +15 -0
  79. data/lib/kontena/cli/vpn_command.rb +13 -0
  80. data/lib/kontena/cli/whoami_command.rb +19 -0
  81. data/lib/kontena/client.rb +14 -11
  82. data/lib/kontena/machine/common.rb +17 -0
  83. data/lib/kontena/machine/digital_ocean.rb +11 -0
  84. data/lib/kontena/machine/digital_ocean/cloudinit.yml +66 -0
  85. data/lib/kontena/machine/digital_ocean/node_destroyer.rb +38 -0
  86. data/lib/kontena/machine/digital_ocean/node_provisioner.rb +74 -0
  87. data/lib/kontena/machine/random_name.rb +42 -0
  88. data/lib/kontena/machine/vagrant.rb +10 -0
  89. data/lib/kontena/machine/vagrant/Vagrantfile.coreos.rb.erb +32 -0
  90. data/lib/kontena/machine/vagrant/cloudinit.yml +65 -0
  91. data/lib/kontena/machine/vagrant/node_destroyer.rb +36 -0
  92. data/lib/kontena/machine/vagrant/node_provisioner.rb +68 -0
  93. data/lib/kontena/scripts/completer +5 -5
  94. data/spec/kontena/cli/app/deploy_command_spec.rb +227 -0
  95. data/spec/kontena/cli/deploy_command_spec.rb +213 -0
  96. data/spec/kontena/cli/login_command_spec.rb +22 -0
  97. data/spec/kontena/cli/register_command_spec.rb +57 -0
  98. data/spec/spec_helper.rb +5 -1
  99. metadata +132 -36
  100. data/lib/kontena/cli/commands.rb +0 -20
  101. data/lib/kontena/cli/containers/commands.rb +0 -12
  102. data/lib/kontena/cli/grids/commands.rb +0 -169
  103. data/lib/kontena/cli/grids/external_registries.rb +0 -40
  104. data/lib/kontena/cli/grids/grids.rb +0 -108
  105. data/lib/kontena/cli/grids/users.rb +0 -32
  106. data/lib/kontena/cli/nodes/commands.rb +0 -27
  107. data/lib/kontena/cli/nodes/nodes.rb +0 -64
  108. data/lib/kontena/cli/server/commands.rb +0 -69
  109. data/lib/kontena/cli/server/server.rb +0 -45
  110. data/lib/kontena/cli/server/user.rb +0 -174
  111. data/lib/kontena/cli/services/commands.rb +0 -138
  112. data/lib/kontena/cli/services/containers.rb +0 -24
  113. data/lib/kontena/cli/services/logs.rb +0 -44
  114. data/lib/kontena/cli/services/services.rb +0 -175
  115. data/lib/kontena/cli/stacks/commands.rb +0 -13
  116. data/spec/kontena/cli/server/user_spec.rb +0 -59
  117. data/spec/kontena/cli/stacks/stacks_spec.rb +0 -212
@@ -0,0 +1,38 @@
1
+ require 'shell-spinner'
2
+
3
+ module Kontena
4
+ module Machine
5
+ module DigitalOcean
6
+ class NodeDestroyer
7
+ include RandomName
8
+
9
+ attr_reader :client, :api_client
10
+
11
+ # @param [Kontena::Client] api_client Kontena api client
12
+ # @param [String] token Digital Ocean token
13
+ def initialize(api_client, token)
14
+ @api_client = api_client
15
+ @client = DropletKit::Client.new(access_token: token)
16
+ end
17
+
18
+ def run!(grid, name)
19
+ droplet = client.droplets.all.find{|d| d.name == name}
20
+ if droplet
21
+ ShellSpinner "Terminating DigitalOcean droplet #{name.colorize(:cyan)} " do
22
+ client.droplets.delete(id: droplet.id)
23
+ sleep 2 until client.droplets.find(id: droplet.id).is_a?(String)
24
+ end
25
+ else
26
+ abort "Cannot find droplet #{name.colorize(:cyan)} in DigitalOcean"
27
+ end
28
+ node = api_client.get("grids/#{grid['id']}/nodes")['nodes'].find{|n| n['name'] == name}
29
+ if node
30
+ ShellSpinner "Removing node #{name.colorize(:cyan)} from grid #{grid['name'].colorize(:cyan)} " do
31
+ api_client.delete("grids/#{grid['id']}/nodes/#{name}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,74 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ require 'open3'
4
+ require 'shell-spinner'
5
+
6
+ module Kontena
7
+ module Machine
8
+ module DigitalOcean
9
+ class NodeProvisioner
10
+ include RandomName
11
+
12
+ attr_reader :client, :api_client
13
+
14
+ # @param [Kontena::Client] api_client Kontena api client
15
+ # @param [String] token Digital Ocean token
16
+ def initialize(api_client, token)
17
+ @api_client = api_client
18
+ @client = DropletKit::Client.new(access_token: token)
19
+ end
20
+
21
+ def run!(opts)
22
+ abort('Invalid ssh key') unless File.exists?(File.expand_path(opts[:ssh_key]))
23
+
24
+ ssh_key = ssh_key(File.read(File.expand_path(opts[:ssh_key])).strip)
25
+ abort('Ssh key does not exist in Digital Ocean') unless ssh_key
26
+
27
+ userdata_vars = {
28
+ version: opts[:version],
29
+ master_uri: opts[:master_uri],
30
+ grid_token: opts[:grid_token],
31
+ }
32
+
33
+ droplet = DropletKit::Droplet.new(
34
+ name: opts[:name] || generate_name,
35
+ region: opts[:region],
36
+ image: 'coreos-stable',
37
+ size: opts[:size],
38
+ private_networking: true,
39
+ user_data: user_data(userdata_vars),
40
+ ssh_keys: [ssh_key.id]
41
+ )
42
+ created = client.droplets.create(droplet)
43
+ ShellSpinner "Creating DigitalOcean droplet #{droplet.name.colorize(:cyan)} " do
44
+ sleep 5 until client.droplets.find(id: created.id).status == 'active'
45
+ end
46
+ ShellSpinner "Waiting for node #{droplet.name.colorize(:cyan)} join to grid #{opts[:grid].colorize(:cyan)} " do
47
+ sleep 2 until droplet_exists_in_grid?(opts[:grid], droplet)
48
+ end
49
+ end
50
+
51
+ def user_data(vars)
52
+ cloudinit_template = File.join(__dir__ , '/cloudinit.yml')
53
+ erb(File.read(cloudinit_template), vars)
54
+ end
55
+
56
+ def generate_name
57
+ "#{super}-#{rand(1..99)}"
58
+ end
59
+
60
+ def ssh_key(public_key)
61
+ ssh_key = client.ssh_keys.all.find{|key| key.public_key == public_key}
62
+ end
63
+
64
+ def droplet_exists_in_grid?(grid, droplet)
65
+ api_client.get("grids/#{grid}/nodes")['nodes'].find{|n| n['name'] == droplet.name}
66
+ end
67
+
68
+ def erb(template, vars)
69
+ ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ module Kontena
2
+ module Machine
3
+ module RandomName
4
+
5
+ def generate_name
6
+ rnd = Random.rand(64)
7
+
8
+ "#{adjectives[rnd%adjectives.length]}-#{nouns[rnd%adjectives.length]}"
9
+ end
10
+
11
+ private
12
+
13
+ def adjectives
14
+ [
15
+ "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
16
+ "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
17
+ "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
18
+ "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
19
+ "long", "late", "bold", "little", "morning", "muddy",
20
+ "red", "rough", "still", "small", "sparkling", "shy",
21
+ "wandering", "withered", "wild", "black", "young", "holy", "solitary",
22
+ "fragrant", "aged", "snowy", "proud", "floral", "restless",
23
+ "polished", "purple", "lively", "nameless"
24
+ ]
25
+ end
26
+
27
+ def nouns
28
+ [
29
+ "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
30
+ "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
31
+ "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
32
+ "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
33
+ "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
34
+ "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
35
+ "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
36
+ "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
37
+ "frog", "smoke", "star"
38
+ ]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'common'
2
+ include Kontena::Machine::Common
3
+
4
+ unless which('vagrant')
5
+ abort('Vagrant is not installed. See https://www.vagrantup.com/ for instructions.')
6
+ end
7
+
8
+ require_relative 'random_name'
9
+ require_relative 'vagrant/node_provisioner'
10
+ require_relative 'vagrant/node_destroyer'
@@ -0,0 +1,32 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+ VAGRANTFILE_API_VERSION = "2"
6
+
7
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+ config.vm.provider :virtualbox do |v|
9
+ v.check_guest_additions = false
10
+ v.functional_vboxsf = false
11
+ end
12
+
13
+ if Vagrant.has_plugin?("vagrant-vbguest") then
14
+ config.vbguest.auto_update = false
15
+ end
16
+
17
+ config.vm.define "<%= name %>" do |docker|
18
+ docker.vm.box = "coreos-stable"
19
+ docker.vm.box_url = "http://stable.release.core-os.net/amd64-usr/current/coreos_production_vagrant.json"
20
+ docker.vm.network "private_network", type: "dhcp"
21
+ docker.vm.hostname = "<%= name %>"
22
+ docker.vm.provider "virtualbox" do |vb|
23
+ vb.gui = false
24
+ vb.customize ["modifyvm", :id, "--memory", "<%= memory %>"]
25
+ vb.auto_nat_dns_proxy = false
26
+ vb.customize ["modifyvm", :id, "--natdnsproxy1", "off" ]
27
+ vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off" ]
28
+ end
29
+ docker.vm.provision :file, :source => "<%= cloudinit %>", :destination => "/tmp/vagrantfile-user-data"
30
+ docker.vm.provision :shell, :inline => "mv /tmp/vagrantfile-user-data /var/lib/coreos-vagrant/", :privileged => true
31
+ end
32
+ end
@@ -0,0 +1,65 @@
1
+ #cloud-config
2
+ write_files:
3
+ - path: /etc/kontena-agent.env
4
+ permissions: 0600
5
+ owner: root
6
+ content: |
7
+ KONTENA_URI="<%= master_uri %>"
8
+ KONTENA_TOKEN="<%= grid_token %>"
9
+ KONTENA_PEER_INTERFACE=eth1
10
+ KONTENA_VERSION=<%= version %>
11
+ - path: /etc/systemd/system/docker.service.d/50-insecure-registry.conf
12
+ content: |
13
+ [Service]
14
+ Environment='DOCKER_OPTS=--insecure-registry="10.81.0.0/19" --bip="172.17.42.1/16"'
15
+ coreos:
16
+ units:
17
+ - name: 00-eth.network
18
+ runtime: true
19
+ content: |
20
+ [Match]
21
+ Name=eth*
22
+ [Network]
23
+ DHCP=yes
24
+ DNS=172.17.42.1
25
+ DNS=172.28.128.1
26
+ DNS=8.8.8.8
27
+ DOMAINS=kontena.local
28
+ [DHCP]
29
+ UseDNS=false
30
+
31
+ - name: 10-weave.network
32
+ runtime: false
33
+ content: |
34
+ [Match]
35
+ Type=bridge
36
+ Name=weave*
37
+
38
+ [Network]
39
+ - name: kontena-agent.service
40
+ command: start
41
+ enable: true
42
+ content: |
43
+ [Unit]
44
+ Description=kontena-agent
45
+ After=network-online.target
46
+ After=docker.service
47
+ Description=Kontena Agent
48
+ Documentation=http://www.kontena.io/
49
+ Requires=network-online.target
50
+ Requires=docker.service
51
+
52
+ [Service]
53
+ Restart=always
54
+ RestartSec=5
55
+ EnvironmentFile=/etc/kontena-agent.env
56
+ ExecStartPre=-/usr/bin/docker stop kontena-agent
57
+ ExecStartPre=-/usr/bin/docker rm kontena-agent
58
+ ExecStartPre=/usr/bin/docker pull kontena/agent:${KONTENA_VERSION}
59
+ ExecStart=/usr/bin/docker run --name kontena-agent \
60
+ -e KONTENA_URI=${KONTENA_URI} \
61
+ -e KONTENA_TOKEN=${KONTENA_TOKEN} \
62
+ -e KONTENA_PEER_INTERFACE=${KONTENA_PEER_INTERFACE} \
63
+ -v=/var/run/docker.sock:/var/run/docker.sock \
64
+ --net=host \
65
+ kontena/agent:${KONTENA_VERSION}
@@ -0,0 +1,36 @@
1
+
2
+ module Kontena
3
+ module Machine
4
+ module Vagrant
5
+ class NodeDestroyer
6
+ include RandomName
7
+
8
+ attr_reader :client, :api_client
9
+
10
+ # @param [Kontena::Client] api_client Kontena api client
11
+ def initialize(api_client)
12
+ @api_client = api_client
13
+ end
14
+
15
+ def run!(grid, name)
16
+ vagrant_path = "#{Dir.home}/.kontena/#{grid}/#{name}"
17
+ Dir.chdir(vagrant_path) do
18
+ ShellSpinner "Terminating Vagrant machine #{name.colorize(:cyan)} " do
19
+ Open3.popen2('vagrant destroy -f') do |stdin, output, wait|
20
+ while o = output.gets
21
+ puts o if ENV['DEBUG']
22
+ end
23
+ end
24
+ end
25
+ end
26
+ node = api_client.get("grids/#{grid}/nodes")['nodes'].find{|n| n['name'] == name}
27
+ if node
28
+ ShellSpinner "Removing node #{name.colorize(:cyan)} from grid #{grid.colorize(:cyan)} " do
29
+ api_client.delete("grids/#{grid}/nodes/#{name}")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+ require 'open3'
4
+ require 'shell-spinner'
5
+
6
+ module Kontena
7
+ module Machine
8
+ module Vagrant
9
+ class NodeProvisioner
10
+ include RandomName
11
+
12
+ attr_reader :client, :api_client
13
+
14
+ # @param [Kontena::Client] api_client Kontena api client
15
+ def initialize(api_client)
16
+ @api_client = api_client
17
+ end
18
+
19
+ def run!(opts)
20
+ grid = opts[:grid]
21
+ name = opts[:name] || generate_name
22
+ version = opts[:version]
23
+ vagrant_path = "#{Dir.home}/.kontena/#{grid}/#{name}"
24
+ FileUtils.mkdir_p(vagrant_path)
25
+
26
+ template = File.join(__dir__ , '/Vagrantfile.coreos.rb.erb')
27
+ cloudinit_template = File.join(__dir__ , '/cloudinit.yml')
28
+ vars = {
29
+ name: name,
30
+ version: version,
31
+ memory: opts[:memory] || 1024,
32
+ master_uri: opts[:master_uri],
33
+ grid_token: opts[:grid_token],
34
+ cloudinit: "#{vagrant_path}/cloudinit.yml"
35
+ }
36
+ vagrant_data = erb(File.read(template), vars)
37
+ cloudinit = erb(File.read(cloudinit_template), vars)
38
+ File.write("#{vagrant_path}/Vagrantfile", vagrant_data)
39
+ File.write("#{vagrant_path}/cloudinit.yml", cloudinit)
40
+ Dir.chdir(vagrant_path) do
41
+ ShellSpinner "Creating Vagrant machine #{name.colorize(:cyan)} " do
42
+ Open3.popen2('vagrant box update && vagrant up') do |stdin, output, wait|
43
+ while o = output.gets
44
+ print o if ENV['DEBUG']
45
+ end
46
+ end
47
+ end
48
+ ShellSpinner "Waiting for node #{name.colorize(:cyan)} join to grid #{grid.colorize(:cyan)} " do
49
+ sleep 1 until node_exists_in_grid?(grid, name)
50
+ end
51
+ end
52
+ end
53
+
54
+ def generate_name
55
+ "#{super}-#{rand(1..99)}"
56
+ end
57
+
58
+ def erb(template, vars)
59
+ ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
60
+ end
61
+
62
+ def node_exists_in_grid?(grid, name)
63
+ api_client.get("grids/#{grid}/nodes")['nodes'].find{|n| n['name'] == name}
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -60,12 +60,9 @@ words = ARGV
60
60
  words.delete_at(0)
61
61
 
62
62
  completion = []
63
- completion.push %w(deploy forgot node grid invite service container vpn external-registry registry login logout whoami) if words.size < 2
63
+ completion.push %w(app deploy forgot-password node grid invite service container vpn external-registry registry login logout whoami) if words.size < 2
64
64
  if words.size > 0
65
65
  case words[0]
66
- when 'forgot'
67
- completion.clear
68
- sub_commands = %w(password)
69
66
  when 'grid'
70
67
  completion.clear
71
68
  sub_commands = %w(add-user audit-log create current list list-users remove remove-user show use)
@@ -77,7 +74,7 @@ if words.size > 0
77
74
  end
78
75
  when 'node'
79
76
  completion.clear
80
- sub_commands = %w(list show remove)
77
+ sub_commands = %w(list show remove vagrant digitalocean)
81
78
  if words[1]
82
79
  completion.push(sub_commands) unless sub_commands.include?(words[1])
83
80
  completion.push helper.nodes
@@ -108,6 +105,9 @@ if words.size > 0
108
105
  when 'external-registry'
109
106
  completion.clear
110
107
  completion.push %w(add list delete)
108
+ when 'app'
109
+ completion.clear
110
+ completion.push %w(init deploy start stop remove rm ps list)
111
111
  end
112
112
  end
113
113
 
@@ -0,0 +1,227 @@
1
+ require_relative "../../../spec_helper"
2
+ require "kontena/cli/apps/deploy_command"
3
+
4
+ describe Kontena::Cli::Apps::DeployCommand do
5
+
6
+ let(:subject) do
7
+ described_class.new(File.basename($0))
8
+ end
9
+
10
+ let(:settings) do
11
+ {'server' => {'url' => 'http://kontena.test', 'token' => token}}
12
+ end
13
+
14
+ let(:token) do
15
+ '1234567'
16
+ end
17
+
18
+ let(:docker_compose_yml) do
19
+ yml_content = <<yml
20
+ wordpress:
21
+ image: wordpress:4.1
22
+ ports:
23
+ - 80:80
24
+ links:
25
+ - mysql:mysql
26
+ mysql:
27
+ image: mysql:5.6
28
+ yml
29
+ yml_content
30
+ end
31
+
32
+ let(:kontena_yml) do
33
+ yml_content = <<yml
34
+ wordpress:
35
+ extends:
36
+ file: docker-compose.yml
37
+ service: wordpress
38
+ stateful: true
39
+ environment:
40
+ - WORDPRESS_DB_PASSWORD=%{prefix}_secret
41
+ instances: 2
42
+ deploy:
43
+ strategy: ha
44
+ mysql:
45
+ extends:
46
+ file: docker-compose.yml
47
+ service: mysql
48
+ stateful: true
49
+ environment:
50
+ - MYSQL_ROOT_PASSWORD=%{prefix}_secret
51
+ yml
52
+ yml_content
53
+ end
54
+
55
+ let(:services) do
56
+ {
57
+ 'wordpress' => {
58
+ 'image' => 'wordpress:latest',
59
+ 'links' => ['mysql:db'],
60
+ 'ports' => ['80:80'],
61
+ 'instances' => 2,
62
+ 'deploy' => {
63
+ 'strategy' => 'ha'
64
+ }
65
+ },
66
+ 'mysql' => {
67
+ 'image' => 'mysql:5.6',
68
+ 'stateful' => true
69
+ }
70
+ }
71
+ end
72
+
73
+ let(:client) do
74
+ double
75
+ end
76
+
77
+ let(:options) do
78
+ double({prefix: false, file: false, service: nil})
79
+ end
80
+
81
+ let(:env_vars) do
82
+ ["#comment line", "TEST_ENV_VAR=test", "MYSQL_ADMIN_PASSWORD=abcdef"]
83
+ end
84
+
85
+ let(:dot_env) do
86
+ ["TEST_ENV_VAR=test2","", "TEST_ENV_VAR2=test3"]
87
+ end
88
+
89
+ describe '#deploy' do
90
+ context 'when api_url is nil' do
91
+ it 'raises error' do
92
+ allow(subject).to receive(:settings).and_return({'server' => {}})
93
+ expect{subject.run([])}.to raise_error(ArgumentError)
94
+ end
95
+ end
96
+
97
+ context 'when token is nil' do
98
+ it 'raises error' do
99
+ allow(subject).to receive(:settings).and_return({'server' => {'url' => 'http://kontena.test'}})
100
+ expect{subject.run([])}.to raise_error(ArgumentError)
101
+ end
102
+ end
103
+
104
+ context 'when api url and token are valid' do
105
+ before(:each) do
106
+ allow(subject).to receive(:settings).and_return(settings)
107
+ allow(File).to receive(:exists?).and_return(true)
108
+ allow(File).to receive(:read).with('kontena.yml').and_return(kontena_yml)
109
+ allow(File).to receive(:read).with('docker-compose.yml').and_return(docker_compose_yml)
110
+ allow(subject).to receive(:get_service).and_raise(Kontena::Errors::StandardError.new(404, 'Not Found'))
111
+ allow(subject).to receive(:create_service).and_return({'id' => 'kontena-test-mysql'},{'id' => 'kontena-test-wordpress'})
112
+ allow(subject).to receive(:current_grid).and_return('1')
113
+ allow(subject).to receive(:deploy_service).and_return(nil)
114
+ end
115
+
116
+ it 'reads ./kontena.yml file by default' do
117
+ allow(subject).to receive(:settings).and_return(settings)
118
+ expect(File).to receive(:read).with('kontena.yml').and_return(kontena_yml)
119
+ subject.run([])
120
+ end
121
+
122
+ it 'reads given yml file' do
123
+ expect(File).to receive(:read).with('custom.yml').and_return(kontena_yml)
124
+ subject.run(["--file", "custom.yml"])
125
+ end
126
+
127
+ it 'uses current directory as service name prefix by default' do
128
+ current_dir = '/kontena/tests/stacks'
129
+ allow(Dir).to receive(:getwd).and_return(current_dir)
130
+ expect(File).to receive(:basename).with(current_dir).and_return('stacks')
131
+ subject.run([])
132
+ end
133
+
134
+ context 'when yml file has multiple env files' do
135
+ it 'merges environment variables correctly' do
136
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
137
+ allow(YAML).to receive(:load).and_return(services)
138
+ services['wordpress']['environment'] = ['MYSQL_ADMIN_PASSWORD=password']
139
+ services['wordpress']['env_file'] = %w(/path/to/env_file .env)
140
+
141
+ expect(File).to receive(:readlines).with('/path/to/env_file').and_return(env_vars)
142
+ expect(File).to receive(:readlines).with('.env').and_return(dot_env)
143
+
144
+ data = {
145
+ :name =>"kontena-test-wordpress",
146
+ :image=>"wordpress:latest",
147
+ :env=>["MYSQL_ADMIN_PASSWORD=password", "TEST_ENV_VAR=test", "TEST_ENV_VAR2=test3"],
148
+ :container_count=>2,
149
+ :stateful=>false,
150
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"db"}],
151
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
152
+ }
153
+
154
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
155
+ subject.run([])
156
+ end
157
+ end
158
+
159
+ context 'when yml file has one env file' do
160
+ it 'merges environment variables correctly' do
161
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
162
+ allow(YAML).to receive(:load).and_return(services)
163
+ services['wordpress']['environment'] = ['MYSQL_ADMIN_PASSWORD=password']
164
+ services['wordpress']['env_file'] = '/path/to/env_file'
165
+
166
+ expect(File).to receive(:readlines).with('/path/to/env_file').and_return(env_vars)
167
+
168
+ data = {
169
+ :name =>"kontena-test-wordpress",
170
+ :image=>"wordpress:latest",
171
+ :env=>["MYSQL_ADMIN_PASSWORD=password", "TEST_ENV_VAR=test"],
172
+ :container_count=>2,
173
+ :stateful=>false,
174
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"db"}],
175
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
176
+ }
177
+
178
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
179
+ subject.run([])
180
+ end
181
+ end
182
+
183
+ it 'creates mysql service before wordpress' do
184
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
185
+ data = {:name =>"kontena-test-mysql", :image=>'mysql:5.6', :env=>["MYSQL_ROOT_PASSWORD=kontena-test_secret"], :container_count=>nil, :stateful=>true}
186
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
187
+
188
+ subject.run([])
189
+ end
190
+
191
+ it 'creates wordpress service' do
192
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
193
+
194
+ data = {
195
+ :name =>"kontena-test-wordpress",
196
+ :image=>"wordpress:4.1",
197
+ :env=>["WORDPRESS_DB_PASSWORD=kontena-test_secret"],
198
+ :container_count=>2,
199
+ :stateful=>true,
200
+ :links=>[{:name=>"kontena-test-mysql", :alias=>"mysql"}],
201
+ :ports=>[{:container_port=>"80", :node_port=>"80", :protocol=>"tcp"}]
202
+ }
203
+ expect(subject).to receive(:create_service).with('1234567', '1', data)
204
+
205
+ subject.run([])
206
+ end
207
+
208
+ it 'deploys services' do
209
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
210
+ expect(subject).to receive(:deploy_service).with('1234567', 'kontena-test-mysql', {})
211
+ expect(subject).to receive(:deploy_service).with('1234567', 'kontena-test-wordpress', {:strategy => 'ha'})
212
+ subject.run([])
213
+ end
214
+
215
+ context 'when giving service option' do
216
+ it 'deploys only given services' do
217
+ allow(subject).to receive(:current_dir).and_return("kontena-test")
218
+ allow(subject).to receive(:deploy_services).and_return({})
219
+ expect(subject).to receive(:create).once.with('wordpress', anything).and_return({})
220
+ expect(subject).not_to receive(:create).with('mysql', services['mysql'])
221
+
222
+ subject.run(['wordpress'])
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end