kuby-core 0.1.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 (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
+ module Docker
3
+ class SetupPhase < Phase
4
+ DEFAULT_WORKING_DIR = '/usr/src/app'.freeze
5
+
6
+ attr_accessor :base_image, :working_dir
7
+
8
+ def apply_to(dockerfile)
9
+ dockerfile.from(base_image || default_base_image)
10
+ dockerfile.workdir(working_dir || DEFAULT_WORKING_DIR)
11
+ dockerfile.env("RAILS_ENV=#{Kuby.env}")
12
+ dockerfile.env("KUBY_ENV=#{Kuby.env}")
13
+ end
14
+
15
+ private
16
+
17
+ def default_base_image
18
+ @default_base_image ||= case metadata.distro
19
+ when :debian
20
+ "ruby:#{RUBY_VERSION}"
21
+ when :alpine
22
+ "ruby:#{RUBY_VERSION}-alpine"
23
+ else
24
+ # ERROR
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,147 @@
1
+ require 'docker/remote'
2
+
3
+ module Kuby
4
+ module Docker
5
+ class Spec
6
+ attr_reader :definition
7
+
8
+ def initialize(definition)
9
+ @definition = definition
10
+ end
11
+
12
+ def base_image(image_url)
13
+ setup_phase.base_image = image_url
14
+ end
15
+
16
+ def working_dir(dir)
17
+ setup_phase.working_dir = dir
18
+ end
19
+
20
+ def rails_env(env)
21
+ setup_phase.rails_env = env
22
+ end
23
+
24
+ def bundler_version(version)
25
+ bundler_phase.bundler_version = version
26
+ end
27
+
28
+ def gemfile(path)
29
+ bundler_phase.gemfile = path
30
+ end
31
+
32
+ def package(pkg)
33
+ package_phase << pkg
34
+ end
35
+
36
+ def distro(distro_name)
37
+ metadata.distro = distro_name
38
+ @distro_spec = nil
39
+ end
40
+
41
+ def files(path)
42
+ copy_phase << path
43
+ end
44
+
45
+ def port(port)
46
+ webserver_phase.port = port
47
+ end
48
+
49
+ def image_url(url)
50
+ metadata.image_url = url
51
+ end
52
+
53
+ def use(*args)
54
+ layer_stack.use(*args)
55
+ end
56
+
57
+ def insert(*args)
58
+ layer_stack.insert(*args)
59
+ end
60
+
61
+ def delete(*args)
62
+ layer_stack.delete(*args)
63
+ end
64
+
65
+ def credentials(&block)
66
+ @credentials ||= Credentials.new
67
+ @credentials.instance_eval(&block) if block
68
+ @credentials
69
+ end
70
+
71
+ def to_dockerfile
72
+ Dockerfile.new.tap do |df|
73
+ layer_stack.each { |layer| layer.apply_to(df) }
74
+ end
75
+ end
76
+
77
+ def setup_phase
78
+ @setup_phase ||= SetupPhase.new(definition)
79
+ end
80
+
81
+ def package_phase
82
+ @package_phase ||= PackagePhase.new(definition)
83
+ end
84
+
85
+ def bundler_phase
86
+ @bundler_phase ||= BundlerPhase.new(definition)
87
+ end
88
+
89
+ def yarn_phase
90
+ @yarn_phase ||= YarnPhase.new(definition)
91
+ end
92
+
93
+ def copy_phase
94
+ @copy_phase ||= CopyPhase.new(definition)
95
+ end
96
+
97
+ def assets_phase
98
+ @assets_phase ||= AssetsPhase.new(definition)
99
+ end
100
+
101
+ def webserver_phase
102
+ @webserver_phase ||= WebserverPhase.new(definition)
103
+ end
104
+
105
+ def metadata
106
+ @metadata ||= Metadata.new(definition)
107
+ end
108
+
109
+ def tags
110
+ @tags ||= Tags.new(cli, remote_client, metadata)
111
+ end
112
+
113
+ def cli
114
+ @cli ||= Docker::CLI.new
115
+ end
116
+
117
+ def remote_client
118
+ @remote_client ||= ::Docker::Remote::Client.new(
119
+ metadata.image_host, metadata.image_repo,
120
+ credentials.username, credentials.password,
121
+ )
122
+ end
123
+
124
+ def distro_spec
125
+ @distro_spec ||= if distro_klass = Kuby.distros[metadata.distro]
126
+ distro_klass.new(self)
127
+ else
128
+ raise MissingDistroError, "distro '#{metadata.distro}' hasn't been registered"
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def layer_stack
135
+ @layer_stack ||= LayerStack.new.tap do |stack|
136
+ stack.use(:setup_phase, setup_phase)
137
+ stack.use(:package_phase, package_phase)
138
+ stack.use(:bundler_phase, bundler_phase)
139
+ stack.use(:yarn_phase, yarn_phase)
140
+ stack.use(:copy_phase, copy_phase)
141
+ stack.use(:assets_phase, assets_phase)
142
+ stack.use(:webserver_phase, webserver_phase)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,39 @@
1
+ module Kuby
2
+ module Docker
3
+ class Tags
4
+ LATEST = Metadata::LATEST_TAG
5
+
6
+ attr_reader :cli, :remote_client, :metadata
7
+
8
+ def initialize(cli, remote_client, metadata)
9
+ @cli = cli
10
+ @remote_client = remote_client
11
+ @metadata = metadata
12
+ end
13
+
14
+ def tags
15
+ (local.tags + remote.tags).uniq
16
+ end
17
+
18
+ def latest_tags
19
+ (local.latest_tags + remote.latest_tags).uniq
20
+ end
21
+
22
+ def timestamp_tags
23
+ (local.timestamp_tags + remote.timestamp_tags).uniq
24
+ end
25
+
26
+ def all
27
+ self
28
+ end
29
+
30
+ def local
31
+ @local ||= LocalTags.new(cli, metadata)
32
+ end
33
+
34
+ def remote
35
+ @remote ||= RemoteTags.new(remote_client, metadata)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ module Kuby
2
+ module Docker
3
+ class TimestampTag
4
+ RE = /20[\d]{2}(?:0[1-9]|11|12)(?:0[1-9]|1[1-9]|2[1-9]|3[01])/.freeze
5
+ FORMAT = '%Y%m%d%H%M%S'.freeze
6
+
7
+ def self.try_parse(str)
8
+ if str =~ RE
9
+ new(Time.strptime(str, FORMAT))
10
+ end
11
+ end
12
+
13
+ attr_reader :time
14
+
15
+ def initialize(time)
16
+ @time = time
17
+ end
18
+
19
+ def to_s
20
+ time.strftime(FORMAT)
21
+ end
22
+
23
+ def <=>(other)
24
+ time <=> other.time
25
+ end
26
+
27
+ def hash
28
+ time.hash
29
+ end
30
+
31
+ def eql?(other)
32
+ time == other.time
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ module Kuby
2
+ module Docker
3
+ class WebserverPhase < Phase
4
+ class Puma
5
+ attr_reader :phase
6
+
7
+ def initialize(phase)
8
+ @phase = phase
9
+ end
10
+
11
+ def apply_to(dockerfile)
12
+ dockerfile.cmd(
13
+ 'puma',
14
+ '--workers', '4',
15
+ '--bind', 'tcp://0.0.0.0',
16
+ '--port', phase.port,
17
+ '--pidfile', './server.pid',
18
+ './config.ru'
19
+ )
20
+
21
+ dockerfile.expose(phase.port)
22
+ end
23
+ end
24
+
25
+ DEFAULT_PORT = 8080
26
+ WEBSERVER_MAP = { puma: Puma }.freeze
27
+
28
+ attr_accessor :port, :webserver
29
+
30
+ def apply_to(dockerfile)
31
+ ws = webserver || default_webserver
32
+ ws_class = WEBSERVER_MAP[ws]
33
+ raise "No webserver named #{ws}" unless ws_class
34
+
35
+ ws_class.new(self).apply_to(dockerfile)
36
+ end
37
+
38
+ def port
39
+ @port || DEFAULT_PORT
40
+ end
41
+
42
+ private
43
+
44
+ def default_webserver
45
+ if Gem.loaded_specs.include?('puma')
46
+ :puma
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ module Kuby
2
+ module Docker
3
+ class YarnPhase < Phase
4
+ def apply_to(dockerfile)
5
+ dockerfile.copy('package.json', '.')
6
+ dockerfile.copy('yarn.lock*', '.')
7
+ dockerfile.run('yarn', 'install')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'kuby/kubernetes/errors'
2
+
3
+ module Kuby
4
+ module Kubernetes
5
+ autoload :MinikubeProvider, 'kuby/kubernetes/minikube_provider'
6
+ autoload :Deployer, 'kuby/kubernetes/deployer'
7
+ autoload :DockerConfig, 'kuby/kubernetes/docker_config'
8
+ autoload :Manifest, 'kuby/kubernetes/manifest'
9
+ autoload :Monitors, 'kuby/kubernetes/monitors'
10
+ autoload :Plugin, 'kuby/kubernetes/plugin'
11
+ autoload :Plugins, 'kuby/kubernetes/plugins'
12
+ autoload :Provider, 'kuby/kubernetes/provider'
13
+ autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
14
+ autoload :Spec, 'kuby/kubernetes/spec'
15
+ end
16
+ end
@@ -0,0 +1,94 @@
1
+ require 'fileutils'
2
+ require 'krane'
3
+ require 'securerandom'
4
+ require 'yaml'
5
+
6
+ module Kuby
7
+ module Kubernetes
8
+ class Deployer
9
+ attr_reader :definition
10
+
11
+ def initialize(definition)
12
+ @definition = definition
13
+ end
14
+
15
+ def deploy
16
+ namespaced, global = all_resources.partition do |resource|
17
+ # Unfortunately we can't use respond_to here because all KubeDSL
18
+ # objects use ObjectMeta, which has a namespace field. Not sure
19
+ # why, since it makes no sense for a namespace to have a namespace.
20
+ # Instead we just check for nil here.
21
+ resource.metadata.namespace
22
+ end
23
+
24
+ deploy_global_resources(global)
25
+ deploy_namespaced_resources(namespaced)
26
+ end
27
+
28
+ private
29
+
30
+ def deploy_global_resources(resources)
31
+ resources.each do |res|
32
+ Kuby.logger.info(
33
+ "Validating global resource, #{res.kind_sym.to_s.humanize.downcase} '#{res.metadata.name}'"
34
+ )
35
+
36
+ cli.apply(res, dry_run: true)
37
+ end
38
+
39
+ resources.each do |res|
40
+ Kuby.logger.info(
41
+ "Deploying #{res.kind_sym.to_s.humanize.downcase} '#{res.metadata.name}'"
42
+ )
43
+
44
+ cli.apply(res)
45
+ end
46
+ rescue InvalidResourceError => e
47
+ Kuby.logger.fatal(e.message)
48
+ Kuby.logger.fatal(e.resource.to_resource.to_yaml)
49
+ end
50
+
51
+ def deploy_namespaced_resources(resources)
52
+ old_kubeconfig = ENV['KUBECONFIG']
53
+ ENV['KUBECONFIG'] = provider.kubeconfig_path
54
+
55
+ tmpdir = Dir.mktmpdir('kuby-deploy-resources')
56
+
57
+ resources.each do |resource|
58
+ resource_path = File.join(
59
+ tmpdir, "#{SecureRandom.hex(6)}-#{resource.kind_sym.downcase}.yaml"
60
+ )
61
+
62
+ File.write(resource_path, resource.to_resource.to_yaml)
63
+ end
64
+
65
+ task = ::Krane::DeployTask.new(
66
+ namespace: namespace.metadata.name,
67
+ context: cli.current_context,
68
+ filenames: [tmpdir]
69
+ )
70
+
71
+ task.run!(verify_result: true, prune: false)
72
+ ensure
73
+ ENV['KUBECONFIG'] = old_kubeconfig
74
+ FileUtils.rm_rf(tmpdir)
75
+ end
76
+
77
+ def provider
78
+ definition.kubernetes.provider
79
+ end
80
+
81
+ def namespace
82
+ definition.kubernetes.namespace
83
+ end
84
+
85
+ def all_resources
86
+ definition.kubernetes.resources
87
+ end
88
+
89
+ def cli
90
+ provider.kubernetes_cli
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ require 'base64'
2
+ require 'kube-dsl'
3
+
4
+ module Kuby
5
+ module Kubernetes
6
+ class DockerConfig
7
+ extend ::KubeDSL::ValueFields
8
+
9
+ value_fields :registry_host, :username, :password, :email
10
+
11
+ def initialize(&block)
12
+ instance_eval(&block) if block
13
+ end
14
+
15
+ def serialize
16
+ {
17
+ registry_host.to_sym => {
18
+ username: username,
19
+ password: password,
20
+ email: email,
21
+ auth: Base64.strict_encode64("#{username}:#{password}")
22
+ }
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end