kuby-core 0.8.0 → 0.10.1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/Gemfile +1 -0
  4. data/README.md +11 -1
  5. data/bin/kuby +4 -0
  6. data/kuby-core.gemspec +5 -2
  7. data/lib/kuby.rb +55 -18
  8. data/lib/kuby/basic_logger.rb +13 -0
  9. data/lib/kuby/cli_base.rb +125 -4
  10. data/lib/kuby/commands.rb +244 -0
  11. data/lib/kuby/definition.rb +1 -3
  12. data/lib/kuby/dev_setup.rb +255 -0
  13. data/lib/kuby/docker.rb +1 -0
  14. data/lib/kuby/docker/bundler_phase.rb +3 -3
  15. data/lib/kuby/docker/cli.rb +24 -1
  16. data/lib/kuby/docker/dev_spec.rb +131 -0
  17. data/lib/kuby/docker/dockerfile.rb +16 -1
  18. data/lib/kuby/docker/errors.rb +1 -0
  19. data/lib/kuby/docker/layer_stack.rb +4 -0
  20. data/lib/kuby/docker/local_tags.rb +4 -0
  21. data/lib/kuby/docker/metadata.rb +14 -34
  22. data/lib/kuby/docker/setup_phase.rb +3 -2
  23. data/lib/kuby/docker/spec.rb +31 -5
  24. data/lib/kuby/docker/timestamp_tag.rb +6 -0
  25. data/lib/kuby/environment.rb +10 -1
  26. data/lib/kuby/kubernetes.rb +9 -9
  27. data/lib/kuby/kubernetes/deploy_task.rb +4 -0
  28. data/lib/kuby/kubernetes/deployer.rb +66 -11
  29. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +4 -4
  30. data/lib/kuby/kubernetes/provider.rb +8 -4
  31. data/lib/kuby/kubernetes/spec.rb +23 -22
  32. data/lib/kuby/plugin_registry.rb +27 -0
  33. data/lib/kuby/plugins/rails_app/database.rb +1 -2
  34. data/lib/kuby/plugins/rails_app/generators/kuby.rb +14 -16
  35. data/lib/kuby/plugins/rails_app/mysql.rb +0 -3
  36. data/lib/kuby/plugins/rails_app/plugin.rb +237 -42
  37. data/lib/kuby/plugins/rails_app/tasks.rake +2 -2
  38. data/lib/kuby/rails_commands.rb +83 -0
  39. data/lib/kuby/railtie.rb +0 -4
  40. data/lib/kuby/tasks.rb +77 -23
  41. data/lib/kuby/version.rb +1 -1
  42. data/spec/docker/metadata_spec.rb +84 -0
  43. data/spec/docker/spec_spec.rb +266 -0
  44. data/spec/docker/timestamp_tag_spec.rb +54 -4
  45. data/spec/dummy/Gemfile +54 -0
  46. data/spec/dummy/Gemfile.lock +223 -0
  47. data/spec/dummy/README.md +24 -0
  48. data/spec/dummy/Rakefile +6 -0
  49. data/spec/dummy/app/assets/config/manifest.js +2 -0
  50. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  51. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  52. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  53. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  55. data/spec/dummy/app/javascript/channels/consumer.js +6 -0
  56. data/spec/dummy/app/javascript/channels/index.js +5 -0
  57. data/spec/dummy/app/javascript/packs/application.js +17 -0
  58. data/spec/dummy/app/jobs/application_job.rb +7 -0
  59. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  60. data/spec/dummy/app/models/application_record.rb +3 -0
  61. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  62. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  63. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  64. data/spec/dummy/bin/bundle +114 -0
  65. data/spec/dummy/bin/rails +9 -0
  66. data/spec/dummy/bin/rake +9 -0
  67. data/spec/dummy/bin/setup +36 -0
  68. data/spec/dummy/bin/spring +17 -0
  69. data/spec/dummy/bin/yarn +11 -0
  70. data/spec/dummy/config.ru +5 -0
  71. data/spec/dummy/config/application.rb +19 -0
  72. data/spec/dummy/config/boot.rb +4 -0
  73. data/spec/dummy/config/cable.yml +10 -0
  74. data/spec/dummy/config/credentials.yml.enc +1 -0
  75. data/spec/dummy/config/database.yml +25 -0
  76. data/spec/dummy/config/environment.rb +5 -0
  77. data/spec/dummy/config/environments/development.rb +62 -0
  78. data/spec/dummy/config/environments/production.rb +112 -0
  79. data/spec/dummy/config/environments/test.rb +49 -0
  80. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  81. data/spec/dummy/config/initializers/assets.rb +14 -0
  82. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  83. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  84. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  85. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  86. data/spec/dummy/config/initializers/inflections.rb +16 -0
  87. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  88. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  89. data/spec/dummy/config/locales/en.yml +33 -0
  90. data/spec/dummy/config/master.key +1 -0
  91. data/spec/dummy/config/puma.rb +38 -0
  92. data/spec/dummy/config/routes.rb +3 -0
  93. data/spec/dummy/config/spring.rb +6 -0
  94. data/spec/dummy/config/storage.yml +34 -0
  95. data/spec/dummy/db/seeds.rb +7 -0
  96. data/spec/dummy/package.json +11 -0
  97. data/spec/dummy/public/404.html +67 -0
  98. data/spec/dummy/public/422.html +67 -0
  99. data/spec/dummy/public/500.html +66 -0
  100. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  101. data/spec/dummy/public/apple-touch-icon.png +0 -0
  102. data/spec/dummy/public/favicon.ico +0 -0
  103. data/spec/dummy/public/robots.txt +1 -0
  104. data/spec/dummy/test/application_system_test_case.rb +5 -0
  105. data/spec/dummy/test/channels/application_cable/connection_test.rb +11 -0
  106. data/spec/dummy/test/test_helper.rb +13 -0
  107. data/spec/dummy/tmp/cache/bootsnap-load-path-cache +0 -0
  108. data/spec/spec_helper.rb +77 -2
  109. data/spec/support/docker/fake_cli.rb +54 -0
  110. data/spec/support/docker/remote/fake_client.rb +16 -0
  111. data/spec/trailing_hash_spec.rb +23 -0
  112. metadata +111 -9
  113. data/lib/kuby/tasks/kuby.rake +0 -70
