kubernetes-deploy 0.6.6 → 0.7.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/exe/kubernetes-deploy +21 -13
  3. data/exe/kubernetes-restart +7 -4
  4. data/exe/kubernetes-run +14 -10
  5. data/kubernetes-deploy.gemspec +1 -0
  6. data/lib/kubernetes-deploy.rb +3 -2
  7. data/lib/kubernetes-deploy/deferred_summary_logging.rb +87 -0
  8. data/lib/kubernetes-deploy/ejson_secret_provisioner.rb +18 -20
  9. data/lib/kubernetes-deploy/formatted_logger.rb +42 -0
  10. data/lib/kubernetes-deploy/kubectl.rb +21 -8
  11. data/lib/kubernetes-deploy/kubernetes_resource.rb +111 -52
  12. data/lib/kubernetes-deploy/kubernetes_resource/bugsnag.rb +3 -11
  13. data/lib/kubernetes-deploy/kubernetes_resource/cloudsql.rb +7 -14
  14. data/lib/kubernetes-deploy/kubernetes_resource/config_map.rb +5 -9
  15. data/lib/kubernetes-deploy/kubernetes_resource/deployment.rb +31 -14
  16. data/lib/kubernetes-deploy/kubernetes_resource/ingress.rb +1 -13
  17. data/lib/kubernetes-deploy/kubernetes_resource/persistent_volume_claim.rb +2 -9
  18. data/lib/kubernetes-deploy/kubernetes_resource/pod.rb +48 -22
  19. data/lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb +5 -9
  20. data/lib/kubernetes-deploy/kubernetes_resource/pod_template.rb +5 -9
  21. data/lib/kubernetes-deploy/kubernetes_resource/redis.rb +9 -15
  22. data/lib/kubernetes-deploy/kubernetes_resource/service.rb +9 -10
  23. data/lib/kubernetes-deploy/resource_watcher.rb +22 -10
  24. data/lib/kubernetes-deploy/restart_task.rb +12 -7
  25. data/lib/kubernetes-deploy/runner.rb +163 -110
  26. data/lib/kubernetes-deploy/runner_task.rb +22 -19
  27. data/lib/kubernetes-deploy/version.rb +1 -1
  28. metadata +18 -4
  29. data/lib/kubernetes-deploy/logger.rb +0 -45
  30. data/lib/kubernetes-deploy/ui_helpers.rb +0 -19
@@ -3,23 +3,15 @@ module KubernetesDeploy
3
3
  class Bugsnag < KubernetesResource
4
4
  TIMEOUT = 1.minute
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- @secret_found = false
12
- end
13
-
14
6
  def sync
15
- _, _err, st = run_kubectl("get", type, @name)
7
+ @secret_found = false
8
+ _, _err, st = kubectl.run("get", type, @name)
16
9
  @found = st.success?
17
10
  if @found
18
- secrets, _err, _st = run_kubectl("get", "secrets", "--output=name")
11
+ secrets, _err, _st = kubectl.run("get", "secrets", "--output=name")
19
12
  @secret_found = secrets.split.any? { |s| s.end_with?("-bugsnag") }
20
13
  end
21
14
  @status = @secret_found ? "Available" : "Unknown"
22
- log_status
23
15
  end
24
16
 
25
17
  def deploy_succeeded?
@@ -3,27 +3,20 @@ module KubernetesDeploy
3
3
  class Cloudsql < KubernetesResource
4
4
  TIMEOUT = 10.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @found = st.success?
16
- @status = if cloudsql_proxy_deployment_exists? && mysql_service_exists?
9
+ @deployment_exists = cloudsql_proxy_deployment_exists?
10
+ @service_exists = mysql_service_exists?
11
+ @status = if @deployment_exists && @service_exists
17
12
  "Provisioned"
18
13
  else
19
14
  "Unknown"
20
15
  end
21
-
22
- log_status
23
16
  end
24
17
 
25
18
  def deploy_succeeded?
26
- cloudsql_proxy_deployment_exists? && mysql_service_exists?
19
+ @service_exists && @deployment_exists
27
20
  end
28
21
 
29
22
  def deploy_failed?
@@ -41,7 +34,7 @@ module KubernetesDeploy
41
34
  private
42
35
 
43
36
  def cloudsql_proxy_deployment_exists?
44
- deployment, _err, st = run_kubectl("get", "deployments", "cloudsql-proxy", "-o=json")
37
+ deployment, _err, st = kubectl.run("get", "deployments", "cloudsql-proxy", "-o=json")
45
38
 
46
39
  if st.success?
47
40
  parsed = JSON.parse(deployment)
