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