@@ -1,3 +1,5 @@
1
+ require 'digest'
2
+
1
3
  module Kuby
2
4
  module Docker
3
5
  class Dockerfile
@@ -65,6 +67,10 @@ module Kuby
65
67
  def to_s; "CMD #{super}"; end
66
68
  end
67
69
 
70
+ class Arg < Command
71
+ def to_s; "ARG #{super}"; end
72
+ end
73
+
68
74
  attr_reader :commands, :cursor
69
75
 
70
76
  def initialize
@@ -84,6 +90,10 @@ module Kuby
84
90
  add Env.new(*args)
85
91
  end
86
92
 
93
+ def arg(*args)
94
+ add Arg.new(*args)
95
+ end
96
+
87
97
  def run(*args)
88
98
  add Run.new(*args)
89
99
  end
@@ -101,7 +111,12 @@ module Kuby
101
111
  end
102
112
 
103
113
  def to_s
104
- commands.map(&:to_s).join("\n")
114
+ # ensure trailing newline
115
+ "#{commands.map(&:to_s).join("\n")}\n"
116
+ end
117
+
118
+ def checksum
119
+ Digest::SHA256.hexdigest(to_s)
105
120
  end
106
121
 
107
122
  def exposed_ports
@@ -2,6 +2,7 @@ module Kuby
2
2
  module Docker
3
3
  class BuildError < StandardError; end
4
4
  class PushError < StandardError; end
5
+ class PullError < StandardError; end
5
6
  class LoginError < StandardError; end
6
7
 
7
8
  class MissingTagError < StandardError
@@ -63,6 +63,10 @@ module Kuby
63
63
  stack.delete(name)
64
64
  layers.delete(name)
65
65
  end
66
+
67
+ def includes?(name)
68
+ layers.include?(name)
69
+ end
66
70
  end
67
71
  end
68
72
  end
@@ -33,6 +33,10 @@ module Kuby
33
33
  def timestamp_tags
34
34
  tags.map { |t| TimestampTag.try_parse(t) }.compact
35
35
  end
36
+
37
+ def latest_timestamp_tag
38
+ @latest_timestamp_tag ||= timestamp_tags.sort.last
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -5,6 +5,7 @@ module Kuby
5
5
  class Metadata
6
6
  DEFAULT_DISTRO = :debian
7
7
  DEFAULT_REGISTRY_HOST = 'https://www.docker.com'.freeze
8
+ DEFAULT_REGISTRY_SCHEME = 'https'
8
9
  LATEST_TAG = 'latest'
9
10
 
10
11
  attr_accessor :image_url
