kuber_kit 0.5.1 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/Gemfile.lock +5 -5
  4. data/TODO.md +5 -5
  5. data/example/configurations/review.rb +1 -1
  6. data/example/services/docker_app.rb +2 -1
  7. data/example/services/env_file.rb +1 -1
  8. data/example/services/ruby_app.rb +2 -1
  9. data/lib/kuber_kit.rb +10 -0
  10. data/lib/kuber_kit/actions/kubectl_get.rb +32 -0
  11. data/lib/kuber_kit/actions/service_checker.rb +5 -0
  12. data/lib/kuber_kit/actions/service_deployer.rb +85 -61
  13. data/lib/kuber_kit/cli.rb +14 -3
  14. data/lib/kuber_kit/configs.rb +10 -6
  15. data/lib/kuber_kit/container.rb +20 -0
  16. data/lib/kuber_kit/core/artifacts/artifact_store.rb +3 -0
  17. data/lib/kuber_kit/core/configuration.rb +4 -2
  18. data/lib/kuber_kit/core/configuration_definition.rb +7 -0
  19. data/lib/kuber_kit/core/configuration_factory.rb +1 -0
  20. data/lib/kuber_kit/core/dependencies/abstract_dependency_resolver.rb +75 -0
  21. data/lib/kuber_kit/core/env_files/abstract_env_file.rb +4 -0
  22. data/lib/kuber_kit/core/env_files/artifact_file.rb +4 -0
  23. data/lib/kuber_kit/core/env_files/env_file_store.rb +3 -0
  24. data/lib/kuber_kit/core/env_files/env_group.rb +12 -0
  25. data/lib/kuber_kit/core/image.rb +2 -1
  26. data/lib/kuber_kit/core/registries/registry_store.rb +3 -0
  27. data/lib/kuber_kit/core/service.rb +4 -2
  28. data/lib/kuber_kit/core/service_definition.rb +13 -6
  29. data/lib/kuber_kit/core/service_factory.rb +1 -0
  30. data/lib/kuber_kit/core/templates/template_store.rb +3 -0
  31. data/lib/kuber_kit/env_file_reader/env_file_parser.rb +51 -0
  32. data/lib/kuber_kit/env_file_reader/env_file_tempfile_creator.rb +17 -0
  33. data/lib/kuber_kit/env_file_reader/reader.rb +2 -0
  34. data/lib/kuber_kit/env_file_reader/strategies/artifact_file.rb +9 -65
  35. data/lib/kuber_kit/env_file_reader/strategies/env_group.rb +21 -0
  36. data/lib/kuber_kit/image_compiler/image_dependency_resolver.rb +5 -53
  37. data/lib/kuber_kit/service_deployer/service_dependency_resolver.rb +14 -0
  38. data/lib/kuber_kit/service_deployer/service_list_resolver.rb +11 -6
  39. data/lib/kuber_kit/service_deployer/strategies/docker.rb +31 -12
  40. data/lib/kuber_kit/service_deployer/strategies/kubernetes.rb +9 -2
  41. data/lib/kuber_kit/shell/commands/kubectl_commands.rb +8 -0
  42. data/lib/kuber_kit/shell/local_shell.rb +15 -1
  43. data/lib/kuber_kit/template_reader/strategies/artifact_file.rb +3 -3
  44. data/lib/kuber_kit/tools/logger_factory.rb +14 -0
  45. data/lib/kuber_kit/version.rb +1 -1
  46. metadata +10 -2
@@ -0,0 +1,17 @@
1
+ class KuberKit::EnvFileReader::EnvFileTempfileCreator
2
+ include KuberKit::Import[
3
+ "env_file_reader.reader",
4
+ "configs"
5
+ ]
6
+
7
+ Contract KuberKit::Shell::AbstractShell, KuberKit::Core::EnvFiles::AbstractEnvFile => String
8
+ def call(shell, env_file)
9
+ env_file_hash = reader.read(shell, env_file)
10
+ env_file_raw = env_file_hash.to_a.map{|k,v| "#{k}=#{v}"}.join("\r\n")
11
+ temp_file_path = File.join(configs.env_file_compile_dir, env_file.uniq_name)
12
+
13
+ shell.write(temp_file_path, env_file_raw)
14
+
15
+ temp_file_path
16
+ end
17
+ end
@@ -3,6 +3,7 @@ class KuberKit::EnvFileReader::Reader
3
3
 
