kubernetes-deploy 0.18.0 → 0.18.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/ISSUE_TEMPLATE.md +25 -0
  4. data/README.md +2 -3
  5. data/lib/kubernetes-deploy.rb +1 -0
  6. data/lib/kubernetes-deploy/deploy_task.rb +8 -5
  7. data/lib/kubernetes-deploy/kubernetes_resource.rb +34 -32
  8. data/lib/kubernetes-deploy/kubernetes_resource/bucket.rb +2 -7
  9. data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +20 -49
  10. data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +4 -10
  11. data/lib/kubernetes-deploy/kubernetes_resource/cron_job.rb +0 -10
  12. data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +36 -32
  13. data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +69 -66
  14. data/lib/kubernetes-deploy/kubernetes_resource/elasticsearch.rb +1 -16
  15. data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +2 -8
  16. data/lib/kubernetes-deploy/kubernetes_resource/memcached.rb +18 -31
  17. data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +4 -10
  18. data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +28 -31
  19. data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +2 -8
  20. data/lib/kubernetes-deploy/kubernetes_resource/pod_set_base.rb +10 -12
  21. data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +2 -8
  22. data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +19 -48
  23. data/lib/kubernetes-deploy/kubernetes_resource/replica_set.rb +33 -35
  24. data/lib/kubernetes-deploy/kubernetes_resource/resource_quota.rb +3 -14
  25. data/lib/kubernetes-deploy/kubernetes_resource/service.rb +20 -34
  26. data/lib/kubernetes-deploy/kubernetes_resource/service_account.rb +2 -8
  27. data/lib/kubernetes-deploy/kubernetes_resource/stateful_set.rb +37 -31
  28. data/lib/kubernetes-deploy/kubernetes_resource/statefulservice.rb +1 -16
  29. data/lib/kubernetes-deploy/kubernetes_resource/topic.rb +1 -16
  30. data/lib/kubernetes-deploy/resource_watcher.rb +6 -3
  31. data/lib/kubernetes-deploy/restart_task.rb +3 -1
  32. data/lib/kubernetes-deploy/sync_mediator.rb +66 -0
  33. data/lib/kubernetes-deploy/version.rb +1 -1
  34. metadata +4 -3
  35. data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +0 -33
@@ -3,18 +3,18 @@ module KubernetesDeploy
3
3
  class Service < KubernetesResource
4
4
  TIMEOUT = 7.minutes
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name)
8
- @found = st.success?
9
- @related_deployment_replicas = fetch_related_replica_count
10
- @num_pods_selected = fetch_related_pod_count
6
+ SYNC_DEPENDENCIES = %w(Pod Deployment)
7
+ def sync(mediator)
8
+ super
9
+ @related_deployments = selector.present? ? mediator.get_all(Deployment.kind, selector) : []
10
+ @related_pods = selector.present? ? mediator.get_all(Pod.kind, selector) : []
11
11
  end
12
12
 
13
13
  def status
14
- if !requires_endpoints?
15
- "Doesn't require any endpoint"
16
- elsif @num_pods_selected.blank?
17
- "Failed to count related pods"
14
+ if !exists?
15
+ "Not found"
16
+ elsif !requires_endpoints?
17
+ "Doesn't require any endpoints"
18
18
  elsif selects_some_pods?
19
19
  "Selects at least 1 pod"
20
20
  else
@@ -23,6 +23,7 @@ module KubernetesDeploy
23
23
  end
24
24
 
25
25
  def deploy_succeeded?
26
+ return false unless exists?
26
27
  return exists? unless requires_endpoints?
27
28
  # We can't use endpoints if we want the service to be able to fail fast when the pods are down
28
29
  exposes_zero_replica_deployment? || selects_some_pods?
@@ -36,15 +37,11 @@ module KubernetesDeploy
36
37
  "This service does not seem to select any pods. This means its spec.selector is probably incorrect."
37
38
  end