@@ -20,12 +21,7 @@ module Kuby
20
21
  end
21
22
 
22
23
  def image_host
23
- @image_host ||= if image_url.include?('/')
24
- uri = parse_url(image_url)
25
- "#{uri.scheme}://#{uri.host}"
26
- else
27
- DEFAULT_REGISTRY_HOST
28
- end
24
+ @image_host ||= "#{full_image_uri.scheme}://#{full_image_uri.host}"
29
25
  end
30
26
 
31
27
  def image_hostname
@@ -33,39 +29,13 @@ module Kuby
33
29
  end
34
30
 
35
31
  def image_repo
36
- @image_repo ||= if image_url.include?('/')
37
- parse_url(image_url).path.sub(/\A\//, '')
38
- else
39
- image_url
40
- end
32
+ @image_repo ||= full_image_uri.path.sub(/\A[\/]+/, '')
41
33
  end
42
34
 
43
35
  def tags
44
36
  @tags.empty? ? default_tags : @tags
45
37
  end
46
38
 
47
- def tag
48
- t = ENV.fetch('KUBY_DOCKER_TAG') do
49
- environment.docker.tags.latest_timestamp_tag
50
- end
51
-
52
- unless t
53
- raise MissingTagError, 'could not find latest timestamped tag'
54
- end
55
-
56
- t.to_s
57
- end
58
-
59
- def previous_tag(current_tag)
60
- t = environment.docker.tags.previous_timestamp_tag(current_tag)
61
-
62
- unless t
63
- raise MissingTagError, 'could not find previous timestamped tag'
64
- end
65
-
66
- t.to_s
67
- end
68
-
69
39
  def distro
70
40
  @distro || DEFAULT_DISTRO
71
41
  end
@@ -76,6 +46,16 @@ module Kuby
76
46
 
77
47
  private
78
48
 
49
+ def full_image_uri
50
+ @full_image_uri ||= if image_url.include?('://')
51
+ URI.parse(image_url)
52
+ elsif image_url =~ /\A[^.]+\.[^\/]+\//
53
+ URI.parse("#{DEFAULT_REGISTRY_SCHEME}://#{image_url}")
54
+ else
55
+ URI.parse("#{DEFAULT_REGISTRY_HOST}/#{image_url.sub(/\A[\/]+/, '')}")
56
+ end
57
+ end
58
+
79
59
  def default_image_url
80
60
  # assuming dockerhub by not specifying full url
81
61
  @default_image_url ||= environment.app_name.downcase
@@ -92,7 +72,7 @@ module Kuby
92
72
  return uri if uri.scheme
93
73
 
94
74
  # force a scheme because URI.parse won't work properly without one
95
- URI.parse("https://#{url}")
75
+ URI.parse("#{DEFAULT_REGISTRY_SCHEME}://#{url}")
96
76
  end
97
77
  end
98
78
  end
@@ -3,13 +3,14 @@ module Kuby
3
3
  class SetupPhase < Layer
4
4
  DEFAULT_WORKING_DIR = '/usr/src/app'.freeze
5
5
 
6
- attr_accessor :base_image, :working_dir
6
+ attr_accessor :base_image, :working_dir, :rails_env
7
7
 
8
8
  def apply_to(dockerfile)
9
9
  dockerfile.from(base_image || default_base_image)
10
10
  dockerfile.workdir(working_dir || DEFAULT_WORKING_DIR)
11
- dockerfile.env("RAILS_ENV=#{Kuby.env}")
11
+ dockerfile.env("RAILS_ENV=#{rails_env || Kuby.env}")
12
12
  dockerfile.env("KUBY_ENV=#{Kuby.env}")
13
+ dockerfile.arg('RAILS_MASTER_KEY')
13
14
  end
14
15
 
15
16
  private
@@ -22,15 +22,15 @@ module Kuby
22
22
  end
23
23
 
24
24
  def bundler_version(version)
25
- bundler_phase.bundler_version = version
25
+ bundler_phase.version = version
26
26
  end
27
27
 
28
28
  def gemfile(path)
29
29
  bundler_phase.gemfile = path
30
30
  end
31
31
 
32
- def package(pkg)
33
- package_phase << pkg
32
+ def package(*args)
33
+ package_phase.add(*args)
34
34
  end
35
35
 
36
36
  def distro(distro_name)
@@ -62,6 +62,10 @@ module Kuby
62
62
  layer_stack.delete(*args)
63
63
  end
64
64
 
65
+ def exists?(*args)
66
+ layer_stack.includes?(*args)
67
+ end
68
+
65
69
  def credentials(&block)
66
70
  @credentials ||= Credentials.new
67
71
  @credentials.instance_eval(&block) if block
@@ -106,8 +110,26 @@ module Kuby
106
110
  @metadata ||= Metadata.new(environment)
107
111
  end
108
112
 
109
- def tags
110
- @tags ||= Tags.new(cli, remote_client, metadata)
113
+ def tag
114
+ t = ENV.fetch('KUBY_DOCKER_TAG') do
115
+ tags.latest_timestamp_tag
116
+ end
117
+
118
+ unless t
119
+ raise MissingTagError, 'could not find latest timestamped tag'
120
+ end
121
+
122
+ t.to_s
123
+ end
124
+
125
+ def previous_tag(current_tag)
126
+ t = tags.previous_timestamp_tag(current_tag)
127
+
128
+ unless t
129
+ raise MissingTagError, 'could not find previous timestamped tag'
130
+ end
131
+
132
+ t.to_s
111
133
  end
112
134
 
113
135
  def cli
@@ -129,6 +151,10 @@ module Kuby
129
151
  end
130
152
  end
131
153
 
154
+ def tags
155
+ @tags ||= Tags.new(cli, remote_client, metadata)
156
+ end
157
+
132
158
  private
133
159
 
134
160
  def layer_stack
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  module Kuby
2
4
  module Docker
3
5
  class TimestampTag
@@ -27,6 +29,10 @@ module Kuby
27
29
  time <=> other.time
28
30
  end
29
31
 
32
+ def ==(other)
33
+ time == other.time
34
+ end
35
+
30
36
  def hash
31
37
  time.hash
32
38
  end
@@ -8,7 +8,12 @@ module Kuby
8
8
  end
9
9
 
10
10
  def docker(&block)
11
- @docker ||= Docker::Spec.new(self)
11
+ @docker ||= if development?
12
+ Docker::DevSpec.new(self)
13
+ else
14
+ Docker::Spec.new(self)
15
+ end
16
+
12
17
  @docker.instance_eval(&block) if block
13
18
  @docker
14
19
  end
@@ -22,5 +27,9 @@ module Kuby
22
27
  def app_name
23
28
  definition.app_name
24
29
  end
30
+
31
+ def development?
32
+ name == 'development'
33
+ end
25
34
  end
26
35
  end
@@ -2,14 +2,14 @@ require 'kuby/kubernetes/errors'
2
2
 
3
3
  module Kuby
4
4
  module Kubernetes
5
- autoload :MinikubeProvider, 'kuby/kubernetes/minikube_provider'
6
- autoload :Deployer, 'kuby/kubernetes/deployer'
7
- autoload :DeployTask, 'kuby/kubernetes/deploy_task'
8
- autoload :DockerConfig, 'kuby/kubernetes/docker_config'
9
- autoload :Manifest, 'kuby/kubernetes/manifest'
10
- autoload :Plugins, 'kuby/kubernetes/plugins'
11
- autoload :Provider, 'kuby/kubernetes/provider'
12
- autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
13
- autoload :Spec, 'kuby/kubernetes/spec'
5
+ autoload :Deployer, 'kuby/kubernetes/deployer'
6
+ autoload :DeployTask, 'kuby/kubernetes/deploy_task'
7
+ autoload :DockerConfig, 'kuby/kubernetes/docker_config'
8
+ autoload :DockerDesktopProvider, 'kuby/kubernetes/docker_desktop_provider'
9
+ autoload :Manifest, 'kuby/kubernetes/manifest'
10
+ autoload :Plugins, 'kuby/kubernetes/plugins'
11
+ autoload :Provider, 'kuby/kubernetes/provider'
12
+ autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
13
+ autoload :Spec, 'kuby/kubernetes/spec'
14
14
  end
15
15
  end
@@ -18,6 +18,10 @@ module Kuby
18
18
  end
19
19
  end
20
20
 
21
+ def logger
22
+ deploy_task.instance_variable_get(:@logger)
23
+ end
24
+
21
25
  private
22
26
 
23
27
  def with_env(new_env)
@@ -6,22 +6,42 @@ module Kuby
6
6
  module Kubernetes
7
7
  class Deployer
8
8
  attr_reader :environment
9
+ attr_accessor :logdev
9
10
 
10
11
  def initialize(environment)
11
12
  @environment = environment
12
13
  end
13
14
 
14
15
  def deploy
15
- namespaced, global = all_resources.partition do |resource|
16
- # Unfortunately we can't use respond_to here because all KubeDSL
17
- # objects use ObjectMeta, which has a namespace field. Not sure
18
- # why, since it makes no sense for a namespace to have a namespace.
19
- # Instead we just check for nil here.
20
- resource.metadata.namespace
16
+ restart_rails_deployment_if_necessary do
17
+ namespaced, global = all_resources.partition do |resource|
18
+ # Unfortunately we can't use respond_to here because all KubeDSL
19
+ # objects use ObjectMeta, which has a namespace field. Not sure
20
+ # why, since it makes no sense for a namespace to have a namespace.
21
+ # Instead we just check for nil here.
22
+ resource.metadata.namespace
23
+ end
24
+
25
+ deploy_global_resources(global)
26
+ deploy_namespaced_resources(namespaced)
21
27
  end
28
+ end
29
+
30
+ # adhere to the "CLI" interface
31
+ def with_pipes(out = STDOUT, err = STDERR)
32
+ previous_logdev = logdev
33
+ @logdev = err
34
+ yield
35
+ ensure
36
+ @logdev = previous_logdev
37
+ end
38
+
39
+ def logdev
40
+ @logdev || STDERR
41
+ end
22
42
 
23
- deploy_global_resources(global)
24
- deploy_namespaced_resources(namespaced)
43
+ def last_status
44
+ nil
25
45
  end
26
46
 
27
47
  private
@@ -67,22 +87,57 @@ module Kuby
67
87
  filenames: [tmpdir]
68
88
  )