@@ -56,7 +49,7 @@ module KubernetesDeploy
56
49
  end
57
50
 
58
51
  def mysql_service_exists?
59
- service, _err, st = run_kubectl("get", "services", "mysql", "-o=json")
52
+ service, _err, st = kubectl.run("get", "services", "mysql", "-o=json")
60
53
 
61
54
  if st.success?
62
55
  parsed = JSON.parse(service)
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class ConfigMap < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @status = st.success? ? "Available" : "Unknown"
16
9
  @found = st.success?
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -25,6 +17,10 @@ module KubernetesDeploy
25
17
  false
26
18
  end
27
19
 
20
+ def timeout_message
21
+ UNUSUAL_FAILURE_MESSAGE
22
+ end
23
+
28
24
  def exists?
29
25
  @found
30
26
  end
@@ -3,39 +3,52 @@ module KubernetesDeploy
3
3
  class Deployment < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- json_data, _err, st = run_kubectl("get", type, @name, "--output=json")
7
+ json_data, _err, st = kubectl.run("get", type, @name, "--output=json")
15
8
  @found = st.success?
16
9
  @rollout_data = {}
17
10
  @status = nil
11
+ @representative_pod = nil
18
12
  @pods = []
19
13
 
20
14
  if @found
21
15
  @rollout_data = JSON.parse(json_data)["status"]
22
16
  .slice("updatedReplicas", "replicas", "availableReplicas", "unavailableReplicas")
23
- @status, _err, _ = run_kubectl("rollout", "status", type, @name, "--watch=false") if @deploy_started
17
+ @status = @rollout_data.map { |replica_state, num| "#{num} #{replica_state}" }.join(", ")
24
18
 
25
- pod_list, _err, st = run_kubectl("get", "pods", "-a", "-l", "name=#{name}", "--output=json")
19
+ pod_list, _err, st = kubectl.run("get", "pods", "-a", "-l", "name=#{name}", "--output=json")
26
20
  if st.success?
27
21
  pods_json = JSON.parse(pod_list)["items"]
28
22
  pods_json.each do |pod_json|
29
23
  pod_name = pod_json["metadata"]["name"]
30
- pod = Pod.new(pod_name, namespace, context, nil, parent: "#{@name.capitalize} deployment")
24
+ pod = Pod.new(
25
+ name: pod_name,
26
+ namespace: namespace,
27
+ context: context,
28
+ file: nil,
29
+ parent: "#{@name.capitalize} deployment",
30
+ logger: @logger
31
+ )
31
32
  pod.deploy_started = @deploy_started
32
33
  pod.interpret_json_data(pod_json)
34
+
35
+ if !@representative_pod && pod_probably_new?(pod_json)
36
+ @representative_pod = pod
37
+ end
33
38
  @pods << pod
34
39
  end
35
40
  end
36
41
  end
42
+ end
37
43
 
38
- log_status
44
+ def fetch_logs
45
+ @representative_pod ? @representative_pod.fetch_logs : {}
46
+ end
47
+
48
+ def fetch_events
49
+ own_events = super
50
+ return own_events unless @representative_pod
51
+ own_events.merge(@representative_pod.fetch_events)
39
52
  end
40
53
 
41
54
  def deploy_succeeded?
@@ -59,8 +72,12 @@ module KubernetesDeploy
59
72
  @found
60
73
  end
61
74
 
62
- def status_data
63
- super.merge(replicas: @rollout_data, num_pods: @pods.length)
75
+ private
76
+
77
+ def pod_probably_new?(pod_json)
78
+ return false unless @deploy_started
79
+ # Shitty, brittle workaround to identify a pod from the new ReplicaSet before implementing ReplicaSet awareness
80
+ Time.parse(pod_json["metadata"]["creationTimestamp"]) >= (@deploy_started - 30.seconds)
64
81
  end
65
82
  end
66
83
  end
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class Ingress < KubernetesResource
4
4
  TIMEOUT = 30.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @status = st.success? ? "Created" : "Unknown"
16
9
  @found = st.success?
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -28,9 +20,5 @@ module KubernetesDeploy
28
20
  def exists?
29
21
  @found
30
22
  end
31
-
32
- def group_name
33
- "Ingresses"
34
- end
35
23
  end
36
24
  end
@@ -3,17 +3,10 @@ module KubernetesDeploy
3
3
  class PersistentVolumeClaim < KubernetesResource
4
4
  TIMEOUT = 5.minutes
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- @status, _err, st = run_kubectl("get", type, @name, "--output=jsonpath={.status.phase}")
7
+ out, _err, st = kubectl.run("get", type, @name, "--output=jsonpath={.status.phase}")
15
8
  @found = st.success?
