kuber_kit 0.5.1 → 0.5.6

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 (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