kontena-plugin-app-command 0.1.0.rc1

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/Dockerfile +19 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +191 -0
  8. data/README.md +299 -0
  9. data/Rakefile +6 -0
  10. data/kontena-plugin-app-command.gemspec +28 -0
  11. data/lib/kontena/cli/apps/build_command.rb +28 -0
  12. data/lib/kontena/cli/apps/common.rb +172 -0
  13. data/lib/kontena/cli/apps/config_command.rb +25 -0
  14. data/lib/kontena/cli/apps/deploy_command.rb +137 -0
  15. data/lib/kontena/cli/apps/docker_compose_generator.rb +61 -0
  16. data/lib/kontena/cli/apps/docker_helper.rb +80 -0
  17. data/lib/kontena/cli/apps/dockerfile_generator.rb +16 -0
  18. data/lib/kontena/cli/apps/init_command.rb +89 -0
  19. data/lib/kontena/cli/apps/kontena_yml_generator.rb +105 -0
  20. data/lib/kontena/cli/apps/list_command.rb +59 -0
  21. data/lib/kontena/cli/apps/logs_command.rb +37 -0
  22. data/lib/kontena/cli/apps/monitor_command.rb +93 -0
  23. data/lib/kontena/cli/apps/remove_command.rb +74 -0
  24. data/lib/kontena/cli/apps/restart_command.rb +39 -0
  25. data/lib/kontena/cli/apps/scale_command.rb +33 -0
  26. data/lib/kontena/cli/apps/service_generator.rb +114 -0
  27. data/lib/kontena/cli/apps/service_generator_v2.rb +27 -0
  28. data/lib/kontena/cli/apps/show_command.rb +23 -0
  29. data/lib/kontena/cli/apps/start_command.rb +40 -0
  30. data/lib/kontena/cli/apps/stop_command.rb +40 -0
  31. data/lib/kontena/cli/apps/yaml/custom_validators/affinities_validator.rb +19 -0
  32. data/lib/kontena/cli/apps/yaml/custom_validators/build_validator.rb +22 -0
  33. data/lib/kontena/cli/apps/yaml/custom_validators/extends_validator.rb +20 -0
  34. data/lib/kontena/cli/apps/yaml/custom_validators/hooks_validator.rb +54 -0
  35. data/lib/kontena/cli/apps/yaml/custom_validators/secrets_validator.rb +22 -0
  36. data/lib/kontena/cli/apps/yaml/reader.rb +213 -0
  37. data/lib/kontena/cli/apps/yaml/service_extender.rb +77 -0
  38. data/lib/kontena/cli/apps/yaml/validations.rb +71 -0
  39. data/lib/kontena/cli/apps/yaml/validator.rb +38 -0
  40. data/lib/kontena/cli/apps/yaml/validator_v2.rb +53 -0
  41. data/lib/kontena/plugin/app-command/app_command.rb +21 -0
  42. data/lib/kontena/plugin/app-command/version.rb +7 -0
  43. data/lib/kontena_cli_plugin.rb +4 -0
  44. metadata +143 -0