38
39
 
39
- def exists?
40
- @found
41
- end
42
-
43
40
  private
44
41
 
45
42
  def exposes_zero_replica_deployment?
46
- return false unless @related_deployment_replicas
47
- @related_deployment_replicas == 0
43
+ return false unless related_replica_count
44
+ related_replica_count == 0
48
45
  end
49
46
 
50
47
  def requires_endpoints?
@@ -52,35 +49,24 @@ module KubernetesDeploy
52
49
  return false if external_name_svc?
53
50
 
54
51
  # problem counting replicas - by default, assume endpoints are required
55
- return true if @related_deployment_replicas.blank?
52
+ return true if related_replica_count.blank?
56
53
 
57
- @related_deployment_replicas > 0
54
+ related_replica_count > 0
58
55
  end
59
56
 
60
57
  def selects_some_pods?
61
- return false unless @num_pods_selected
62
- @num_pods_selected > 0
58
+ return false unless selector.present?
59
+ @related_pods.present?
63
60
  end
64
61
 
65
62
  def selector
66
- @selector ||= @definition["spec"].fetch("selector", []).map { |k, v| "#{k}=#{v}" }.join(",")
67
- end
68
-
69
- def fetch_related_pod_count
70
- return 0 unless selector.present?
71
- raw_json, _err, st = kubectl.run("get", "pods", "--selector=#{selector}", "--output=json")
72
- return unless st.success?
73
- JSON.parse(raw_json)["items"].length
63
+ @definition["spec"].fetch("selector", {})
74
64
  end
75
65
 
76
- def fetch_related_replica_count
66
+ def related_replica_count
77
67
  return 0 unless selector.present?
78
- raw_json, _err, st = kubectl.run("get", "deployments", "--selector=#{selector}", "--output=json")
79
- return unless st.success?
80
-
81
- deployments = JSON.parse(raw_json)["items"]
82
- return unless deployments.length == 1
83
- deployments.first["spec"]["replicas"].to_i
68
+ return unless @related_deployments.length == 1
69
+ @related_deployments.first["spec"]["replicas"].to_i
84
70
  end
85
71
 
86
72
  def external_name_svc?
@@ -3,10 +3,8 @@ module KubernetesDeploy
3
3
  class ServiceAccount < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def sync
7
- _, _err, st = kubectl.run("get", type, @name, "--output=json")
8
- @status = st.success? ? "Created" : "Unknown"
9
- @found = st.success?
6
+ def status
7
+ exists? ? "Created" : "Unknown"
10
8
  end
11
9
 
12
10
  def deploy_succeeded?
@@ -17,10 +15,6 @@ module KubernetesDeploy
17
15
  false
18
16
  end
19
17
 
20
- def exists?
21
- @found
22
- end
23
-
24
18
  def timeout_message
25
19
  UNUSUAL_FAILURE_MESSAGE
26
20
  end
@@ -6,31 +6,21 @@ module KubernetesDeploy
6
6
  ONDELETE = 'OnDelete'
7
7
  attr_reader :pods
8
8
 
9
- def sync
10
- raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
11
- @found = st.success?
9
+ SYNC_DEPENDENCIES = %w(Pod)
10
+ def sync(mediator)
11
+ super
12
+ @pods = exists? ? find_pods(mediator) : []
13
+ @server_version ||= mediator.kubectl.server_version
14
+ end
12
15
 
13
- if @found
14
- stateful_data = JSON.parse(raw_json)
15
- @desired_replicas = stateful_data["spec"]["replicas"].to_i
16
- @status_data = stateful_data["status"]
17
- rollout_data = stateful_data["status"].slice("replicas", "readyReplicas", "currentReplicas")
18
- @update_strategy = if kubectl.server_version < Gem::Version.new("1.7.0")
19
- ONDELETE
20
- else
21
- stateful_data['spec']['updateStrategy']['type']
22
- end
23
- @status = rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
24
- @pods = find_pods(stateful_data)
25
- else # reset
26
- @status_data = { 'readyReplicas' => '-1', 'currentReplicas' => '-2' }
27
- @status = nil
28
- @pods = []
29
- end
16
+ def status
17
+ return super unless @instance_data["status"].present?
18
+ rollout_data = @instance_data["status"].slice("replicas", "readyReplicas", "currentReplicas")
19
+ rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
30
20
  end
