kontena-cli 0.13.4 → 0.14.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/kontena-cli.gemspec +2 -0
  4. data/lib/kontena/cli/app_command.rb +2 -0
  5. data/lib/kontena/cli/apps/common.rb +80 -74
  6. data/lib/kontena/cli/apps/config_command.rb +29 -0
  7. data/lib/kontena/cli/apps/deploy_command.rb +12 -81
  8. data/lib/kontena/cli/apps/docker_helper.rb +3 -3
  9. data/lib/kontena/cli/apps/init_command.rb +0 -3
  10. data/lib/kontena/cli/apps/list_command.rb +2 -3
  11. data/lib/kontena/cli/apps/logs_command.rb +2 -3
  12. data/lib/kontena/cli/apps/monitor_command.rb +3 -4
  13. data/lib/kontena/cli/apps/remove_command.rb +4 -4
  14. data/lib/kontena/cli/apps/restart_command.rb +2 -3
  15. data/lib/kontena/cli/apps/scale_command.rb +3 -5
  16. data/lib/kontena/cli/apps/service_generator.rb +123 -0
  17. data/lib/kontena/cli/apps/service_generator_v2.rb +26 -0
  18. data/lib/kontena/cli/apps/show_command.rb +1 -2
  19. data/lib/kontena/cli/apps/start_command.rb +2 -3
  20. data/lib/kontena/cli/apps/stop_command.rb +2 -3
  21. data/lib/kontena/cli/apps/yaml/reader.rb +150 -0
  22. data/lib/kontena/cli/apps/yaml/service_extender.rb +60 -0
  23. data/lib/kontena/cli/apps/yaml/validations.rb +79 -0
  24. data/lib/kontena/cli/apps/yaml/validator.rb +55 -0
  25. data/lib/kontena/cli/apps/yaml/validator_v2.rb +74 -0
  26. data/lib/kontena/cli/common.rb +23 -0
  27. data/lib/kontena/cli/etcd/remove_command.rb +2 -0
  28. data/lib/kontena/cli/grids/remove_command.rb +2 -0
  29. data/lib/kontena/cli/grids/users/remove_command.rb +3 -0
  30. data/lib/kontena/cli/master/azure/create_command.rb +0 -2
  31. data/lib/kontena/cli/master/packet/create_command.rb +42 -0
  32. data/lib/kontena/cli/master/packet_command.rb +14 -0
  33. data/lib/kontena/cli/master/upcloud/create_command.rb +39 -0
  34. data/lib/kontena/cli/master/upcloud_command.rb +13 -0
  35. data/lib/kontena/cli/master/users/remove_command.rb +3 -0
  36. data/lib/kontena/cli/master/users/roles/remove_command.rb +2 -0
  37. data/lib/kontena/cli/master_command.rb +4 -0
  38. data/lib/kontena/cli/node_command.rb +4 -0
  39. data/lib/kontena/cli/nodes/azure/create_command.rb +0 -2
  40. data/lib/kontena/cli/nodes/list_command.rb +4 -8
  41. data/lib/kontena/cli/nodes/packet/create_command.rb +35 -0
  42. data/lib/kontena/cli/nodes/packet/restart_command.rb +17 -0
  43. data/lib/kontena/cli/nodes/packet/terminate_command.rb +20 -0
  44. data/lib/kontena/cli/nodes/packet_command.rb +15 -0
  45. data/lib/kontena/cli/nodes/remove_command.rb +2 -0
  46. data/lib/kontena/cli/nodes/show_command.rb +3 -1
  47. data/lib/kontena/cli/nodes/upcloud/create_command.rb +33 -0
  48. data/lib/kontena/cli/nodes/upcloud/restart_command.rb +20 -0
  49. data/lib/kontena/cli/nodes/upcloud/terminate_command.rb +20 -0
  50. data/lib/kontena/cli/nodes/upcloud_command.rb +15 -0
  51. data/lib/kontena/cli/registry/remove_command.rb +3 -0
  52. data/lib/kontena/cli/services/remove_command.rb +2 -0
  53. data/lib/kontena/cli/services/services_helper.rb +1 -0
  54. data/lib/kontena/cli/vault/list_command.rb +2 -0
  55. data/lib/kontena/cli/vault/read_command.rb +2 -0
  56. data/lib/kontena/cli/vault/remove_command.rb +4 -0
  57. data/lib/kontena/cli/vault/update_command.rb +8 -1
  58. data/lib/kontena/cli/vault/write_command.rb +2 -0
  59. data/lib/kontena/cli/vpn/remove_command.rb +3 -0
  60. data/lib/kontena/machine/azure/master_provisioner.rb +2 -2
  61. data/lib/kontena/machine/azure/node_provisioner.rb +7 -4
  62. data/lib/kontena/machine/digital_ocean/node_provisioner.rb +1 -1
  63. data/lib/kontena/machine/packet.rb +17 -0
  64. data/lib/kontena/machine/packet/cloudinit.yml +66 -0
  65. data/lib/kontena/machine/packet/cloudinit_master.yml +118 -0
  66. data/lib/kontena/machine/packet/master_provisioner.rb +93 -0
  67. data/lib/kontena/machine/packet/node_destroyer.rb +42 -0
  68. data/lib/kontena/machine/packet/node_provisioner.rb +77 -0
  69. data/lib/kontena/machine/packet/node_restarter.rb +41 -0
  70. data/lib/kontena/machine/packet/packet_common.rb +89 -0
  71. data/lib/kontena/machine/upcloud.rb +9 -0
  72. data/lib/kontena/machine/upcloud/cloudinit.yml +64 -0
  73. data/lib/kontena/machine/upcloud/cloudinit_master.yml +118 -0
  74. data/lib/kontena/machine/upcloud/master_provisioner.rb +136 -0
  75. data/lib/kontena/machine/upcloud/node_destroyer.rb +82 -0
  76. data/lib/kontena/machine/upcloud/node_provisioner.rb +119 -0
  77. data/lib/kontena/machine/upcloud/node_restarter.rb +47 -0
  78. data/lib/kontena/machine/upcloud/upcloud_common.rb +70 -0
  79. data/lib/kontena/scripts/completer +8 -3
  80. data/spec/fixtures/docker-compose_v2.yml +10 -0
  81. data/spec/fixtures/kontena-invalid.yml +4 -0
  82. data/spec/fixtures/kontena-with-variables.yml +19 -0
  83. data/spec/fixtures/kontena.yml +2 -2
  84. data/spec/fixtures/kontena_v2.yml +35 -0
  85. data/spec/kontena/cli/app/common_spec.rb +39 -101
  86. data/spec/kontena/cli/app/deploy_command_spec.rb +37 -388
  87. data/spec/kontena/cli/app/docker_helper_spec.rb +4 -4
  88. data/spec/kontena/cli/app/service_generator_spec.rb +374 -0
  89. data/spec/kontena/cli/app/service_generator_v2_spec.rb +74 -0
  90. data/spec/kontena/cli/app/yaml/reader_spec.rb +249 -0
  91. data/spec/kontena/cli/app/yaml/service_extender_spec.rb +104 -0
  92. data/spec/kontena/cli/app/yaml/validator_spec.rb +263 -0
  93. data/spec/kontena/cli/app/yaml/validator_v2_spec.rb +309 -0
  94. data/spec/kontena/cli/common_spec.rb +39 -1
  95. data/spec/kontena/cli/master/users/remove_command_spec.rb +9 -0
  96. data/spec/kontena/cli/master/users/roles/remove_command_spec.rb +2 -0
  97. metadata +86 -2
