kuber_kit 0.1.5 → 0.2.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -2
  3. data/README.md +16 -3
  4. data/TODO.md +5 -4
  5. data/example/app_data/env_file.yml +10 -0
  6. data/example/configurations/review.rb +2 -1
  7. data/example/images/app_sources/Dockerfile +1 -1
  8. data/example/images/ruby_app/image.rb +4 -4
  9. data/example/infrastructure/build_servers.rb +8 -0
  10. data/example/infrastructure/templates.rb +5 -0
  11. data/example/services/env_file.rb +6 -0
  12. data/kuber_kit.gemspec +1 -0
  13. data/lib/kuber_kit.rb +51 -23
  14. data/lib/kuber_kit/actions/configuration_loader.rb +11 -2
  15. data/lib/kuber_kit/actions/env_file_reader.rb +8 -5
  16. data/lib/kuber_kit/actions/image_compiler.rb +18 -22
  17. data/lib/kuber_kit/actions/kubectl_applier.rb +6 -1
  18. data/lib/kuber_kit/actions/kubectl_attacher.rb +19 -0
  19. data/lib/kuber_kit/actions/service_deployer.rb +13 -3
  20. data/lib/kuber_kit/actions/service_reader.rb +9 -6
  21. data/lib/kuber_kit/actions/template_reader.rb +5 -0
  22. data/lib/kuber_kit/cli.rb +51 -20
  23. data/lib/kuber_kit/configs.rb +24 -22
  24. data/lib/kuber_kit/container.rb +43 -19
  25. data/lib/kuber_kit/core/artifacts/artifact_store.rb +12 -23
  26. data/lib/kuber_kit/core/build_servers/abstract_build_server.rb +21 -0
  27. data/lib/kuber_kit/core/build_servers/build_server.rb +24 -0
  28. data/lib/kuber_kit/core/build_servers/build_server_store.rb +18 -0
  29. data/lib/kuber_kit/core/configuration.rb +14 -4
  30. data/lib/kuber_kit/core/configuration_definition.rb +22 -1
  31. data/lib/kuber_kit/core/configuration_factory.rb +11 -1
  32. data/lib/kuber_kit/core/configuration_store.rb +14 -24
  33. data/lib/kuber_kit/core/context_helper/base_helper.rb +12 -7
  34. data/lib/kuber_kit/core/context_helper/context_helper_factory.rb +11 -8
  35. data/lib/kuber_kit/core/context_helper/service_helper.rb +7 -6
  36. data/lib/kuber_kit/core/env_files/env_file_store.rb +8 -23
  37. data/lib/kuber_kit/core/image_store.rb +8 -18
  38. data/lib/kuber_kit/core/registries/registry_store.rb +8 -23
  39. data/lib/kuber_kit/core/service.rb +6 -2
  40. data/lib/kuber_kit/core/service_factory.rb +4 -1
  41. data/lib/kuber_kit/core/service_store.rb +13 -23
  42. data/lib/kuber_kit/core/store.rb +48 -0
  43. data/lib/kuber_kit/core/templates/template_store.rb +12 -23
  44. data/lib/kuber_kit/env_file_reader/action_handler.rb +12 -0
  45. data/lib/kuber_kit/env_file_reader/reader.rb +4 -4
  46. data/lib/kuber_kit/env_file_reader/{abstract_env_file_reader.rb → strategies/abstract.rb} +1 -1
  47. data/lib/kuber_kit/env_file_reader/{artifact_file_reader.rb → strategies/artifact_file.rb} +1 -1
  48. data/lib/kuber_kit/image_compiler/action_handler.rb +21 -0
  49. data/lib/kuber_kit/image_compiler/build_server_pool.rb +30 -0
  50. data/lib/kuber_kit/image_compiler/build_server_pool_factory.rb +13 -0
  51. data/lib/kuber_kit/image_compiler/compiler.rb +2 -5
  52. data/lib/kuber_kit/image_compiler/image_build_dir_creator.rb +13 -7
  53. data/lib/kuber_kit/image_compiler/image_dependency_resolver.rb +25 -5
  54. data/lib/kuber_kit/preprocessing/file_preprocessor.rb +5 -4
  55. data/lib/kuber_kit/preprocessing/text_preprocessor.rb +1 -1
  56. data/lib/kuber_kit/service_deployer/action_handler.rb +16 -0
  57. data/lib/kuber_kit/service_deployer/deployer.rb +28 -6
  58. data/lib/kuber_kit/service_deployer/strategies/abstract.rb +1 -1
  59. data/lib/kuber_kit/service_deployer/strategies/kubernetes.rb +9 -4
  60. data/lib/kuber_kit/service_deployer/strategy_detector.rb +6 -0
  61. data/lib/kuber_kit/service_reader/action_handler.rb +13 -0
  62. data/lib/kuber_kit/{service_deployer/service_reader.rb → service_reader/reader.rb} +1 -1
  63. data/lib/kuber_kit/shell/abstract_shell.rb +4 -0
  64. data/lib/kuber_kit/shell/{bash_commands.rb → commands/bash_commands.rb} +1 -1
  65. data/lib/kuber_kit/shell/{docker_commands.rb → commands/docker_commands.rb} +1 -1
  66. data/lib/kuber_kit/shell/{git_commands.rb → commands/git_commands.rb} +1 -1
  67. data/lib/kuber_kit/shell/{kubectl_commands.rb → commands/kubectl_commands.rb} +25 -1
  68. data/lib/kuber_kit/shell/commands/rsync_commands.rb +32 -0
  69. data/lib/kuber_kit/shell/local_shell.rb +24 -5
  70. data/lib/kuber_kit/shell/ssh_session.rb +60 -0
  71. data/lib/kuber_kit/shell/ssh_shell.rb +77 -0
  72. data/lib/kuber_kit/tools/file_presence_checker.rb +6 -2
  73. data/lib/kuber_kit/ui/interactive.rb +8 -0
  74. data/lib/kuber_kit/ui/simple.rb +6 -0
  75. data/lib/kuber_kit/version.rb +2 -2
  76. metadata +41 -13
  77. data/lib/kuber_kit/preprocessing/dir_preprocessor.rb +0 -19
  78. data/lib/kuber_kit/service_deployer/service_restarter.rb +0 -37
  79. data/lib/kuber_kit/shell/rsync_commands.rb +0 -20
  80. data/lib/kuber_kit/tools/files_sync.rb +0 -10