4
4
  include KuberKit::Import[
5
5
  "env_file_reader.strategies.artifact_file",
6
+ "env_file_reader.strategies.env_group",
6
7
  ]
7
8
 
8
9
  def initialize(**injected_deps)
@@ -35,5 +36,6 @@ class KuberKit::EnvFileReader::Reader
35
36
  private
36
37
  def add_default_strategies
37
38
  use_reader(artifact_file, env_file_class: KuberKit::Core::EnvFiles::ArtifactFile)
39
+ use_reader(env_group, env_file_class: KuberKit::Core::EnvFiles::EnvGroup)
38
40
  end
39
41
  end
@@ -1,8 +1,12 @@
1
1
  class KuberKit::EnvFileReader::Strategies::ArtifactFile < KuberKit::EnvFileReader::Strategies::Abstract
2
2
  include KuberKit::Import[
3
- "core.artifact_store"
3
+ "core.artifact_store",
4
+ "env_file_reader.env_file_parser",
5
+ "preprocessing.text_preprocessor"
4
6
  ]
5
7
 
8
+ PREPROCESS_EXTENSIONS = [".erb"]
9
+
6
10
  def read(shell, env_file)
7
11
  artifact = artifact_store.get(env_file.artifact_name)
8
12
 
@@ -16,71 +20,11 @@ class KuberKit::EnvFileReader::Strategies::ArtifactFile < KuberKit::EnvFileReade
16
20
  def read_file(shell, file_path)
17
21
  result = {}
18
22
  content = shell.read(file_path)