69
89
 
90
+ task.logger.reopen(logdev)
91
+
70
92
  task.run!(verify_result: true, prune: false)
71
93
  ensure
72
94
  ENV['KUBECONFIG'] = old_kubeconfig
73
95
  FileUtils.rm_rf(tmpdir)
74
96
  end
75
97
 
98
+ def restart_rails_deployment_if_necessary
99
+ deployed_image = nil
100
+ current_image = "#{docker.metadata.image_url}:#{docker.tag}"
101
+
102
+ if rails_app = kubernetes.plugin(:rails_app)
103
+ deployment_name = rails_app.deployment.metadata.name
104
+
105
+ begin
106
+ deployment = cli.get_object(
107
+ 'deployment', namespace.metadata.name, deployment_name
108
+ )
109
+
110
+ deployed_image = deployment.dig(*%w(spec template spec containers), 0, 'image')
111
+ rescue ::KubernetesCLI::GetResourceError
112
+ end
113
+ end
114
+
115
+ yield
116
+
117
+ if deployed_image == current_image
118
+ Kuby.logger.info('Docker image URL did not change, restarting Rails deployment manually')
119
+ cli.restart_deployment(namespace.metadata.name, deployment_name)
120
+ end
121
+ end
122
+
76
123
  def provider
77
- environment.kubernetes.provider
124
+ kubernetes.provider
78
125
  end