16
- log_status
9
+ @status = out if @found
17
10
  end
18
11
 
19
12
  def deploy_succeeded?
@@ -2,19 +2,19 @@
2
2
  module KubernetesDeploy
3
3
  class Pod < KubernetesResource
4
4
  TIMEOUT = 10.minutes
5
- SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError).freeze
5
+ SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError ErrImagePull).freeze
6
6
 
7
- def initialize(name, namespace, context, file, parent: nil)
7
+ def initialize(name:, namespace:, context:, file:, parent: nil, logger:)
8
8
  @name = name
9
9
  @namespace = namespace
10
10
  @context = context
11
11
  @file = file
12
12
  @parent = parent
13
- @bare = !@parent
13
+ @logger = logger
14
14
  end
15
15
 
16
16
  def sync
17
- out, _err, st = run_kubectl("get", type, @name, "-a", "--output=json")
17
+ out, _err, st = kubectl.run("get", type, @name, "-a", "--output=json")
18
18
  if @found = st.success?
19
19
  pod_data = JSON.parse(out)
20
20
  interpret_json_data(pod_data)
@@ -23,8 +23,7 @@ module KubernetesDeploy
23
23
  @ready = false
24
24
  @containers = []
25
25
  end
26
- log_status
27
- display_logs if @bare && deploy_finished?
26
+ display_logs if unmanaged? && deploy_succeeded?
28
27
  end
29
28
 
30
29
  def interpret_json_data(pod_data)
@@ -36,7 +35,7 @@ module KubernetesDeploy
36
35
  waiting_state = status["state"]["waiting"] if status["state"]
37
36
  reason = waiting_state["reason"] if waiting_state
38
37
  next unless SUSPICIOUS_CONTAINER_STATES.include?(reason)
39
- KubernetesDeploy.logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
38
+ @logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
40
39
  end
41
40
  end
42
41
 
@@ -52,7 +51,7 @@ module KubernetesDeploy
52
51
  end
53
52
 
54
53
  def deploy_succeeded?
55
- if @bare
54
+ if unmanaged?
56
55
  @phase == "Succeeded"
57
56
  else
58
57
  @phase == "Running" && @ready
@@ -64,30 +63,57 @@ module KubernetesDeploy
64
63
  end
65
64
 
66
65
  def exists?
67
- @bare ? @found : true
66
+ unmanaged? ? @found : true
68
67
  end
69
68
 
70
- def group_name
71
- @bare ? "Bare pods" : @parent
72
- end
73
-
74
- private
75
-
76
- def display_logs
77
- return {} unless exists? && @containers.present? && !@already_displayed
69
+ # Returns a hash in the following format:
70
+ # {
71
+ # "pod/web-1/app-container" => "giant blob of logs\nas a single string"
72
+ # "pod/web-1/nginx-container" => "another giant blob of logs\nas a single string"
73
+ # }
74
+ def fetch_logs
75
+ return {} unless exists? && @containers.present?
78
76
 