19
- Parser.call(content)
20
- end
21
-
22
- # Parser is based on:
23
- # https://github.com/bkeepers/dotenv/blob/master/lib/dotenv/parser.rb
24
- class Parser
25
- LINE = /
26
- (?:^|\A) # beginning of line
27
- \s* # leading whitespace
28
- (?:export\s+)? # optional export
29
- ([\w\.]+) # key
30
- (?:\s*=\s*?|:\s+?) # separator
31
- ( # optional value begin
32
- \s*'(?:\\'|[^'])*' # single quoted value
33
- | # or
34
- \s*"(?:\\"|[^"])*" # double quoted value
35
- | # or
36
- [^\#\r\n]+ # unquoted value
37
- )? # value end
38
- \s* # trailing whitespace
39
- (?:\#.*)? # optional comment
40
- (?:$|\z) # end of line
41
- /x
42
-
43
- class << self
44
- def call(string, is_load = false)
45
- new(string, is_load).call
23
+ enable_preprocessing = PREPROCESS_EXTENSIONS.any?{ |e| e == File.extname(file_path) }
24
+ if enable_preprocessing
25
+ content = text_preprocessor.compile(content)
46
26
  end
47
- end
48
27
 
49
- def initialize(string, is_load = false)
50
- @string = string
51
- @hash = {}
52
- @is_load = is_load
28
+ env_file_parser.call(content)
53
29
  end
54
-
55
- def call
56
- # Convert line breaks to same format
57
- lines = @string.gsub(/\r\n?/, "\n")
58
- # Process matches
59
- lines.scan(LINE).each do |key, value|
60
- @hash[key] = parse_value(value || "")
61
- end
62
- @hash
63
- end
64
-
65
- private
66
-
67
- def parse_value(value)
68
- # Remove surrounding quotes
69
- value = value.strip.sub(/\A(['"])(.*)\1\z/m, '\2')
70
-
71
- if Regexp.last_match(1) == '"'
72
- value = unescape_characters(expand_newlines(value))
73
- end
74
-
75
- value
76
- end
77
-
78
- def unescape_characters(value)
79
- value.gsub(/\\([^$])/, '\1')
80
- end
81
-
82
- def expand_newlines(value)
83
- value.gsub('\n', "\n").gsub('\r', "\r")
84
- end
85
- end
86
30
  end
@@ -0,0 +1,21 @@
1
+ class KuberKit::EnvFileReader::Strategies::EnvGroup < KuberKit::EnvFileReader::Strategies::Abstract
2
+ include KuberKit::Import[
3
+ "env_file_reader.strategies.artifact_file",
4
+ "core.env_file_store",
5
+ ]
6
+
7
+ def read(shell, env_group)
8
+ content = {}
9
+ env_group.env_files.each do |env_file_name|
10
+ env_file = env_file_store.get(env_file_name)
11
+
12
+ if env_file.is_a?(KuberKit::Core::EnvFiles::EnvGroup)
13
+ raise "EnvGroup inside another EnvGroup is not supported"
14
+ end
15
+
16
+ result = artifact_file.read(shell, env_file)
17
+ content = content.merge(result)
18
+ end
19
+ content
20
+ end
21
+ end
@@ -1,62 +1,14 @@
1
- class KuberKit::ImageCompiler::ImageDependencyResolver
2
- CircularDependencyError = Class.new(KuberKit::Error)
3
- DependencyNotFoundError = Class.new(KuberKit::NotFoundError)
4
-
1
+ class KuberKit::ImageCompiler::ImageDependencyResolver < KuberKit::Core::Dependencies::AbstractDependencyResolver
5
2
  include KuberKit::Import[
6
3
  "core.image_store",
7
4
  "configs"
8
5
  ]
9
-
10
- Contract Or[Symbol, ArrayOf[Symbol]], Proc => Any
11
- def each_with_deps(image_names, &block)
12
- compile_limit = configs.compile_simultaneous_limit
13
-
14
- resolved_dependencies = []
15
- next_dependencies = get_next(image_names, limit: compile_limit)
16
-
17
- while (next_dependencies - resolved_dependencies).any?
18
- block.call(next_dependencies)
19
- resolved_dependencies += next_dependencies
20
- next_dependencies = get_next(image_names, resolved: resolved_dependencies, limit: compile_limit)
21
- end
22
-
23
- (image_names - resolved_dependencies).each_slice(compile_limit) do |group|
24
- block.call(group)
25
- end
26
- end
27
6
 
28
- Contract Or[Symbol, ArrayOf[Symbol]], KeywordArgs[
29
- resolved: Optional[ArrayOf[Symbol]],
30
- limit: Optional[Maybe[Num]]
31
- ] => Any
32
- def get_next(image_names, resolved: [], limit: nil)
33
- deps = Array(image_names).map { |i| get_recursive_deps(i) }.flatten.uniq
34
-
35
- ready_to_resolve = deps.select do |dep_name|
36
- unresolved_deps = get_deps(dep_name) - resolved
37
- unresolved_deps.empty?
38
- end
39
- unresolved_deps = ready_to_resolve - resolved
40
- unresolved_deps = unresolved_deps.take(limit) if limit
41
- unresolved_deps
42
- end
43
-
44
- def get_recursive_deps(image_name, dependency_tree: [])
45
- deps = get_deps(image_name)
46
-
47
- if dependency_tree.include?(image_name)
48
- raise CircularDependencyError, "Circular dependency found for #{image_name}. Dependency tree: #{dependency_tree.inspect}"
49
- end
50
-
51
- child_deps = []
52
- deps.each do |i|
53
- child_deps += get_recursive_deps(i, dependency_tree: dependency_tree + [image_name])
54
- end
55
-
56
- (deps + child_deps).uniq
57
- end
58
-
59
7
  def get_deps(image_name)
60
8
  image_store.get_definition(image_name).dependencies
61
9
  end
10
+
11
+ def dependency_batch_size
12
+ configs.compile_simultaneous_limit
13
+ end
62
14
  end
@@ -0,0 +1,14 @@
1
+ class KuberKit::ServiceDeployer::ServiceDependencyResolver < KuberKit::Core::Dependencies::AbstractDependencyResolver
2
+ include KuberKit::Import[
3
+ "core.service_store",
4
+ "configs"
5
+ ]
6
+
7
+ def get_deps(service_name)
8
+ service_store.get_definition(service_name).dependencies
9
+ end
10
+
11
+ def dependency_batch_size
12
+ configs.deploy_simultaneous_limit
13
+ end
14
+ end
@@ -4,11 +4,12 @@ class KuberKit::ServiceDeployer::ServiceListResolver
4
4
  ]
5
5
 
6
6
  Contract KeywordArgs[
7
- services: Optional[ArrayOf[String]],
8
- tags: Optional[ArrayOf[String]],
9
- enabled_services: Optional[ArrayOf[String]]
7
+ services: Optional[ArrayOf[String]],
8
+ tags: Optional[ArrayOf[String]],
9
+ enabled_services: Optional[ArrayOf[String]],
10
+ disabled_services: Optional[ArrayOf[String]]
10
11
  ] => ArrayOf[String]
11
- def resolve(services: [], tags: [], enabled_services: [])
12
+ def resolve(services: [], tags: [], enabled_services: [], disabled_services: [])
12
13
  all_definitions = service_store.all_definitions.values
13
14
 
14
15
  included_services, excluded_services = split_by_inclusion(services)
@@ -36,14 +37,18 @@ class KuberKit::ServiceDeployer::ServiceListResolver
36
37
  included_services = included_services.select{ |s| enabled_services.include?(s) }
37
38
  end
38
39
 
40
+ if disabled_services.any?
41
+ included_services = included_services.select{ |s| !disabled_services.include?(s) }
42
+ end
43
+
39
44
  included_services
40
45
  end
41
46
 
42
47
  Contract Array => Array
43
48
  def split_by_inclusion(array)
44
- excluded, included = array.partition{|e| e.start_with?('-') }
49
+ excluded, included = array.partition{|e| e.start_with?('^') }
45
50
 
46
- excluded.map!{ |item| item.gsub(/^\-/, "") }
51
+ excluded.map!{ |item| item.gsub(/^\^/, "") }
47
52
 
48
53
  [included, excluded]
49
54
  end
@@ -1,6 +1,8 @@
1
1
  class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer::Strategies::Abstract
2
2
  include KuberKit::Import[
3
+ "env_file_reader.env_file_tempfile_creator",
3
4
  "shell.docker_commands",
5
+ "core.env_file_store",
4
6
  "core.image_store",
5
7
  "configs",
6
8
  ]
@@ -18,6 +20,7 @@ class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer:
18
20
  :networks,
19
21
  :expose,
20
22
  :publish,
23
+ :env_file_names
21
24
  ]
22
25
 
23
26
  Contract KuberKit::Shell::AbstractShell, KuberKit::Core::Service => Any
@@ -28,16 +31,19 @@ class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer:
28
31
  raise KuberKit::Error, "Unknow options for deploy strategy: #{unknown_options}. Available options: #{STRATEGY_OPTIONS}"
29
32
  end
30
33
 
31
- namespace = strategy_options.fetch(:namespace, nil)
32
- container_name = strategy_options.fetch(:container_name, [namespace, service.name].compact.join("_"))
33
- command_name = strategy_options.fetch(:command_name, nil)
34
- env_file = strategy_options.fetch(:env_file, nil)
35
- custom_args = strategy_options.fetch(:custom_args, nil)
36
- networks = strategy_options.fetch(:networks, [])
37
- volumes = strategy_options.fetch(:volumes, [])
38
- expose_ports = strategy_options.fetch(:expose, [])
39
- publish_ports = strategy_options.fetch(:publish, [])
40
- hostname = strategy_options.fetch(:hostname, container_name)
34
+ namespace = strategy_options.fetch(:namespace, nil)
35
+ container_name = strategy_options.fetch(:container_name, [namespace, service.name].compact.join("_"))
36
+ command_name = strategy_options.fetch(:command_name, nil)
37
+ custom_env_file = strategy_options.fetch(:env_file, nil)
38
+ custom_args = strategy_options.fetch(:custom_args, nil)
39
+ networks = strategy_options.fetch(:networks, [])
40
+ volumes = strategy_options.fetch(:volumes, [])
41
+ expose_ports = strategy_options.fetch(:expose, [])
42
+ publish_ports = strategy_options.fetch(:publish, [])
43
+ hostname = strategy_options.fetch(:hostname, container_name)
44
+
45
+ env_file_names = strategy_options.fetch(:env_file_names, [])
46
+ env_files = prepare_env_files(shell, env_file_names)
41
47
 
42
48
  image_name = strategy_options.fetch(:image_name, nil)
43
49
  if image_name.nil?
@@ -54,8 +60,8 @@ class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer:
54
60
  if container_name
55
61
  custom_args << "--name #{container_name}"
56
62
  end
57
- if env_file
58
- custom_args << "--env-file #{env_file}"
63
+ if custom_env_file
64
+ custom_args << "--env-file #{custom_env_file}"
59
65
  end
60
66
  if hostname
61
67
  custom_args << "--hostname #{hostname}"
@@ -75,6 +81,9 @@ class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer:
75
81
  Array(publish_ports).each do |publish_port|
76
82
  custom_args << "--publish #{publish_port}"
77
83
  end
84
+ Array(env_files).each do |env_file|
85
+ custom_args << "--env-file #{env_file}"
86
+ end
78
87
 
79
88
  docker_commands.run(
80
89
  shell, image.remote_registry_url,
@@ -84,4 +93,14 @@ class KuberKit::ServiceDeployer::Strategies::Docker < KuberKit::ServiceDeployer:
84
93
  interactive: !strategy_options[:detached]
85
94
  )
86
95
  end
96
+
97
+ private
98
+ def prepare_env_files(shell, env_file_names)
99
+ env_files = env_file_names.map do |env_file_name|
100
+ env_file_store.get(env_file_name)
101
+ end
102
+ env_files.map do |env_file|
103
+ env_file_tempfile_creator.call(shell, env_file)
104
+ end
105
+ end
87
106
  end
@@ -9,7 +9,8 @@ class KuberKit::ServiceDeployer::Strategies::Kubernetes < KuberKit::ServiceDeplo
9
9
  :resource_type,
10
10
  :resource_name,
11
11
  :delete_if_exists,
12
- :restart_if_exists
12
+ :restart_if_exists,
13
+ :wait_for_rollout
13
14
  ]
14
15
 
15
16
  Contract KuberKit::Shell::AbstractShell, KuberKit::Core::Service => Any
@@ -41,12 +42,18 @@ class KuberKit::ServiceDeployer::Strategies::Kubernetes < KuberKit::ServiceDeplo
41
42
 
42
43
  apply_result = kubectl_commands.apply_file(shell, config_path, kubeconfig_path: kubeconfig_path, namespace: namespace)
43
44
 
44
- restart_enabled = strategy_options.fetch(:restart_if_exists, true)
45
+ restart_enabled = strategy_options.fetch(:restart_if_exists, true)
46
+ wait_for_rollout = strategy_options.fetch(:wait_for_rollout, true)
45
47
  if restart_enabled && resource_exists
46
48
  kubectl_commands.rolling_restart(
47
49
  shell, resource_type, resource_name,
48
50
  kubeconfig_path: kubeconfig_path, namespace: namespace
49
51
  )
52
+
53
+ kubectl_commands.rollout_status(
54
+ shell, resource_type, resource_name, wait: true,
55
+ kubeconfig_path: kubeconfig_path, namespace: namespace
56
+ ) if wait_for_rollout
50
57
  end
51
58
 
52
59
  apply_result
@@ -102,4 +102,12 @@ class KuberKit::Shell::Commands::KubectlCommands
102
102
  }
103
103
  }, kubeconfig_path: kubeconfig_path, namespace: namespace)
104
104
  end
105
+
106
+ def rollout_status(shell, resource_type, resource_name, wait: true, kubeconfig_path: nil, namespace: nil)
107
+ command_parts = []
108
+ command_parts << %Q{rollout status #{resource_type} #{resource_name}}
109
+ command_parts << "-w" if wait
110
+
111
+ kubectl_run(shell, command_parts, kubeconfig_path: kubeconfig_path, namespace: namespace)
112
+ end
105
113
  end
@@ -1,6 +1,8 @@
1
1
  require 'fileutils'
2
2
 
3
3
  class KuberKit::Shell::LocalShell < KuberKit::Shell::AbstractShell
4
+ MAX_LINES_TO_PRINT = 50
5
+
4
6
  include KuberKit::Import[
5
7
  "shell.command_counter",
6
8
  "shell.rsync_commands",
@@ -20,7 +22,19 @@ class KuberKit::Shell::LocalShell < KuberKit::Shell::AbstractShell
20
22
  end
21
23
 
22
24
  if result && result != "" && log_command
23
- ui.print_debug("LocalShell", "Finished [#{command_number}] with result: \n ----\n#{result.grey}\n ----")
25
+ print_result = result
26
+ print_result_lines = print_result.split("\n")
27
+
28
+ if print_result_lines.count >= MAX_LINES_TO_PRINT
29
+ print_result = [
30
+ "[Result is too long, showing only first and last items]".yellow,
31
+ print_result_lines.first,
32
+ "[#{print_result_lines.count - 2} lines not showing]".yellow,
33
+ print_result_lines.last
34
+ ].join("\n")
35
+ end
36
+
37
+ ui.print_debug("LocalShell", "Finished [#{command_number}] with result: \n ----\n#{print_result.grey}\n ----")
24
38
  end
25
39
 
26
40
  if $?.exitstatus != 0
@@ -3,10 +3,10 @@ class KuberKit::TemplateReader::Strategies::ArtifactFile < KuberKit::TemplateRea
3
3
  "core.artifact_store"
4
4
  ]
5
5
 
6
- def read(shell, env_file)
7
- artifact = artifact_store.get(env_file.artifact_name)
6
+ def read(shell, template)
7
+ artifact = artifact_store.get(template.artifact_name)
8
8
 
9
- file_parts = [artifact.cloned_path, env_file.file_path].compact
9
+ file_parts = [artifact.cloned_path, template.file_path].compact
10
10
  file_path = File.join(*file_parts)
11
11
 
12
12
  shell.read(file_path)
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'fileutils'
2
3
 
3
4
  class KuberKit::Tools::LoggerFactory
4
5
  SEVERITY_COLORS_BY_LEVEL = {
@@ -14,6 +15,10 @@ class KuberKit::Tools::LoggerFactory
14
15
  ]
15
16
 
16
17
  def create(stdout = nil, level = nil)
18
+ if !stdout
19
+ prepare_log_file(configs.log_file_path)
20
+ end
21
+
17
22
  logger = Logger.new(stdout || configs.log_file_path)
18
23
 
19
24
  logger.level = level || Logger::DEBUG
@@ -35,4 +40,13 @@ class KuberKit::Tools::LoggerFactory
35
40
 
36
41
  logger
37
42
  end
43
+
44
+ private
45
+ def prepare_log_file(file_path)
46
+ dir_path = File.dirname(file_path)
47
+ unless Dir.exists?(dir_path)
48
+ FileUtils.mkdir_p(dir_path)
49
+ end
50
+ FileUtils.touch(file_path)
51
+ end
38
52
  end