31
21
 
32
22
  def deploy_succeeded?
33
- if @update_strategy == ONDELETE
23
+ if update_strategy == ONDELETE
34
24
  # Gem cannot monitor update since it doesn't occur until delete
35
25
  unless @success_assumption_warning_shown
36
26
  @logger.warn("WARNING: Your StatefulSet's updateStrategy is set to OnDelete, "\
@@ -40,27 +30,43 @@ module KubernetesDeploy
40
30
  end
41
31
  true
42
32
  else
43
- @status_data['currentRevision'] == @status_data['updateRevision'] &&
44
- @desired_replicas == @status_data['readyReplicas'].to_i &&
45
- @desired_replicas == @status_data['currentReplicas'].to_i
33
+ status_data['currentRevision'] == status_data['updateRevision'] &&
34
+ desired_replicas == status_data['readyReplicas'].to_i &&
35
+ desired_replicas == status_data['currentReplicas'].to_i
46
36
  end
47
37
  end
48
38
 
49
39
  def deploy_failed?
50
- return false if @update_strategy == ONDELETE
40
+ return false if update_strategy == ONDELETE
51
41
  pods.present? && pods.any?(&:deploy_failed?)
52
42
  end
53
43
 
54
- def exists?
55
- @found
44
+ private
45
+
46
+ def update_strategy
47
+ if @server_version < Gem::Version.new("1.7.0")
48
+ ONDELETE
49
+ elsif exists?
50
+ @instance_data['spec']['updateStrategy']['type']
51
+ else
52
+ 'Unknown'
53
+ end
56
54
  end
57
55
 
58
- private
56
+ def status_data
57
+ return { 'readyReplicas' => '-1', 'currentReplicas' => '-2' } unless exists?
58
+ @instance_data["status"]
59
+ end
60
+
61
+ def desired_replicas
62
+ return -1 unless exists?
63
+ @instance_data["spec"]["replicas"].to_i
64
+ end
59
65
 
60
- def parent_of_pod?(set_data, pod_data)
66
+ def parent_of_pod?(pod_data)
61
67
  return false unless pod_data.dig("metadata", "ownerReferences")
62
- pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == set_data["metadata"]["uid"] } &&
63
- set_data["status"]["currentRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"]
68
+ pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == @instance_data["metadata"]["uid"] } &&
69
+ @instance_data["status"]["currentRevision"] == pod_data["metadata"]["labels"]["controller-revision-hash"]
64
70
  end
65
71
  end
66
72
  end
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class Statefulservice < KubernetesResource
4
- def sync
5
- _, _err, st = kubectl.run("get", type, @name)
6
- @found = st.success?
7
- end
8
-
9
4
  def deploy_succeeded?
10
- return false unless deploy_started?
11
-
12
- unless @success_assumption_warning_shown
13
- @logger.warn("Don't know how to monitor resources of type #{type}. Assuming #{id} deployed successfully.")
14
- @success_assumption_warning_shown = true
15
- end
16
- true
17
- end
18
-
19
- def exists?
20
- @found
5
+ super # success assumption, with warning
21
6
  end
22
7
 
23
8
  def deploy_failed?
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class Topic < KubernetesResource
4
- def sync
5
- _, _err, st = kubectl.run("get", type, @name)
6
- @found = st.success?
7
- end
8
-
9
4
  def deploy_succeeded?