@@ -0,0 +1,60 @@
1
+ require 'yaml'
2
+
3
+ module Kontena::Cli::Apps
4
+ module YAML
5
+ class ServiceExtender
6
+ attr_reader :service_config
7
+
8
+ # @param [Hash] service_config
9
+ def initialize(service_config)
10
+ @service_config = service_config
11
+ end
12
+
13
+ # @param [Hash] from
14
+ # @return [Hash]
15
+ def extend(from)
16
+ service_config['environment'] = extend_env_vars(
17
+ from['environment'],
18
+ service_config['environment']
19
+ )
20
+ service_config['secrets'] = extend_secrets(
21
+ from['secrets'],
22
+ service_config['secrets']
23
+ )
24
+ from.merge(service_config)
25
+ end
26
+
27
+ private
28
+
29
+ # @param [Array] from
30
+ # @param [Array] to
31
+ # @return [Array]
32
+ def extend_env_vars(from, to)
33
+ env_vars = to || []
34
+ if from
35
+ from.each do |env|
36
+ env_vars << env unless to && to.find do |key|
37
+ key.split('=').first == env.split('=').first
38
+ end
39
+ end
40
+ end
41
+ env_vars
42
+ end
43
+
44
+ # @param [Array] from
45
+ # @param [Array] to
46
+ # @return [Array]
47
+ def extend_secrets(from, to)
48
+ secrets = to || []
49
+ if from
50
+ from.each do |from_secret|
51
+ secrets << from_secret unless to && to.any? do |to_secret|
52
+ to_secret['secret'] == from_secret['secret']
53
+ end
54
+ end
55
+ end
56
+ secrets
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,79 @@
1
+ module Kontena::Cli::Apps::YAML
2
+ module Validations
3
+
4
+ def append_common_validations(base)
5
+ base.optional('image').maybe(:str?)
6
+ base.optional('extends').schema do
7
+ key('service').required(:str?)
8
+ optional('file') { str? }
9
+ end
10
+ base.optional('stateful') { bool? }
11
+ base.optional('affinity') { array? { each { format?(/(?<=\!|\=)=/) } } }
12
+ base.optional('cap_add') { array? | none? }
13
+ base.optional('cap_drop') { array? | none? }
14
+ base.optional('command') { str? | none? }
15
+ base.optional('cpu_shares') { int? | none? }
16
+ base.optional('external_links') { array? }
17
+ base.optional('mem_limit') { int? | str? }
18
+ base.optional('memswap_limit') { int? | str? }
19
+ base.optional('environment') { array? | type?(Hash) }
20
+ base.optional('env_file') { str? | array? }
21
+ base.optional('instances') { int? }
22
+ base.optional('links') { array? | empty? }
23
+ base.optional('ports') { array? }
24
+ base.optional('volumes') { array? }
25
+ base.optional('volumes_from') { array? }
26
+ base.optional('deploy').schema do
27
+ optional('strategy') { inclusion?(%w(ha daemon random)) }
28
+ optional('wait_for_port') { int? }
29
+ optional('min_health') { float? }
30
+ end
31
+ base.optional('hooks').schema do
32
+ optional('post_start').each do
33
+ key('name').required
34
+ key('cmd').required
35
+ key('instances') { int? | eql?('*') }
36
+ optional('oneshot') { bool? }
37
+ end
38
+ optional('pre_build').each do
39
+ key('cmd').required
40
+ end
41
+ end
42
+ base.optional('secrets').each do
43
+ key('secret').required
44
+ key('name').required
45
+ key('type').required
46
+ end
47
+ end
48
+
49
+ ##
50
+ # @param [Hash] service_config
51
+ def validate_options(service_config)
52
+ @yaml_schema.call(service_config)
53
+ end
54
+
55
+ ##
56
+ # @param [Hash] service_config
57
+ # @return [Array<String>] errors
58
+ def validate_keys(service_config)
59
+ errors = {}
60
+ service_config.keys.each do |key|
61
+ error = validate_key(key)
62
+ errors[key] = error if error
63
+ end
64
+ errors
65
+ end
66
+
67
+ ##
68
+ # @param [String] key
69
+ def validate_key(key)
70
+ if self.class::UNSUPPORTED_KEYS.include?(key)
71
+ ['unsupported option']
72
+ elsif !self.class::VALID_KEYS.include?(key)
73
+ ['invalid option']
74
+ else
75
+ nil
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,55 @@
1
+ require 'dry-validation'
2
+ module Kontena::Cli::Apps
3
+ module YAML
4
+ class Validator
5
+ require_relative 'validations'
6
+ include Validations
7
+
8
+ VALID_KEYS = %w(
9
+ affinity build dockerfile cap_add cap_drop command deploy env_file environment extends external_links
10
+ image links log_driver log_opt net pid ports volumes volumes_from cpu_shares
11
+ mem_limit memswap_limit privileged stateful user instances hooks secrets
12
+ ).freeze
13
+
14
+ UNSUPPORTED_KEYS = %w(
15
+ cgroup_parent container_name devices depends_on dns dns_search tmpfs entrypoint
16
+ expose extra_hosts labels logging network_mode networks security_opt stop_signal ulimits volume_driver
17
+ cpu_quota cpuset domainname hostname ipc mac_address
18
+ read_only restart shm_size stdin_open tty working_dir
19
+ ).freeze
20
+
21
+ ##
22
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
23
+ def initialize(need_image=false)
24
+ base = self
25
+ @yaml_schema = Dry::Validation.Schema do
26
+ base.append_common_validations(self)
27
+ optional('build').maybe(:str?)
28
+ optional('dockerfile') { str? }
29
+ optional('net') { inclusion?(%w(host bridge)) }
30
+ optional('log_driver') { str? }
31
+ optional('log_opts') { type?(Hash) }
32
+
33
+ end
34
+ end
35
+
36
+ ##
37
+ # @param [Hash] yaml
38
+ # @return [Array] validation_errors
39
+ def validate(yaml)
40
+ result = {
41
+ errors: [],
42
+ notifications: []
43
+ }
44
+
45
+ yaml.each do |service, options|
46
+ key_errors = validate_keys(options)
47
+ option_errors = validate_options(options)
48
+ result[:errors] << { service => option_errors.messages } if option_errors.failure?
49
+ result[:notifications] << { service => key_errors } if key_errors.size > 0
50
+ end
51
+ result
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ require 'dry-validation'
2
+ require_relative 'validator'
3
+
4
+ module Kontena::Cli::Apps
5
+ module YAML
6
+ class ValidatorV2
7
+ require_relative 'validations'
8
+ include Validations
9
+
10
+ VALID_KEYS = %w(
11
+ affinity build cap_add cap_drop command deploy depends_on env_file environment extends external_links
12
+ image links logging network_mode pid ports volumes volumes_from cpu_shares
13
+ mem_limit memswap_limit privileged stateful user instances hooks secrets
14
+ ).freeze
15
+
16
+ UNSUPPORTED_KEYS = %w(
17
+ cgroup_parent container_name devices dns dns_search tmpfs entrypoint
18
+ expose extra_hosts labels log_driver log_opt net networks security_opt stop_signal ulimits volume_driver
19
+ cpu_quota cpuset domainname hostname ipc mac_address
20
+ read_only restart shm_size stdin_open tty working_dir
21
+ ).freeze
22
+
23
+ ##
24
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
25
+ def initialize
26
+ base = self
27
+ @yaml_schema = Dry::Validation.Schema do
28
+ base.append_common_validations(self)
29
+ optional('build') { str? | type?(Hash) }
30
+
31
+ rule(build_hash: ['build']) do |build|
32
+ build.type?(Hash) > build.schema do
33
+ key('context').required
34
+ optional('dockerfile') { str? }
35
+ end
36
+ end
37
+ optional('depends_on') { array? }
38
+ optional('network_mode') { inclusion?(%w(host bridge)) }
39
+ optional('logging').schema do
40
+ optional('driver') { str? }
41
+ optional('options') { type?(Hash) }
42
+ end
43
+ end
44
+ end
45
+
46
+ ##
47
+ # @param [Hash] yaml
48
+ # @return [Array] validation_errors
49
+ def validate(yaml)
50
+ result = {
51
+ errors: [],
52
+ notifications: []
53
+ }
54
+ if yaml.key?('services')
55
+ yaml['services'].each do |service, options|
56
+ key_errors = validate_keys(options)
57
+ option_errors = validate_options(options)
58
+ result[:errors] << { service => option_errors.messages } if option_errors.failure?
59
+ result[:notifications] << { service => key_errors } if key_errors.size > 0
60
+ end
61
+ else
62
+ result[:errors] << { 'file' => 'services missing' }
63
+ end
64
+ if yaml.key?('volumes')
65
+ result[:notifications] << { 'volumes' => 'Kontena does not support volumes yet. To persist data just define service as stateful (stateful: true)' }
66
+ end
67
+ if yaml.key?('networks')
68
+ result[:notifications] << { 'networks' => 'Kontena does not support multiple networks yet. You can reference services with Kontena\'s internal DNS (service_name.kontena.local)' }
69
+ end
70
+ result
71
+ end
72
+ end
73
+ end
74
+ end
@@ -122,6 +122,29 @@ module Kontena
122
122
  save_settings
