kontena-cli 0.7.3 → 0.8.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.
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