79
- @containers.each do |container_name|
80
- out, _err, st = run_kubectl(
77
+ @containers.each_with_object({}) do |container_name, container_logs|
78
+ out, _err, _st = kubectl.run(
81
79
  "logs",
82
80
  @name,
83
81
  "--timestamps=true",
84
82
  "--since-time=#{@deploy_started.to_datetime.rfc3339}"
85
83
  )
86
- next unless st.success? && out.present?
84
+ container_logs["#{id}/#{container_name}"] = out
85
+ end
86
+ end
87
+
88
+ private
87
89
 
88
- KubernetesDeploy.logger.info "Logs from #{id} container #{container_name}:\x1b[0m \n#{out}\n"
89
- @already_displayed = true
90
+ def unmanaged?
91
+ @parent.blank?
92
+ end
93
+
94
+ def display_logs
95
+ return if @already_displayed
96
+ container_logs = fetch_logs
97
+
98
+ if container_logs.empty?
99
+ @logger.warn("No logs found for pod #{id}")
100
+ return
90
101
  end
102
+
103
+ container_logs.each do |container_identifier, logs|
104
+ if logs.blank?
105
+ @logger.warn("No logs found for #{container_identifier}")
106
+ else
107
+ @logger.blank_line
108
+ @logger.info("Logs from #{container_identifier}:")
109
+ logs.split("\n").each do |line|
110
+ @logger.info("[#{container_identifier}]\t#{line}")
111
+ end
112
+ @logger.blank_line
113
+ end
114
+ end
115
+
116
+ @already_displayed = true
91
117
  end
92
118
  end
93
119
  end
@@ -3,18 +3,10 @@ module KubernetesDeploy
3
3
  class PodDisruptionBudget < KubernetesResource
4
4
  TIMEOUT = 10.seconds
5
5
 
6
- def initialize(name, namespace, context, file)
7
- @name = name
8
- @namespace = namespace
9
- @context = context
10
- @file = file
11
- end
12
-
13
6
  def sync
14
- _, _err, st = run_kubectl("get", type, @name)
7
+ _, _err, st = kubectl.run("get", type, @name)
15
8
  @found = st.success?
16
9
  @status = @found ? "Available" : "Unknown"
17
- log_status
18
10
  end
19
11
 
20
12
  def deploy_succeeded?
@@ -26,6 +18,10 @@ module KubernetesDeploy
26
18
  :replace_force
27
19
  end
28
20
 
21
+ def timeout_message
22
+ UNUSUAL_FAILURE_MESSAGE
23
+ end
24
+
29
25
  def exists?
30
26
  @found
31
27
  end
@@ -1,18 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  module KubernetesDeploy
3
3
  class PodTemplate < KubernetesResource
4
- def initialize(name, namespace, context, file)
5
- @name = name
6
- @namespace = namespace
7
- @context = context
8
- @file = file
9
- end
10
-
11
4
  def sync
12
- _, _err, st = run_kubectl("get", type, @name)
5
+ _, _err, st = kubectl.run("get", type, @name)
13
6
  @status = st.success? ? "Available" : "Unknown"
14
7
  @found = st.success?
15
- log_status
16
8
  end
17
9
 
18
10
  def deploy_succeeded?
@@ -23,6 +15,10 @@ module KubernetesDeploy
23
15
  false
24
16
  end
25
17
 
18
+ def timeout_message
19
+ UNUSUAL_FAILURE_MESSAGE
20
+ end
21
+
26
22
  def exists?
27
23
  @found
28
24
  end
@@ -4,27 +4,21 @@ module KubernetesDeploy
4
4
  TIMEOUT = 5.minutes
5
5
  UUID_ANNOTATION = "redis.stable.shopify.io/owner_uid"
6
6
 
7
- def initialize(name, namespace, context, file)
8
- @name = name
9
- @namespace = namespace
10
- @context = context
11
- @file = file
12
- end
13
-
14
7
  def sync
15
- _, _err, st = run_kubectl("get", type, @name)
8
+ _, _err, st = kubectl.run("get", type, @name)
16
9
  @found = st.success?
17
- @status = if redis_deployment_exists? && redis_service_exists?
10
+ @deployment_exists = redis_deployment_exists?
11
+ @service_exists = redis_service_exists?
12
+
13
+ @status = if @deployment_exists && @service_exists
18
14
  "Provisioned"
19
15
  else
20
16
  "Unknown"
21
17
  end
22
-
23
- log_status
24
18
  end
25
19
 
26
20
  def deploy_succeeded?
27
- redis_deployment_exists? && redis_service_exists?
21
+ @deployment_exists && @service_exists
28
22
  end
29
23
 
30
24
  def deploy_failed?
@@ -42,7 +36,7 @@ module KubernetesDeploy
42
36
  private
43
37
 
44
38
  def redis_deployment_exists?
45
- deployment, _err, st = run_kubectl("get", "deployments", "redis-#{redis_resource_uuid}", "-o=json")
39
+ deployment, _err, st = kubectl.run("get", "deployments", "redis-#{redis_resource_uuid}", "-o=json")
46
40
 
47
41
  if st.success?
48
42
  parsed = JSON.parse(deployment)
@@ -57,7 +51,7 @@ module KubernetesDeploy
57
51
  end
58
52
 
59
53
  def redis_service_exists?
60
- service, _err, st = run_kubectl("get", "services", "redis-#{redis_resource_uuid}", "-o=json")
54
+ service, _err, st = kubectl.run("get", "services", "redis-#{redis_resource_uuid}", "-o=json")
61
55
 
62
56
  if st.success?
63
57
  parsed = JSON.parse(service)
@@ -73,7 +67,7 @@ module KubernetesDeploy
73
67
  def redis_resource_uuid
74
68
  return @redis_resource_uuid if defined?(@redis_resource_uuid) && @redis_resource_uuid
75
69
 
76
- redis, _err, st = run_kubectl("get", "redises", @name, "-o=json")
70
+ redis, _err, st = kubectl.run("get", "redises", @name, "-o=json")
77
71
  if st.success?
78
72
  parsed = JSON.parse(redis)
79
73