123
123
  end
124
124
 
125
+ def error(message = nil)
126
+ $stderr.puts(message) if message
127
+ exit(1)
128
+ end
129
+
130
+ def prompt(prefix = '> ')
131
+ require 'highline/import'
132
+ ask(prefix)
133
+ end
134
+
135
+ def confirm_command(name, message = nil)
136
+ puts message if message
137
+ puts "Destructive command. To proceed, type \"#{name}\" or re-run this command with --force option."
138
+
139
+ prompt == name || error("Confirmation did not match #{name}. Aborted command.")
140
+ end
141
+
142
+ def confirm(message = 'Destructive command. Are you sure? (y/n) or re-run this command with --force option.')
143
+ puts message
144
+
145
+ ['y', 'yes'].include?(prompt) || error("Aborted command.")
146
+ end
147
+
125
148
  def api_url=(api_url)
126
149
  settings['servers'][current_master_index]['url'] = api_url
127
150
  save_settings
@@ -9,11 +9,13 @@ module Kontena::Cli::Etcd
9
9
  parameter "KEY", "Etcd key"
10
10
 
11
11
  option "--recursive", :flag, "Remove keys recursively"
12
+ option "--force", :flag, "Force remove", default: false, attribute_name: :forced
12
13
 
13
14
  def execute