@@ -1,6 +1,7 @@
1
1
  class KuberKit::Preprocessing::FilePreprocessor
2
2
  include KuberKit::Import[
3
- "preprocessing.text_preprocessor"
3
+ "preprocessing.text_preprocessor",
4
+ "shell.bash_commands"
4
5
  ]
5
6
 
6
7
  PreprocessingError = Class.new(KuberKit::Error)
@@ -8,7 +9,7 @@ class KuberKit::Preprocessing::FilePreprocessor
8
9
  def compile(shell, source_path, destination_path: nil, context_helper: nil)
9
10
  destination_path ||= source_path
10
11
 
11
- prepare_destination_dir(destination_path)
12
+ prepare_destination_dir(shell, destination_path)
12
13
 
13
14
  template = shell.read(source_path)
14
15
  content = text_preprocessor.compile(template, context_helper: context_helper)
@@ -27,7 +28,7 @@ class KuberKit::Preprocessing::FilePreprocessor
27
28
  raise PreprocessingError, "Error while processing template #{source_path}.\r\n#{message}"
28
29
  end
29
30
 
30
- def prepare_destination_dir(destination_path)
31
- FileUtils.mkdir_p(File.dirname(destination_path))
31
+ def prepare_destination_dir(shell, destination_path)
32
+ bash_commands.mkdir_p(shell, File.dirname(destination_path))
32
33
  end
33
34
  end
@@ -2,6 +2,6 @@ require 'erb'
2
2
 
3
3
  class KuberKit::Preprocessing::TextPreprocessor
4
4
  def compile(template, context_helper: nil)
