kuby-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +21 -0
  5. data/README.md +186 -0
  6. data/Rakefile +14 -0
  7. data/kuby-core.gemspec +27 -0
  8. data/lib/kuby.rb +112 -0
  9. data/lib/kuby/basic_logger.rb +22 -0
  10. data/lib/kuby/cli_base.rb +75 -0
  11. data/lib/kuby/definition.rb +29 -0
  12. data/lib/kuby/docker.rb +27 -0
  13. data/lib/kuby/docker/alpine.rb +62 -0
  14. data/lib/kuby/docker/assets_phase.rb +11 -0
  15. data/lib/kuby/docker/bundler_phase.rb +56 -0
  16. data/lib/kuby/docker/cli.rb +72 -0
  17. data/lib/kuby/docker/copy_phase.rb +23 -0
  18. data/lib/kuby/docker/credentials.rb +11 -0
  19. data/lib/kuby/docker/debian.rb +66 -0
  20. data/lib/kuby/docker/dockerfile.rb +128 -0
  21. data/lib/kuby/docker/errors.rb +22 -0
  22. data/lib/kuby/docker/layer_stack.rb +42 -0
  23. data/lib/kuby/docker/local_tags.rb +38 -0
  24. data/lib/kuby/docker/metadata.rb +69 -0
  25. data/lib/kuby/docker/package_list.rb +34 -0
  26. data/lib/kuby/docker/package_phase.rb +59 -0
  27. data/lib/kuby/docker/packages.rb +10 -0
  28. data/lib/kuby/docker/packages/managed_package.rb +29 -0
  29. data/lib/kuby/docker/packages/nodejs.rb +29 -0
  30. data/lib/kuby/docker/packages/package.rb +22 -0
  31. data/lib/kuby/docker/packages/yarn.rb +47 -0
  32. data/lib/kuby/docker/phase.rb +21 -0
  33. data/lib/kuby/docker/remote_tags.rb +24 -0
  34. data/lib/kuby/docker/setup_phase.rb +29 -0
  35. data/lib/kuby/docker/spec.rb +147 -0
  36. data/lib/kuby/docker/tags.rb +39 -0
  37. data/lib/kuby/docker/timestamp_tag.rb +36 -0
  38. data/lib/kuby/docker/webserver_phase.rb +51 -0
  39. data/lib/kuby/docker/yarn_phase.rb +11 -0
  40. data/lib/kuby/kubernetes.rb +16 -0
  41. data/lib/kuby/kubernetes/deployer.rb +94 -0
  42. data/lib/kuby/kubernetes/docker_config.rb +27 -0
  43. data/lib/kuby/kubernetes/errors.rb +22 -0
  44. data/lib/kuby/kubernetes/manifest.rb +56 -0
  45. data/lib/kuby/kubernetes/minikube_provider.rb +51 -0
  46. data/lib/kuby/kubernetes/plugin.rb +55 -0
  47. data/lib/kuby/kubernetes/plugins.rb +8 -0
  48. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +61 -0
  49. data/lib/kuby/kubernetes/plugins/rails_app.rb +16 -0
  50. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +58 -0
  51. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +142 -0
  52. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +393 -0
  53. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +10 -0
  54. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +13 -0
  55. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +10 -0
  56. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +23 -0
  57. data/lib/kuby/kubernetes/provider.rb +77 -0
  58. data/lib/kuby/kubernetes/registry_secret.rb +26 -0
  59. data/lib/kuby/kubernetes/spec.rb +152 -0
  60. data/lib/kuby/middleware.rb +5 -0
  61. data/lib/kuby/middleware/health_check.rb +16 -0
  62. data/lib/kuby/railtie.rb +18 -0
  63. data/lib/kuby/tasks.rb +135 -0
  64. data/lib/kuby/tasks/kuby.rake +63 -0
  65. data/lib/kuby/trailing_hash.rb +19 -0
  66. data/lib/kuby/version.rb +3 -0
  67. metadata +233 -0