14
15
  require_api_url
15
16
  token = require_token
16
17
  validate_key
18
+ confirm unless forced?
17
19
 
18
20
  data = {}
19
21
  data[:recursive] = true if recursive?
@@ -6,10 +6,12 @@ module Kontena::Cli::Grids
6
6
  include Common
7
7
 
8
8
  parameter "NAME", "Grid name"
9
+ option "--force", :flag, "Force remove", default: false, attribute_name: :forced
9
10
 
10
11
  def execute
11
12
  require_api_url
12
13
  token = require_token
14
+ confirm_command(name) unless forced?
13
15
  grid = find_grid_by_name(name)
14
16
 
15
17
  if !grid.nil?
@@ -7,10 +7,13 @@ module Kontena::Cli::Grids::Users
7
7
  include Kontena::Cli::Grids::Common
8
8
 
9
9
  parameter "EMAIL", "Email address"
10
+ option "--force", :flag, "Force remove", default: false, attribute_name: :forced
10
11
 
11
12
  def execute
12
13
  require_api_url
13
14
  token = require_token
15
+ confirm_command(email) unless forced?
16
+
14
17
  result = client(token).delete("grids/#{current_grid}/users/#{email}")
15
18
  end
16
19
  end
@@ -10,7 +10,6 @@ module Kontena::Cli::Master::Azure
10
10
  option "--network", "NETWORK", "Virtual Network name"