5
- ERB.new(template).result(context_helper&.get_binding)
5
+ ERB.new(template, nil, '-').result(context_helper&.get_binding)
6
6
  end
7
7
  end
@@ -0,0 +1,16 @@
1
+ class KuberKit::ServiceDeployer::ActionHandler
2
+ include KuberKit::Import[
3
+ "service_deployer.deployer",
4
+ "service_deployer.strategy_detector",
5
+ "core.service_store",
6
+ ]
7
+
8
+ Contract KuberKit::Shell::AbstractShell, Symbol => Any
9
+ def call(shell, service_name)
10
+ service = service_store.get_service(service_name)
11
+
12
+ strategy_name = strategy_detector.call(service)
13
+
14
+ deployer.deploy(shell, service, strategy_name)
15
+ end
16
+ end
@@ -1,15 +1,37 @@
1
1
  class KuberKit::ServiceDeployer::Deployer
2
+ StrategyNotFoundError = Class.new(KuberKit::NotFoundError)
3
+
2
4
  include KuberKit::Import[
3
- "service_deployer.service_restarter",
4
5
  "core.service_store",
6
+ "service_deployer.strategies.kubernetes"
5
7
  ]
6
8
 
7
- Contract KuberKit::Shell::AbstractShell, Symbol => Any
8
- def deploy(shell, service_name)
9
- service = service_store.get_service(service_name)
9
+ def register_strategy(strategy_name, strategy)
10
+ @@strategies ||= {}
11
+
12
+ if !strategy.is_a?(KuberKit::ServiceDeployer::Strategies::Abstract)
13
+ raise ArgumentError.new("should be an instance of KuberKit::ServiceDeployer::Strategies::Abstract, got: #{strategy.inspect}")
14
+ end
15
+
16
+ @@strategies[strategy_name] = strategy
17
+ end
18
+
19
+ Contract KuberKit::Shell::AbstractShell, KuberKit::Core::Service, Symbol => Any
20
+ def deploy(shell, service, strategy_name)
21
+ add_default_strategies
10
22
 
11
- strategy_name = KuberKit.current_configuration.deploy_strategy
23
+ deployer = @@strategies[strategy_name]
24
+
25
+ raise StrategyNotFoundError, "Can't find strategy with name #{strategy_name}" if deployer.nil?
26
+
27
+ deployer.deploy(shell, service)
28
+ end
29
+
30
+ def add_default_strategies
31
+ register_strategy(:kubernetes, kubernetes)
32
+ end
12
33
 
13
- service_restarter.restart(shell, service, strategy_name)
34
+ def reset!
35
+ @@strategies = {}
14
36
  end
15
37
  end
@@ -1,5 +1,5 @@
1
1
  class KuberKit::ServiceDeployer::Strategies::Abstract
2
- def restart(shell, service)
2
+ def deploy(shell, service)
3
3
  raise KuberKit::NotImplementedError, "must be implemented"
4
4
  end
5
5
  end
@@ -1,18 +1,23 @@
1
1
  class KuberKit::ServiceDeployer::Strategies::Kubernetes < KuberKit::ServiceDeployer::Strategies::Abstract
2
2
  include KuberKit::Import[
3
- "service_deployer.service_reader",
3
+ "service_reader.reader",
4
4
  "shell.kubectl_commands",
5
5
  "configs",
6
6
  ]
7
7
 
8
8
  Contract KuberKit::Shell::AbstractShell, KuberKit::Core::Service => Any
9
- def restart(shell, service)
10
- service_config = service_reader.read(shell, service)
9
+ def deploy(shell, service)
10
+ service_config = reader.read(shell, service)
11
11
  config_path = "#{configs.service_config_dir}/#{service.name}.yml"
12
12
  shell.write(config_path, service_config)
13
13
 
14
14
  kubeconfig_path = KuberKit.current_configuration.kubeconfig_path
15
15
  kubectl_commands.apply_file(shell, config_path, kubeconfig_path: kubeconfig_path)