@@ -0,0 +1,74 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class RemoveCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
10
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
11
+ option '--force', :flag, 'Force remove', default: false, attribute_name: :forced
12
+
13
+ parameter "[SERVICE] ...", "Remove services"
14
+
15
+ attr_reader :services
16
+
17
+ def execute
18
+ require_api_url
19
+ require_token
20
+ require_config_file(filename)
21
+ confirm unless forced?
22
+
23
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
24
+ if services.size > 0
25
+ remove_services(services)
26
+ elsif !service_list.empty?
27
+ puts "No such service: #{service_list.join(', ')}".colorize(:red)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def remove_services(services)
34
+ services.find_all {|service_name, options| options['links'] && options['links'].size > 0 }.each do |service_name, options|
35
+ delete(service_name, options, false)
36
+ services.delete(service_name)
37
+ end
38
+ services.each do |service_name, options|
39
+ delete(service_name, options)
40
+ end
41
+ end
42
+
43
+ def delete(name, options, async = true)
44
+ unless deleted_services.include?(name)
45
+ service = get_service(token, prefixed_name(name)) rescue nil
46
+ if(service)
47
+ spinner "removing #{pastel.cyan(name)}" do
48
+ delete_service(token, prefixed_name(name))
49
+ unless async
50
+ wait_for_delete_to_finish(service)
51
+ end
52
+ end
53
+ deleted_services << name
54
+ else
55
+ warning "No such service #{name}"
56
+ end
57
+ end
58
+ end
59
+
60
+ def wait_for_delete_to_finish(service)
61
+ until service.nil?
62
+ service = get_service(token, service['name']) rescue nil
63
+ sleep 0.5
64
+ end
65
+ end
66
+
67
+ ##
68
+ #
69
+ # @return [Array]
70
+ def deleted_services
71
+ @deleted_services ||= []
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class RestartCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Common
7
+
8
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
9
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
10
+
11
+ parameter "[SERVICE] ...", "Services to start"
12
+
13
+ attr_reader :services
14
+
15
+ def execute
16
+ require_config_file(filename)
17
+
18
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
19
+ if services.size > 0
20
+ restart_services(services)
21
+ elsif !service_list.empty?
22
+ puts "No such service: #{service_list.join(', ')}".colorize(:red)
23
+ end
24
+
25
+ end
26
+
27
+ def restart_services(services)
28
+ services.each do |service_name, opts|
29
+ if service_exists?(service_name)
30
+ spinner "Sending restart signal to #{service_name.colorize(:cyan)} " do
31
+ restart_service(token, prefixed_name(service_name))
32
+ end
33
+ else
34
+ warning "No such service: #{service_name}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class ScaleCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
10
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
11
+
12
+ parameter "SERVICE", "Service to show"
13
+ parameter "INSTANCES", "Scales service to given number of instances"
14
+
15
+ attr_reader :services
16
+
17
+ def execute
18
+ require_config_file(filename)
19
+ yml_service = services_from_yaml(filename, [service], service_prefix, true)
20
+ if yml_service[service]
21
+ options = yml_service[service]
22
+ exit_with_error("Service has already instances defined in #{filename}. Please update #{filename} and deploy service instead") if options['instances']
23
+ spinner "Scaling #{service.colorize(:cyan)} " do
24
+ deployment = scale_service(require_token, prefixed_name(service), instances)
25
+ wait_for_deploy_to_finish(token, deployment)
26
+ end
27
+
28
+ else
29
+ exit_with_error("Service not found")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,114 @@
1
+ require 'yaml'
2
+ require 'shellwords'
3
+ require 'kontena/cli/services/services_helper'
4
+
5
+ module Kontena::Cli::Apps
6
+ class ServiceGenerator
7
+ include Kontena::Cli::Services::ServicesHelper
8
+
9
+ attr_reader :service_config
10
+
11
+ def initialize(service_config)
12
+ @service_config = service_config
13
+ end
14
+
15
+ ##
16
+ # @return [Hash]
17
+ def generate
18
+ parse_data(service_config)
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # @param [Hash] options
25
+ # @return [Hash]
26
+ def parse_data(options)
27
+ data = {}
28
+ data['instances'] = options['instances']
29
+ data['image'] = parse_image(options['image'])
30
+ data['env'] = options['environment'] if options['environment']
31
+ data['links'] = parse_links(options['links'] || [])
32
+ data['external_links'] = parse_links(options['external_links'] || [])
33
+ data['ports'] = parse_stringified_ports(options['ports'] || [])
34
+ data['memory'] = parse_memory(options['mem_limit'].to_s) if options['mem_limit']
35
+ data['memory_swap'] = parse_memory(options['memswap_limit'].to_s) if options['memswap_limit']
36
+ data['cpu_shares'] = options['cpu_shares'] if options['cpu_shares']
37
+ data['volumes'] = options['volumes'] || []
38
+ data['volumes_from'] = options['volumes_from'] || []
39
+ data['cmd'] = Shellwords.split(options['command']) if options['command']
40
+ data['affinity'] = options['affinity'] || []
41
+ data['user'] = options['user'] if options['user']
42
+ data['stateful'] = options['stateful'] == true
43
+ data['privileged'] = options['privileged'] unless options['privileged'].nil?
44
+ data['cap_add'] = options['cap_add'] if options['cap_add']
45
+ data['cap_drop'] = options['cap_drop'] if options['cap_drop']
46
+ data['net'] = options['net'] if options['net']
47
+ data['pid'] = options['pid'] if options['pid']
48
+ data['log_driver'] = options['log_driver'] if options['log_driver']
49
+ data['log_opts'] = options['log_opt'] if options['log_opt'] && !options['log_opt'].empty?
50
+ deploy_opts = options['deploy'] || {}
51
+ data['strategy'] = deploy_opts['strategy'] if deploy_opts['strategy']
52
+ deploy = {}
53
+ deploy['wait_for_port'] = deploy_opts['wait_for_port'] if deploy_opts.has_key?('wait_for_port')
54
+ deploy['min_health'] = deploy_opts['min_health'] if deploy_opts.has_key?('min_health')
55
+ deploy['interval'] = parse_relative_time(deploy_opts['interval']) if deploy_opts.has_key?('interval')
56
+ unless deploy.empty?
57
+ data['deploy_opts'] = deploy
58
+ end
59
+ data['hooks'] = options['hooks'] || {}
60
+ data['secrets'] = options['secrets'] if options['secrets']
61
+ data['build'] = parse_build_options(options) if options['build']
62
+ health_check = {}
63
+ health_opts = options['health_check'] || {}
64
+ health_check['protocol'] = health_opts['protocol'] if health_opts.has_key?('protocol')
65
+ health_check['uri'] = health_opts['uri'] if health_opts.has_key?('uri')
66
+ health_check['port'] = health_opts['port'] if health_opts.has_key?('port')
67
+ health_check['timeout'] = health_opts['timeout'] if health_opts.has_key?('timeout')
68
+ health_check['interval'] = health_opts['interval'] if health_opts.has_key?('interval')
69
+ health_check['initial_delay'] = health_opts['initial_delay'] if health_opts.has_key?('initial_delay')
70
+ unless health_check.empty?
71
+ data['health_check'] = health_check
72
+ end
73
+ data
74
+ end
75
+
76
+ # @param [Array<String>] port_options
77
+ # @return [Array<Hash>]
78
+ def parse_stringified_ports(port_options)
79
+ parse_ports(port_options).map {|p|
80
+ {
81
+ 'ip' => p[:ip],
82
+ 'container_port' => p[:container_port],
83
+ 'node_port' => p[:node_port],
84
+ 'protocol' => p[:protocol]
85
+ }
86
+ }
87
+ end
88
+
89
+ # @param [Array<String>] link_options
90
+ # @return [Array<Hash>]
91
+ def parse_links(link_options)
92
+ link_options.map{|l|
93
+ service_name, alias_name = l.split(':')
94
+ if service_name.nil?
95
+ raise ArgumentError.new("Invalid link value #{l}")
96
+ end
97
+ alias_name = service_name if alias_name.nil?
98
+ {
99
+ 'name' => service_name,
100
+ 'alias' => alias_name
101
+ }
102
+ }
103
+ end
104
+
105
+ # @param [Hash] options
106
+ # @return [Hash]
107
+ def parse_build_options(options)
108
+ build = {}
109
+ build['context'] = options['build'] if options['build']
110
+ build['dockerfile'] = options['dockerfile'] if options['dockerfile']
111
+ build
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+ require_relative 'service_generator'
3
+
4
+ module Kontena::Cli::Apps
5
+ class ServiceGeneratorV2 < ServiceGenerator
6
+
7
+ def parse_data(options)
8
+ data = super(options)
9
+ data['net'] = options['network_mode'] if options['network_mode']
10
+ data['log_driver'] = options.dig('logging', 'driver')
11
+ data['log_opts'] = options.dig('logging', 'options')
12
+ if options['depends_on']
13
+ data['links'] ||= []
14
+ data['links'] = (data['links'] + parse_links(options['depends_on'])).uniq
15
+ end
16
+ data
17
+ end
18
+
19
+ def parse_build_options(options)
20
+ unless options['build'].is_a?(Hash)
21
+ options['build'] = { 'context' => options['build']}
22
+ end
23
+ options['build']['args'] = parse_build_args(options['build']['args']) if options['build']['args']
24
+ options['build']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class ShowCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
10
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
11
+
12
+ parameter "SERVICE", "Service to show"
13
+
14
+ attr_reader :services
15
+
16
+ def execute
17
+ require_config_file(filename)
18
+ token = require_token
19
+ show_service(token, prefixed_name(service))
20
+ show_service_instances(token, prefixed_name(service))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class StartCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
10
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
11
+
12
+ parameter "[SERVICE] ...", "Services to start"
13
+
14
+ attr_reader :services
15
+
16
+ def execute
17
+ require_config_file(filename)
18
+
19
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
20
+ if services.size > 0
21
+ start_services(services)
22
+ elsif !service_list.empty?
23
+ puts "No such service: #{service_list.join(', ')}".colorize(:red)
24
+ end
25
+
26
+ end
27
+
28
+ def start_services(services)
29
+ services.each do |service_name, opts|
30
+ if service_exists?(service_name)
31
+ spinner "Starting #{service_name.colorize(:cyan)} " do
32
+ start_service(token, prefixed_name(service_name))
33
+ end
34
+ else
35
+ warning "No such service: #{service_name}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Apps
4
+ class StopCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
10
+ option ['-p', '--project-name'], 'NAME', 'Specify an alternate project name (default: directory name)'
11
+
12
+ parameter "[SERVICE] ...", "Services to stop"
13
+
14
+ attr_reader :services
15
+
16
+ def execute
17
+ require_config_file(filename)
18
+
19
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
20
+ if services.size > 0
21
+ stop_services(services)
22
+ elsif !service_list.empty?
23
+ puts "No such service: #{service_list.join(', ')}".colorize(:red)
24
+ end
25
+
26
+ end
27
+
28
+ def stop_services(services)
29
+ services.each do |service_name, opts|
30
+ if service_exists?(service_name)
31
+ spinner "Sending stop signal to #{service_name.colorize(:cyan)} " do
32
+ stop_service(token, prefixed_name(service_name))
33
+ end
34
+ else
35
+ warning "No such service: #{service_name}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class AffinitiesValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_affinities')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(Array)
9
+ errors[key] = 'affinity must be array'
10
+ return
11
+ end
12
+
13
+ invalid_formats = value.find_all { |a| !a.match(/(?<=\!|\=)=/) }
14
+ if invalid_formats.count > 0
15
+ errors[key] = "affinity contains invalid formats: #{invalid_formats.join(', ')}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class BuildValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_build')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(String) || value.is_a?(Hash)
9
+ errors[key] = 'build must be string or hash'
10
+ return
11
+ end
12
+ if value.is_a?(Hash)
13
+ build_validation = {
14
+ 'context' => 'string',
15
+ 'dockerfile' => HashValidator.optional('string'),
16
+ 'args' => HashValidator.optional(-> (value) { value.is_a?(Array) || value.is_a?(Hash) })
17
+ }
18
+ HashValidator.validator_for(build_validation).validate(key, value, build_validation, errors)
19
+ end
20
+ end
21
+ end
22
+ end