10
- return false unless deploy_started?
11
-
12
- unless @success_assumption_warning_shown
13
- @logger.warn("Don't know how to monitor resources of type #{type}. Assuming #{id} deployed successfully.")
14
- @success_assumption_warning_shown = true
15
- end
16
- true
17
- end
18
-
19
- def exists?
20
- @found
5
+ super # success assumption, with warning
21
6
  end
22
7
 
23
8
  def deploy_failed?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class ResourceWatcher
4
- def initialize(resources, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
4
+ def initialize(resources:, sync_mediator:, logger:, deploy_started_at: Time.now.utc, operation_name: "deploy")
5
5
  unless resources.is_a?(Enumerable)
6
6
  raise ArgumentError, <<~MSG
7
7
  ResourceWatcher expects Enumerable collection, got `#{resources.class}` instead
@@ -9,6 +9,7 @@ module KubernetesDeploy
9
9
  end
10
10
  @resources = resources
11
11
  @logger = logger
12
+ @sync_mediator = sync_mediator
12
13
  @deploy_started_at = deploy_started_at
13
14
  @operation_name = operation_name
14
15
  end
@@ -23,7 +24,7 @@ module KubernetesDeploy
23
24
  end
24
25
  delay_sync_until = Time.now.utc + delay_sync # don't pummel the API if the sync is fast
25
26
 
26
- KubernetesDeploy::Concurrency.split_across_threads(remainder, &:sync)
27
+ @sync_mediator.sync(remainder)
27
28
  new_successes, remainder = remainder.partition(&:deploy_succeeded?)
28
29
  new_failures, remainder = remainder.partition(&:deploy_failed?)
29
30
  new_timeouts, remainder = remainder.partition(&:deploy_timed_out?)
@@ -93,7 +94,9 @@ module KubernetesDeploy
93
94
  "failed to #{@operation_name} #{failures.length} #{'resource'.pluralize(failures.length)}"
94
95
  )
95
96
  end
96
- KubernetesDeploy::Concurrency.split_across_threads(failed_resources, &:sync_debug_info)
97
+ KubernetesDeploy::Concurrency.split_across_threads(failed_resources) do |r|
98
+ r.sync_debug_info(@sync_mediator.kubectl)
99
+ end
97
100
  failed_resources.each { |r| @logger.summary.add_paragraph(r.debug_message) }
98
101
  end
99
102
  end
@@ -24,6 +24,7 @@ module KubernetesDeploy
24
24
  @context = context
25
25
  @namespace = namespace
26
26
  @logger = logger
27
+ @sync_mediator = SyncMediator.new(namespace: @namespace, context: @context, logger: @logger)
27
28
  end
28
29
 
29
30
  def perform(*args)
@@ -48,7 +49,8 @@ module KubernetesDeploy
48
49
 
49
50
  @logger.phase_heading("Waiting for rollout")
50
51
  resources = build_watchables(deployments, start)
51
- ResourceWatcher.new(resources, logger: @logger, operation_name: "restart").run
52
+ ResourceWatcher.new(resources: resources, sync_mediator: @sync_mediator,
53
+ logger: @logger, operation_name: "restart").run
52
54
  failed_resources = resources.reject(&:deploy_succeeded?)
53
55
  success = failed_resources.empty?
54
56
  if !success && failed_resources.all?(&:deploy_timed_out?)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class SyncMediator