16
- kubectl_commands.rolling_restart(shell, service.uri, kubeconfig_path: kubeconfig_path)
16
+
17
+ deployment_restart_enabled = service.attribute(:deployment_restart_enabled, default: true)
18
+ deployment_restart_name = service.attribute(:deployment_restart_name, default: service.uri)
19
+ if deployment_restart_enabled
20
+ kubectl_commands.rolling_restart(shell, deployment_restart_name, kubeconfig_path: kubeconfig_path)
21
+ end
17
22
  end
18
23
  end
@@ -0,0 +1,6 @@
1
+ class KuberKit::ServiceDeployer::StrategyDetector
2
+ Contract KuberKit::Core::Service => Symbol
3
+ def call(service)
4
+ KuberKit.current_configuration.deploy_strategy
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ class KuberKit::ServiceReader::ActionHandler
2
+ include KuberKit::Import[
3
+ "service_reader.reader",
4
+ "core.service_store",
5
+ ]
6
+
7
+ Contract KuberKit::Shell::AbstractShell, Symbol => Any
8
+ def call(shell, service_name)
9
+ service = service_store.get_service(service_name)
10
+
11
+ reader.read(shell, service)
12
+ end
13
+ end
@@ -1,4 +1,4 @@
1
- class KuberKit::ServiceDeployer::ServiceReader
1
+ class KuberKit::ServiceReader::Reader
2
2
  include KuberKit::Import[
3
3
  "core.template_store",
4
4
  "core.context_helper_factory",
@@ -17,4 +17,8 @@ class KuberKit::Shell::AbstractShell
17
17
  def recursive_list_files(path, name: nil)
18
18
  raise KuberKit::NotImplementedError, "must be implemented"
19
19
  end
20
+
21
+ def sync(local_path, remote_path, exclude: nil)
22
+ raise KuberKit::NotImplementedError, "must be implemented"
23
+ end
20
24
  end
@@ -1,4 +1,4 @@
1
- class KuberKit::Shell::BashCommands
1
+ class KuberKit::Shell::Commands::BashCommands
2
2
  def rm(shell, path)
3
3
  shell.exec!(%Q{rm "#{path}"})
4
4
  end
@@ -1,4 +1,4 @@
1
- class KuberKit::Shell::DockerCommands
1
+ class KuberKit::Shell::Commands::DockerCommands
2
2
  def build(shell, build_dir, args = [])
3
3
  default_args = ["--rm=true"]
4
4
  args_list = (default_args + args).join(" ")
@@ -1,4 +1,4 @@
1
- class KuberKit::Shell::GitCommands
1
+ class KuberKit::Shell::Commands::GitCommands
2
2
  def get_remote_url(shell, git_repo_path, remote_name: "origin")
3
3
  shell.exec! [
4
4
  "cd #{git_repo_path}",
@@ -1,7 +1,7 @@
1
1
  require 'json'
2
2
  require 'shellwords'
3
3
 
4
- class KuberKit::Shell::KubectlCommands
4
+ class KuberKit::Shell::Commands::KubectlCommands
5
5
  def apply_file(shell, file_path, kubeconfig_path: nil)
6
6
  command_parts = []
7
7
  if kubeconfig_path
@@ -13,6 +13,30 @@ class KuberKit::Shell::KubectlCommands
13
13
  shell.exec!(command_parts.join(" "))
14
14
  end
15
15
 
16
+ def exec(shell, pod_name, command, args: nil, kubeconfig_path: nil, interactive: false)
17
+ command_parts = []
18
+
19
+ if kubeconfig_path
20
+ command_parts << "KUBECONFIG=#{kubeconfig_path}"
21
+ end
22
+
23
+ command_parts << "kubectl exec"
24
+
25
+ if args
26
+ command_parts << args
27
+ end
28
+
29
+ command_parts << pod_name
30
+ command_parts << "-- #{command}"
31
+
32
+ # TODO: investigate how to do it with shell.
33
+ if interactive
34
+ system(command_parts.join(" "))
35
+ else
36
+ shell.exec!(command_parts.join(" "))
37
+ end
38
+ end
39
+
16
40
  def rolling_restart(shell, deployment_name, kubeconfig_path: nil)
17
41
  patch_deployment(shell, deployment_name, {
18
42
  spec: {
@@ -0,0 +1,32 @@
1
+ class KuberKit::Shell::Commands::RsyncCommands
2
+ def rsync(shell, source_path, target_path, target_host: nil, exclude: nil, delete: true)
3
+ # Add a trailing slash to directory to have behavior similar to CP command
4
+ if path_is_directory?(source_path) && !source_path.end_with?("/")
5
+ source_path = "#{source_path}/"
6
+ end
7
+
8
+ if target_host
9
+ destination = "#{target_host}:#{target_path}"
10
+ else
11
+ destination = target_path
12
+ end
13
+
14
+ args = [source_path, destination]
15
+ if exclude
16
+ Array(exclude).each do |e|
17
+ args << "--exclude=#{e}"
18
+ end
19
+ end
20
+
21
+ if delete
22
+ args << "--delete"
23
+ end
24
+
25
+ shell.exec!(%Q{rsync -a #{args.join(' ')}})
26
+ end
27
+
28
+ private
29
+ def path_is_directory?(path)
30
+ File.directory?(path)
31
+ end
32
+ end
@@ -3,21 +3,24 @@ require 'fileutils'
3
3
  class KuberKit::Shell::LocalShell < KuberKit::Shell::AbstractShell
4
4
  include KuberKit::Import[
5
5
  "tools.logger",
6
- "shell.command_counter"
6
+ "shell.command_counter",
7
+ "shell.rsync_commands",
7
8
  ]
8
9
 
9
- def exec!(command)
10
+ def exec!(command, log_command: true)
10
11
  command_number = command_counter.get_number.to_s.rjust(2, "0")
11
12
 
12
- logger.info("Executed command [#{command_number}]: #{command.to_s.cyan}")
13
+ if log_command
14
+ logger.info("Execute: [#{command_number}]: #{command.to_s.cyan}")
15
+ end
13
16
 
14
17
  result = nil
15
18
  IO.popen(command, err: [:child, :out]) do |io|
16
19
  result = io.read.chomp.strip
17
20
  end
18
21
 
19
- if result && result != ""
20
- logger.info("Finished command [#{command_number}] with result: \n#{result.grey}")
22
+ if result && result != "" && log_command
23
+ logger.info("Finished [#{command_number}] with result: \n#{result.grey}")
21
24
  end
22
25
 
23
26
  if $?.exitstatus != 0
@@ -27,6 +30,10 @@ class KuberKit::Shell::LocalShell < KuberKit::Shell::AbstractShell
27
30
  result
28
31
  end
29
32
 
33
+ def sync(local_path, remote_path, exclude: nil)
34
+ rsync_commands.rsync(self, local_path, remote_path, exclude: exclude)
35
+ end
36
+
30
37
  def read(file_path)
31
38
  File.read(file_path)
32
39
  end
@@ -41,6 +48,18 @@ class KuberKit::Shell::LocalShell < KuberKit::Shell::AbstractShell
41
48
  true
42
49
  end
43
50
 
51
+ def delete(file_path)
52
+ exec!("rm #{file_path}")
53
+ end
54
+
55
+ def file_exists?(file_path)
56
+ exec!("test -f #{file_path} && echo 'true' || echo 'false'", log_command: false) == 'true'
57
+ end
58
+
59
+ def dir_exists?(dir_path)
60
+ exec!("test -d #{dir_path} && echo 'true' || echo 'false'", log_command: false) == 'true'
61
+ end
62
+
44
63
  def recursive_list_files(path, name: nil)
45
64
  command = %Q{find -L #{path} -type f}
46
65
  command += " -name #{name}" if name
@@ -0,0 +1,60 @@
1
+ require 'net/ssh'
2
+
3
+ class KuberKit::Shell::SshSession
4
+ SshSessionError = Class.new(KuberKit::Error)
5
+
6
+ attr_reader :session, :host, :user, :port
7
+
8
+ def initialize(host:, user:, port:)
9
+ @host = host
10
+ @user = user
11
+ @port = port
12
+ @session = Net::SSH.start(host, user, {port: port})
13
+ end
14
+
15
+ def connected?
16
+ !!@session
17
+ end
18
+
19
+ def disconnect
20
+ return unless connected?
21
+ @session.close
22
+ @session = nil
23
+ end
24
+
25
+ def exec!(command)
26
+ stdout_data = ''
27
+ stderr_data = ''
28
+ exit_code = nil
29
+ channel = session.open_channel do |ch|
30
+ ch.exec(command) do |ch, success|
31
+ if !success
32
+ raise SshSessionError, "Shell command failed: #{command}\r\n#{stdout_data}\r\n#{stderr_data}"
33
+ end
34
+
35
+ channel.on_data do |ch,data|
36
+ stdout_data += data
37
+ end
38
+
39
+ channel.on_extended_data do |ch,type,data|
40
+ stderr_data += data
41
+ end
42
+
43
+ channel.on_request('exit-status') do |ch,data|
44
+ exit_code = data.read_long
45
+ end
46
+ end
47
+ end
48
+
49
+ channel.wait
50
+ session.loop
51
+
52
+ stdout_data = stdout_data.chomp.strip
53
+
54
+ if exit_code != 0
55
+ raise SshSessionError, "Shell command failed: #{command}\r\n#{stdout_data}\r\n#{stderr_data}"
56
+ end
57
+
58
+ stdout_data
59
+ end
60
+ end
@@ -0,0 +1,77 @@
1
+ require 'tempfile'
2
+
3
+ class KuberKit::Shell::SshShell < KuberKit::Shell::LocalShell
4
+ include KuberKit::Import[
5
+ "tools.logger",
6
+ "shell.command_counter",
7
+ "shell.rsync_commands",
8
+ "shell.local_shell"
9
+ ]
10
+
11
+ def connect(host:, user:, port:)
12
+ @ssh_session = KuberKit::Shell::SshSession.new(host: host, user: user, port: port)
13
+ end
14
+
15
+ def connected?
16
+ @ssh_session && @ssh_session.connected?
17
+ end
18
+
19
+ def disconnect
20
+ @ssh_session.disconnect if @ssh_session
21
+ end
22
+
23
+ def exec!(command, log_command: true)
24
+ command_number = command_counter.get_number.to_s.rjust(2, "0")
25
+
26
+ if log_command
27
+ logger.info("#{ssh_session.host.green} > Execute: [#{command_number}]: #{command.to_s.cyan}")
28
+ end
29
+
30
+ result = ssh_session.exec!(command)
31
+
32
+ if result && result != "" && log_command
33
+ logger.info("#{ssh_session.host.green} > Finished [#{command_number}] with result: \n#{result.grey}")
34
+ end
35
+
36
+ result
37
+ rescue KuberKit::Shell::SshSession::SshSessionError => e
38
+ raise ShellError.new(e.message)
39
+ end
40
+
41
+ def sync(local_path, remote_path, exclude: nil)
42
+ rsync_commands.rsync(
43
+ local_shell, local_path, remote_path,
44
+ target_host: "#{ssh_session.user}@#{ssh_session.host}",
45
+ exclude: exclude
46
+ )
47
+ end
48
+
49
+ def read(file_path)
50
+ exec!("cat #{file_path}")
51
+ end
52
+
53
+ def write(file_path, content)
54
+ Tempfile.create do |file|
55
+ file << content
56
+ file.flush
57
+ sync(file.path, file_path)
58
+ end
59
+
60
+ logger.info("Created file #{file_path.to_s.cyan}\r\n#{content.grey}")
61
+
62
+ true
63
+ end
64
+
65
+ private
66
+ def ssh_session
67
+ unless connected?
68
+ raise ArgumentError, "ssh session is not created, please call #connect"
69
+ end
70
+
71
+ @ssh_session
72
+ end
73
+
74
+ def ensure_directory_exists(file_path)
75
+ exec!("mkdir -p #{file_path}")
76
+ end
77
+ end