79
126
 
80
127
  def namespace
81
- environment.kubernetes.namespace
128
+ kubernetes.namespace
82
129
  end
83
130
 
84
131
  def all_resources
85
- environment.kubernetes.resources
132
+ kubernetes.resources
133
+ end
134
+
135
+ def docker
136
+ environment.docker
137
+ end
138
+
139
+ def kubernetes
140
+ environment.kubernetes
86
141
  end
87
142
 
88
143
  def cli
@@ -2,7 +2,7 @@ require 'kube-dsl'
2
2
 
3
3
  module Kuby
4
4
  module Kubernetes
5
- class MinikubeProvider < Provider
5
+ class DockerDesktopProvider < Provider
6
6
  STORAGE_CLASS_NAME = 'hostpath'.freeze
7
7
 
8
8
  class Config
@@ -20,9 +20,9 @@ module Kuby
20
20
  def after_configuration
21
21
  if rails_app = spec.plugin(:rails_app)
22
22
  # Remove ingress and change service type from ClusterIP to
23
- # LoadBalancer. No need to set up ingress for minikube since
24
- # it handles all the localhost mapping, etc if you set up a
25
- # service LB.
23
+ # LoadBalancer. No need to set up ingress for Docker Desktop
24
+ # since it handles all the localhost mapping, etc if you set
25
+ # up a service LB.
26
26
  rails_app.resources.delete(rails_app.ingress)
27
27
  rails_app.service.spec { type 'LoadBalancer' }
28
28
  end