11
11
  option "--subnet", "SUBNET", "Subnet name"
12
12
  option "--ssh-key", "SSH KEY", "SSH private key file", required: true
13
- option "--password", "PASSWORD", "Password"
14
13
  option "--location", "LOCATION", "Location", default: 'West Europe'
15
14
  option "--ssl-cert", "SSL CERT", "SSL certificate file"
16
15
  option "--vault-secret", "VAULT_SECRET", "Secret key for Vault"
@@ -22,7 +21,6 @@ module Kontena::Cli::Master::Azure
22
21
  require 'kontena/machine/azure'
23
22
  provisioner = Kontena::Machine::Azure::MasterProvisioner.new(subscription_id, certificate)
24
23
  provisioner.run!(
25
- password: password,
26
24
  ssh_key: ssh_key,
27
25
  ssl_cert: ssl_cert,
28
26
  size: size,
@@ -0,0 +1,42 @@
1
+ require 'securerandom'
2
+
3
+ module Kontena::Cli::Master::Packet
4
+ class CreateCommand < Clamp::Command
5
+ include Kontena::Cli::Common
6
+
7
+ option "--token", "TOKEN", "Packet API token", required: true
8
+ option "--project", "PROJECT ID", "Packet project id", required: true
9
+ option "--ssl-cert", "PATH", "SSL certificate file (optional)"
10
+ option "--type", "TYPE", "Server type (baremetal_0, baremetal_1, ..)", default: 'baremetal_0', attribute_name: :plan
11
+ option "--facility", "FACILITY CODE", "Facility", default: 'ams1'
12
+ option "--billing", "BILLING", "Billing cycle", default: 'hourly'
13
+ option "--ssh-key", "PATH", "Path to ssh public key (optional)"
14
+ option "--vault-secret", "VAULT_SECRET", "Secret key for Vault (optional)"
15
+ option "--vault-iv", "VAULT_IV", "Initialization vector for Vault (optional)"
16
+ option "--mongodb-uri", "URI", "External MongoDB uri (optional)"
17
+ option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
18
+ option "--auth-provider-url", "AUTH_PROVIDER_URL", "Define authentication provider url"
19
+
20
+ def execute
21
+
22
+ require 'kontena/machine/packet'
23
+
24
+ provisioner = Kontena::Machine::Packet::MasterProvisioner.new(token)
25
+ provisioner.run!(
26
+ project: project,
27
+ billing: billing,
28
+ ssh_key: ssh_key,
29
+ ssl_cert: ssl_cert,
30
+ plan: plan,
31
+ facility: facility,
32
+ version: version,
33
+ auth_server: auth_provider_url,
34
+ vault_secret: vault_secret || SecureRandom.hex(24),
35
+ vault_iv: vault_iv || SecureRandom.hex(24),
36
+ mongodb_uri: mongodb_uri
37
+ )
38
+ end
39
+
40
+ end
41
+ end
42
+
@@ -0,0 +1,14 @@
1
+
2
+ module Kontena::Cli::Master
3
+
4
+ require_relative 'packet/create_command'
5
+
6
+ class PacketCommand < Clamp::Command
7
+
8
+ subcommand "create", "Create a new Packet master", Packet::CreateCommand
9
+
10
+ def execute
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,39 @@
1
+ require 'securerandom'
2
+
3
+ module Kontena::Cli::Master::Upcloud
4
+ class CreateCommand < Clamp::Command
5
+ include Kontena::Cli::Common
6
+
7
+ option "--username", "USER", "Upcloud username", required: true
8
+ option "--password", "PASS", "Upcloud password", required: true
9
+ option "--ssh-key", "SSH_KEY", "Path to ssh public key", required: true
10
+ option "--ssl-cert", "SSL CERT", "SSL certificate file (optional)"
11
+ option "--plan", "PLAN", "Server plan", default: '1xCPU-1GB'
12
+ option "--zone", "ZONE", "Zone", default: 'fi-hel1'
13
+ option "--vault-secret", "VAULT_SECRET", "Secret key for Vault (optional)"
14
+ option "--vault-iv", "VAULT_IV", "Initialization vector for Vault (optional)"
15
+ option "--mongodb-uri", "URI", "External MongoDB uri (optional)"
16
+ option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
17
+ option "--auth-provider-url", "AUTH_PROVIDER_URL", "Define authentication provider url"
18
+
19
+
20
+ def execute
21
+
22
+ require 'kontena/machine/upcloud'
23
+
24
+ provisioner = Kontena::Machine::Upcloud::MasterProvisioner.new(username, password)
25
+ provisioner.run!(
26
+ ssh_key: ssh_key,
27
+ ssl_cert: ssl_cert,
28
+ plan: plan,
29
+ zone: zone,
30
+ version: version,
31
+ auth_server: auth_provider_url,
32
+ vault_secret: vault_secret || SecureRandom.hex(24),
33
+ vault_iv: vault_iv || SecureRandom.hex(24),
34
+ mongodb_uri: mongodb_uri
35
+ )
36
+ end
37
+
38
+ end
39
+ end