@@ -0,0 +1,29 @@
1
+ module Kuby
2
+ class Definition
3
+ attr_reader :environment, :app
4
+
5
+ def initialize(environment, app, &block)
6
+ @environment = environment
7
+ @app = app
8
+
9
+ instance_eval(&block) if block
10
+ kubernetes.after_configuration
11
+ end
12
+
13
+ def docker(&block)
14
+ @docker ||= Docker::Spec.new(self)
15
+ @docker.instance_eval(&block) if block
16
+ @docker
17
+ end
18
+
19
+ def kubernetes(&block)
20
+ @kubernetes ||= Kubernetes::Spec.new(self)
21
+ @kubernetes.instance_eval(&block) if block
22
+ @kubernetes
23
+ end
24
+
25
+ def app_name
26
+ @app_name ||= app.class.module_parent.name
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ require 'kuby/docker/errors'
2
+
3
+ module Kuby
4
+ module Docker
5
+ autoload :Alpine, 'kuby/docker/alpine'
6
+ autoload :AssetsPhase, 'kuby/docker/assets_phase'
7
+ autoload :BundlerPhase, 'kuby/docker/bundler_phase'
8
+ autoload :CLI, 'kuby/docker/cli'
9
+ autoload :CopyPhase, 'kuby/docker/copy_phase'
10
+ autoload :Credentials, 'kuby/docker/credentials'
11
+ autoload :Debian, 'kuby/docker/debian'
12
+ autoload :Dockerfile, 'kuby/docker/dockerfile'
13
+ autoload :LayerStack, 'kuby/docker/layer_stack'
14
+ autoload :LocalTags, 'kuby/docker/local_tags'
15
+ autoload :Metadata, 'kuby/docker/metadata'
16
+ autoload :Packages, 'kuby/docker/packages'
17
+ autoload :PackagePhase, 'kuby/docker/package_phase'
18
+ autoload :Phase, 'kuby/docker/phase'
19
+ autoload :RemoteTags, 'kuby/docker/remote_tags'
20
+ autoload :SetupPhase, 'kuby/docker/setup_phase'
21
+ autoload :Spec, 'kuby/docker/spec'
22
+ autoload :Tags, 'kuby/docker/tags'
23
+ autoload :TimestampTag, 'kuby/docker/timestamp_tag'
24
+ autoload :WebserverPhase, 'kuby/docker/webserver_phase'
25
+ autoload :YarnPhase, 'kuby/docker/yarn_phase'
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ module Kuby
2
+ module Docker
3
+ class Alpine
4
+ SHELL_EXE = '/bin/sh'.freeze
5
+
6
+ DEFAULT_PACKAGES = [
7
+ [:ca_certificates],
8
+ [:nodejs, '12.14.1'],
9
+ [:yarn, '1.21.1'],
10
+ [:c_toolchain],
11
+ [:sqlite_dev],
12
+ [:tzdata]
13
+ ].freeze
14
+
15
+ attr_reader :phase
16
+
17
+ def initialize(phase)
18
+ @phase = phase
19
+ end
20
+
21
+ def install(packages, into:)
22
+ dockerfile = into
23
+ install_managed(packages, dockerfile)
24
+ install_unmanaged(packages, dockerfile)
25
+ end
26
+
27
+ def default_packages
28
+ DEFAULT_PACKAGES
29
+ end
30
+
31
+ def shell_exe
32
+ SHELL_EXE
33
+ end
34
+
35
+ private
36
+
37
+ def install_managed(packages, dockerfile)
38
+ pkgs = packages.select(&:managed?)
39
+
40
+ unless pkgs.empty?
41
+ package_names = pkgs.map { |pkg| pkg.package_name_for(:alpine) }
42
+ dockerfile.run(
43
+ "apk add --no-cache #{package_names.join(' ')}"
44
+ )
45
+ end
46
+ end
47
+
48
+ def install_unmanaged(packages, dockerfile)
49
+ packages
50
+ .reject(&:managed?)
51
+ .each do |package|
52
+ if package.respond_to?(:install_on_alpine)
53
+ package.send(:install_on_alpine, dockerfile)
54
+ else
55
+ raise UnsupportedDistroError, "Couldn't install #{package.name} "\
56
+ "because alpine is an unsupported distro"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ module Kuby
2
+ module Docker
3
+ class AssetsPhase < Phase
4
+ def apply_to(dockerfile)
5
+ dockerfile.run(
6
+ 'bundle', 'exec', 'rake', 'assets:precompile'
7
+ )
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ module Kuby
2
+ module Docker
3
+ class BundlerPhase < Phase
4
+ DEFAULT_WITHOUT = ['development', 'test', 'deploy'].freeze
5
+
6
+ attr_accessor :version, :gemfile, :without
7
+
8
+ def apply_to(dockerfile)
9
+ gf = gemfile || default_gemfile
10
+ lf = "#{gf}.lock"
11
+ v = version || default_version
12
+ wo = without || DEFAULT_WITHOUT
13
+
14
+ dockerfile.run("gem install bundler -v #{v}")
15
+
16
+ # bundle install
17
+ dockerfile.copy(gf, '.')
18
+ dockerfile.copy(lf, '.')
19
+
20
+ # set bundle path so docker will cache the bundle
21
+ dockerfile.run('mkdir ./bundle')
22
+ dockerfile.env('BUNDLE_PATH=./bundle')
23
+
24
+ unless wo.empty?
25
+ dockerfile.env("BUNDLE_WITHOUT='#{wo.join(' ')}'")
26
+ end
27
+
28
+ dockerfile.run(
29
+ 'bundle', 'install',
30
+ '--jobs', '$(nproc)',
31
+ '--retry', '3',
32
+ '--gemfile', gf
33
+ )
34
+
35
+ # generate binstubs and add the bin directory to our path
36
+ dockerfile.run('bundle binstubs --all')
37
+ dockerfile.env("PATH=./bin:$PATH")
38
+ end
39
+
40
+ private
41
+
42
+ def default_version
43
+ Bundler::VERSION
44
+ end
45
+
46
+ def default_gemfile
47
+ Bundler
48
+ .definition
49
+ .gemfiles
50
+ .first
51
+ .relative_path_from(app.root)
52
+ .to_s
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+ require 'open3'
3
+
4
+ module Kuby
5
+ module Docker
6
+ class CLI < CLIBase
7
+ attr_reader :executable
8
+
9
+ def initialize(executable = nil)
10
+ @executable = executable || `which docker`.strip
11
+ end
12
+
13
+ def build(dockerfile:, image_url:, tags:)
14
+ cmd = [
15
+ executable, 'build',
16
+ *tags.flat_map { |tag| ['-t', "#{image_url}:#{tag}"] },
17
+ '-f-', '.'
18
+ ]
19
+
20
+ open3_w({}, cmd) do |stdin, _wait_threads|
21
+ stdin.puts(dockerfile.to_s)
22
+ end
23
+
24
+ unless last_status.success?
25
+ raise BuildError, 'build failed: docker command exited with '\
26
+ "status code #{last_status.exitstatus}"
27
+ end
28
+ end
29
+
30
+ def run(image_url:, tag: 'latest', env: {}, ports: [])
31
+ cmd = [
32
+ executable, 'run',
33
+ *env.flat_map { |k, v| ['-e', "#{k}=#{v}"] },
34
+ *ports.flat_map { |port| ['-p', "#{port}:#{port}"] },
35
+ '--init',
36
+ '--rm',
37
+ "#{image_url}:#{tag}"
38
+ ]
39
+
40
+ execc(cmd)
41
+ end
42
+
43
+ def images(image_url)
44
+ cmd = [
45
+ executable, 'images', image_url,
46
+ '--format', '"{{json . }}"'
47
+ ]
48
+
49
+ backticks(cmd).split("\n").map do |image_data|
50
+ JSON.parse(image_data).each_with_object({}) do |(k, v), ret|
51
+ ret[k.underscore.to_sym] = v
52
+ end
53
+ end
54
+ end
55
+
56
+ def push(image_url, tag)
57
+ systemm([
58
+ executable, 'push', "#{image_url}:#{tag}"
59
+ ])
60
+
61
+ unless last_status.success?
62
+ raise PushError, 'push failed: docker command exited with '\
63
+ "status code #{last_status.exitstatus}"
64
+ end
65
+ end
66
+
67
+ def status_key
68
+ :kuby_docker_cli_last_status
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,23 @@
1
+ module Kuby
2
+ module Docker
3
+ class CopyPhase < Phase
4
+ DEFAULT_PATHS = ['./'].freeze
5
+
6
+ attr_reader :paths
7
+
8
+ def initialize(*args)
9
+ super
10
+ @paths = []
11
+ end
12
+
13
+ def <<(path)
14
+ paths << path
15
+ end
16
+
17
+ def apply_to(dockerfile)
18
+ to_copy = paths.empty? ? DEFAULT_PATHS : paths
19
+ to_copy.each { |path| dockerfile.copy(path, '.') }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ require 'kube-dsl'
2
+
3
+ module Kuby
4
+ module Docker
5
+ class Credentials
6
+ extend ::KubeDSL::ValueFields
7
+
8
+ value_fields :username, :password, :email
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ module Kuby
2
+ module Docker
3
+ class Debian
4
+ SHELL_EXE = '/bin/bash'
5
+
6
+ DEFAULT_PACKAGES = [
7
+ [:ca_certificates],
8
+ [:nodejs, '12.14.1'],
9
+ [:yarn, '1.21.1']
10
+ ].freeze
11
+
12
+ attr_reader :phase
13
+
14
+ def initialize(phase)
15
+ @phase = phase
16
+ end
17
+
18
+ def install(packages, into:)
19
+ dockerfile = into
20
+ install_managed(packages, dockerfile)
21
+ install_unmanaged(packages, dockerfile)
22
+ end
23
+
24
+ def default_packages
25
+ DEFAULT_PACKAGES
26
+ end
27
+
28
+ def shell_exe
29
+ SHELL_EXE
30
+ end
31
+
32
+ private
33
+
34
+ def install_managed(packages, dockerfile)
35
+ pkgs = packages.select(&:managed?)
36
+
37
+ pkg_cmd = <<~CMD.strip
38
+ apt-get update -qq && \\
39
+ DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends apt-transport-https && \\
40
+ DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends apt-utils && \\
41
+ DEBIAN_FRONTEND=noninteractive apt-get install -qq -y --no-install-recommends
42
+ CMD
43
+
44
+ unless pkgs.empty?
45
+ package_names = pkgs.map { |pkg| pkg.package_name_for(:debian) }
46
+ pkg_cmd << " \\\n #{package_names.join(" \\\n ")}"
47
+ end
48
+
49
+ dockerfile.run(pkg_cmd)
50
+ end
51
+
52
+ def install_unmanaged(packages, dockerfile)
53
+ packages
54
+ .reject(&:managed?)
55
+ .each do |package|
56
+ if package.respond_to?(:install_on_debian)
57
+ package.send(:install_on_debian, dockerfile)
58
+ else
59
+ raise UnsupportedDistroError, "Couldn't install #{package.name} "\
60
+ "because debian is an unsupported distro"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,128 @@
1
+ module Kuby
2
+ module Docker
3
+ class Dockerfile
4
+ class Command
5
+ attr_reader :args
6
+
7
+ def initialize(*args)
8
+ @args = args
9
+ end
10
+
11
+ def to_s
12
+ args.join(' ')
13
+ end
14
+ end
15
+
16
+ class From < Command
17
+ attr_reader :image_url, :as
18
+
19
+ def initialize(image_url, as: nil)
20
+ @image_url = image_url
21
+ @as = as
22
+ end
23
+
24
+ def to_s
25
+ str = "FROM #{image_url}"
26
+ str << " AS #{as}" if as
27
+ str
28
+ end
29
+ end
30
+
31
+ class Workdir < Command
32
+ def to_s; "WORKDIR #{super}"; end
33
+ end
34
+
35
+ class Env < Command
36
+ def to_s; "ENV #{super}"; end
37
+ end
38
+
39
+ class Run < Command
40
+ def to_s; "RUN #{super}"; end
41
+ end
42
+
43
+ class Copy < Command
44
+ attr_reader :source, :dest, :from
45
+
46
+ def initialize(source, dest, from: nil)
47
+ @source = source
48
+ @dest = dest
49
+ @from = from
50
+ end
51
+
52
+ def to_s
53
+ cmd = ['COPY']
54
+ cmd << "--from=#{from}" if from
55
+ cmd += [source, dest]
56
+ cmd.join(' ')
57
+ end
58
+ end
59
+
60
+ class Expose < Command
61
+ def to_s; "EXPOSE #{super}"; end
62
+ end
63
+
64
+ class Cmd < Command
65
+ def to_s; "CMD #{super}"; end
66
+ end
67
+
68
+ attr_reader :commands, :cursor
69
+
70
+ def initialize
71
+ @commands = []
72
+ @cursor = 0
73
+ end
74
+
75
+ def from(*args, **kwargs)
76
+ add From.new(*args, **kwargs)
77
+ end
78
+
79
+ def workdir(*args)
80
+ add Workdir.new(*args)
81
+ end
82
+
83
+ def env(*args)
84
+ add Env.new(*args)
85
+ end
86
+
87
+ def run(*args)
88
+ add Run.new(*args)
89
+ end
90
+
91
+ def copy(*args, **kwargs)
92
+ add Copy.new(*args, **kwargs)
93
+ end
94
+
95
+ def expose(*args)
96
+ add Expose.new(*args)
97
+ end
98
+
99
+ def cmd(*args)
100
+ add Cmd.new(*args)
101
+ end
102
+
103
+ def to_s
104
+ commands.map(&:to_s).join("\n")
105
+ end
106
+
107
+ def exposed_ports
108
+ commands
109
+ .select { |c| c.is_a?(Expose) }
110
+ .map { |c| c.args.first }
111
+ end
112
+
113
+ def insert_at(pos)
114
+ @cursor = pos
115
+ yield
116
+ ensure
117
+ @cursor = commands.size
118
+ end
119
+
120
+ private
121
+
122
+ def add(cmd)
123
+ commands.insert(cursor, cmd)
124
+ @cursor += 1
125
+ end
126
+ end
127
+ end
128
+ end