4
+ def initialize(namespace:, context:, logger:)
5
+ @namespace = namespace
6
+ @context = context
7
+ @logger = logger
8
+ clear_cache
9
+ end
10
+
11
+ def get_instance(kind, resource_name)
12
+ if @cache.key?(kind)
13
+ @cache.dig(kind, resource_name) || {}
14
+ else
15
+ request_instance(kind, resource_name)
16
+ end
17
+ end
18
+
19
+ def get_all(kind, selector = nil)
20
+ fetch_by_kind(kind) unless @cache.key?(kind)
21
+ instances = @cache.fetch(kind, {}).values
22
+ return instances unless selector
23
+
24
+ instances.select do |r|
25
+ labels = r.dig("metadata", "labels") || {}
26
+ labels >= selector
27
+ end
28
+ end
29
+
30
+ def sync(resources)
31
+ clear_cache
32
+ dependencies = resources.map(&:class).uniq.flat_map do |c|
33
+ c::SYNC_DEPENDENCIES if c.const_defined?('SYNC_DEPENDENCIES')
34
+ end
35
+ kinds = (resources.map(&:type) + dependencies).compact.uniq
36
+ kinds.each { |kind| fetch_by_kind(kind) }
37
+
38
+ KubernetesDeploy::Concurrency.split_across_threads(resources) do |r|
39
+ r.sync(dup)
40
+ end
41
+ end
42
+
43
+ def kubectl
44
+ @kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: false)
45
+ end
46
+
47
+ private
48
+
49
+ def clear_cache
50
+ @cache = {}
51
+ end
52
+
53
+ def request_instance(kind, iname)
54
+ raw_json, _, st = kubectl.run("get", kind, iname, "-a", "--output=json")
55
+ st.success? ? JSON.parse(raw_json) : {}
56
+ end
57
+
58
+ def fetch_by_kind(kind)
59
+ raw_json, _, st = kubectl.run("get", kind, "-a", "--output=json")
60
+ return unless st.success?
61
+ @cache[kind] = JSON.parse(raw_json)["items"].each_with_object({}) do |r, instances|
62
+ instances[r.dig("metadata", "name")] = r
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
- VERSION = "0.18.0"
3
+ VERSION = "0.18.1"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubernetes-deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katrina Verey
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-04-03 00:00:00.000000000 Z
12
+ date: 2018-04-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -210,6 +210,7 @@ files:
210
210
  - CHANGELOG.md
211
211
  - CODE_OF_CONDUCT.md
212
212
  - Gemfile
213
+ - ISSUE_TEMPLATE.md
213
214
  - LICENSE.txt
214
215
  - NO-BINAUTH
215
216
  - README.md
@@ -237,7 +238,6 @@ files:
237
238
  - lib/kubernetes-deploy/kubectl.rb
238
239
  - lib/kubernetes-deploy/kubernetes_resource.rb
239
240
  - lib/kubernetes-deploy/kubernetes_resource/bucket.rb
240
- - lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb
241
241
  - lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb
242
242
  - lib/kubernetes-deploy/kubernetes_resource/config_map.rb
243
243
  - lib/kubernetes-deploy/kubernetes_resource/cron_job.rb
@@ -264,6 +264,7 @@ files:
264
264
  - lib/kubernetes-deploy/restart_task.rb
265
265
  - lib/kubernetes-deploy/runner_task.rb
266
266
  - lib/kubernetes-deploy/statsd.rb
267
+ - lib/kubernetes-deploy/sync_mediator.rb
267
268
  - lib/kubernetes-deploy/version.rb
268
269
  - screenshots/deploy-demo.gif
269
270
  - screenshots/migrate-logs.png
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
- module KubernetesDeploy
3
- class Bugsnag < KubernetesResource
4
- TIMEOUT = 1.minute
5
-
6
- def sync
7
- @secret_found = false
8
- _, _err, st = kubectl.run("get", type, @name)
9
- @found = st.success?
10
- if @found
11
- secrets, _err, _st = kubectl.run("get", "secrets", "--output=name")
12
- @secret_found = secrets.split.any? { |s| s.end_with?("-bugsnag") }
13
- end
14
- @status = @secret_found ? "Available" : "Unknown"
15
- end
16
-
17
- def deploy_succeeded?
18
- @secret_found
19
- end
20
-
21
- def deploy_failed?
22
- false
23
- end
24
-
25
- def exists?
26
- @found
27
- end
28
-
29
- def deploy_method
30
- :replace
31
- end
32
- end
33
- end