kubernetes-deploy 0.7.8 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c72be87cc8ea1503ae698d2a789ceba24015691e
4
- data.tar.gz: 42985326dfd947a6bb549f595bf5aae4f6296889
3
+ metadata.gz: 13dbeb21aaaa5ab8c43c1f7fb6737ad1a9c2f735
4
+ data.tar.gz: 817ac9680c47685d2ecc9257952830baab8ee4ef
5
5
  SHA512:
6
- metadata.gz: 6a9d5f8e191e7de823812350ba7931dc504b74a8268cbae3f80a39da4dab2aea47265e6a9aaf65d2e400d9477302490d0198450df25460360c1983a484f21e29
7
- data.tar.gz: 89e0078eb5300a7b791a1205154a4c322b3ce2c0370ac20bd70b2fc9efe710c856c2fa0fd463fcbc1cc48e19561cb5f69d646926b60c1f18b385264741f4343f
6
+ metadata.gz: a613afd9f8697c843223f20e70b7c22ec174af2d1f0394e6978d3644900b9713f6d6480c7897074c9cf77d594cdd37ee8dfa5ef55f05d240c69e0e226b61a709
7
+ data.tar.gz: 8247c8ca0ac3fdc7ef2a867e0bb2207741fc86894ad5b7ae80e12f71c29bbecc898be64a94175ca4e57a5f3344ad03976eb36d34f5ee284b52598a8caef81b54
@@ -22,27 +22,15 @@ module KubernetesDeploy
22
22
  If you have reason to believe it will succeed, retry the deploy to continue to monitor the rollout.
23
23
  MSG
24
24
 
25
- def self.for_type(type:, name:, namespace:, context:, file:, logger:)
26
- subclass = case type
27
- when 'cloudsql' then Cloudsql
28
- when 'configmap' then ConfigMap
29
- when 'deployment' then Deployment
30
- when 'pod' then Pod
31
- when 'redis' then Redis
32
- when 'bugsnag' then Bugsnag
33
- when 'ingress' then Ingress
34
- when 'persistentvolumeclaim' then PersistentVolumeClaim
35
- when 'service' then Service
36
- when 'podtemplate' then PodTemplate
37
- when 'poddisruptionbudget' then PodDisruptionBudget
38
- end
39
-
40
- opts = { name: name, namespace: namespace, context: context, file: file, logger: logger }
41
- if subclass
42
- subclass.new(**opts)
25
+ def self.build(namespace:, context:, definition:, logger:)
26
+ opts = { namespace: namespace, context: context, definition: definition, logger: logger }
27
+ if KubernetesDeploy.const_defined?(definition["kind"])
28
+ klass = KubernetesDeploy.const_get(definition["kind"])
29
+ klass.new(**opts)
43
30
  else
44
31
  inst = new(**opts)
45
- inst.tap { |r| r.type = type }
32
+ inst.type = definition["kind"]
33
+ inst
46
34
  end
47
35
  end
48
36
 
@@ -54,19 +42,28 @@ module KubernetesDeploy
54
42
  self.class.timeout
55
43
  end
56
44
 
57
- def initialize(name:, namespace:, context:, file:, logger:)
45
+ def initialize(namespace:, context:, definition:, logger:)
58
46
  # subclasses must also set these if they define their own initializer
59
- @name = name
47
+ @name = definition.fetch("metadata", {})["name"]
48
+ unless @name.present?
49
+ logger.summary.add_paragraph("Rendered template content:\n#{definition.to_yaml}")
50
+ raise FatalDeploymentError, "Template is missing required field metadata.name"
51
+ end
52
+
60
53
  @namespace = namespace
61
54
  @context = context
62
- @file = file
63
55
  @logger = logger
56
+ @definition = definition
64
57
  end
65
58
 
66
59
  def id
67
60
  "#{type}/#{name}"
68
61
  end
69
62
 
63
+ def file_path
64
+ file.path
65
+ end
66
+
70
67
  def sync
71
68
  end
72
69
 
@@ -139,10 +136,11 @@ module KubernetesDeploy
139
136
  if container_logs.blank? || container_logs.values.all?(&:blank?)
140
137
  helpful_info << " - Logs: #{DEBUG_RESOURCE_NOT_FOUND_MESSAGE}"
141
138
  else
142
- helpful_info << " - Logs (last #{LOG_LINE_COUNT} lines shown):"
143
- container_logs.each do |identifier, logs|
144
- logs.split("\n").each do |line|
145
- helpful_info << " [#{identifier}]\t#{line}"
139
+ sorted_logs = container_logs.sort_by { |_, log_lines| log_lines.length }
140
+ sorted_logs.each do |identifier, log_lines|
141
+ helpful_info << " - Logs from container '#{identifier}' (last #{LOG_LINE_COUNT} lines shown):"
142
+ log_lines.each do |line|
143
+ helpful_info << " #{line}"
146
144
  end
147
145
  end
148
146
  end
@@ -241,5 +239,19 @@ module KubernetesDeploy
241
239
  "#{@reason}: #{@message} (#{@count} events)"
242
240
  end
243
241
  end
242
+
243
+ private
244
+
245
+ def file
246
+ @file ||= create_definition_tempfile
247
+ end
248
+
249
+ def create_definition_tempfile
250
+ file = Tempfile.new(["#{type}-#{name}", ".yml"])
251
+ file.write(YAML.dump(@definition))
252
+ file
253
+ ensure
254
+ file.close if file
255
+ end
244
256
  end
245
257
  end
@@ -4,80 +4,84 @@ module KubernetesDeploy
4
4
  TIMEOUT = 5.minutes
5
5
 
6
6
  def sync
7
- json_data, _err, st = kubectl.run("get", type, @name, "--output=json")
7
+ raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
8
8
  @found = st.success?
9
- @rollout_data = {}
10
- @status = nil
11
- @representative_pod = nil
12
- @pods = []
13
9
 
14
10
  if @found
15
- @rollout_data = JSON.parse(json_data)["status"]
16
- .slice("updatedReplicas", "replicas", "availableReplicas", "unavailableReplicas")
17
- @status = @rollout_data.map { |replica_state, num| "#{num} #{replica_state}" }.join(", ")
18
-
19
- pod_list, _err, st = kubectl.run("get", "pods", "-a", "-l", "name=#{name}", "--output=json")
20
- if st.success?
21
- pods_json = JSON.parse(pod_list)["items"]
22
- pods_json.each do |pod_json|
23
- pod_name = pod_json["metadata"]["name"]
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
- )
32
- pod.deploy_started = @deploy_started
33
- pod.interpret_json_data(pod_json)
34
-
35
- if !@representative_pod && pod_probably_new?(pod_json)
36
- @representative_pod = pod
37
- end
38
- @pods << pod
39
- end
40
- end
11
+ deployment_data = JSON.parse(raw_json)
12
+ @latest_rs = find_latest_rs(deployment_data)
13
+ @rollout_data = { "replicas" => 0 }.merge(deployment_data["status"]
14
+ .slice("replicas", "updatedReplicas", "availableReplicas", "unavailableReplicas"))
15
+ @status = @rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
16
+ else # reset
17
+ @latest_rs = nil
18
+ @rollout_data = { "replicas" => 0 }
19
+ @status = nil
41
20
  end
42
21
  end
43
22
 
44
- def fetch_logs
45
- @representative_pod ? @representative_pod.fetch_logs : {}
46
- end
47
-
48
23
  def fetch_events
49
24
  own_events = super
50
- return own_events unless @representative_pod
51
- own_events.merge(@representative_pod.fetch_events)
25
+ return own_events unless @latest_rs.present?
26
+ own_events.merge(@latest_rs.fetch_events)
27
+ end
28
+
29
+ def fetch_logs
30
+ return {} unless @latest_rs.present?
31
+ @latest_rs.fetch_logs
52
32
  end
53
33
 
54
34
  def deploy_succeeded?
55
- return false unless @rollout_data.key?("availableReplicas")
56
- # TODO: this should look at the current replica set's pods too
35
+ return false unless @latest_rs
36
+
37
+ @latest_rs.deploy_succeeded? &&
38
+ @latest_rs.desired_replicas == desired_replicas && # latest RS fully scaled up
57
39
  @rollout_data["updatedReplicas"].to_i == @rollout_data["replicas"].to_i &&
58
40
  @rollout_data["updatedReplicas"].to_i == @rollout_data["availableReplicas"].to_i
59
41
  end
60
42
 
61
43
  def deploy_failed?
62
- # TODO: this should look at the current replica set's pods only or it'll never be true for rolling updates
63
- @pods.present? && @pods.all?(&:deploy_failed?)
44
+ @latest_rs && @latest_rs.deploy_failed?
64
45
  end
65
46
 
66
47
  def deploy_timed_out?
67
- # TODO: this should look at the current replica set's pods only or it'll never be true for rolling updates
68
- super || @pods.present? && @pods.all?(&:deploy_timed_out?)
48
+ super || @latest_rs && @latest_rs.deploy_timed_out?
69
49
  end
70
50
 
71
51
  def exists?
72
52
  @found
73
53
  end
74
54
 
55
+ def desired_replicas
56
+ @definition["spec"]["replicas"].to_i
57
+ end
58
+
75
59
  private
76
60
 
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)
61
+ def find_latest_rs(deployment_data)
62
+ label_string = deployment_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",")
63
+ raw_json, _err, st = kubectl.run("get", "replicasets", "--output=json", "--selector=#{label_string}")
64
+ return unless st.success?
65
+
66
+ all_rs_data = JSON.parse(raw_json)["items"]
67
+ current_revision = deployment_data["metadata"]["annotations"]["deployment.kubernetes.io/revision"]
68
+
69
+ latest_rs_data = all_rs_data.find do |rs|
70
+ rs["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == deployment_data["metadata"]["uid"] } &&
71
+ rs["metadata"]["annotations"]["deployment.kubernetes.io/revision"] == current_revision
72
+ end
73
+ return unless latest_rs_data.present?
74
+
75
+ rs = ReplicaSet.new(
76
+ namespace: namespace,
77
+ context: context,
78
+ definition: latest_rs_data,
79
+ logger: @logger,
80
+ parent: "#{@name.capitalize} deployment",
81
+ deploy_started: @deploy_started
82
+ )
83
+ rs.sync(latest_rs_data)
84
+ rs
81
85
  end
82
86
  end
83
87
  end
@@ -2,52 +2,37 @@
2
2
  module KubernetesDeploy
3
3
  class Pod < KubernetesResource
4
4
  TIMEOUT = 10.minutes
5
- SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError ErrImagePull).freeze
5
+ SUSPICIOUS_CONTAINER_STATES = %w(ImagePullBackOff RunContainerError ErrImagePull CrashLoopBackOff).freeze
6
6
 
7
- def initialize(name:, namespace:, context:, file:, parent: nil, logger:)
8
- @name = name
9
- @namespace = namespace
10
- @context = context
11
- @file = file
7
+ def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started: nil)
12
8
  @parent = parent
13
- @logger = logger
14
- end
15
-
16
- def sync
17
- out, _err, st = kubectl.run("get", type, @name, "-a", "--output=json")
18
- if @found = st.success?
19
- pod_data = JSON.parse(out)
20
- interpret_json_data(pod_data)
21
- else # reset
22
- @status = @phase = nil
23
- @ready = false
24
- @containers = []
9
+ @deploy_started = deploy_started
10
+ @containers = definition.fetch("spec", {}).fetch("containers", {}).map { |c| c["name"] }
11
+ unless @containers.present?
12
+ logger.summary.add_paragraph("Rendered template content:\n#{definition.to_yaml}")
13
+ raise FatalDeploymentError, "Template is missing required field spec.containers"
25
14
  end
26
- display_logs if unmanaged? && deploy_succeeded?
15
+ super(namespace: namespace, context: context, definition: definition, logger: logger)
27
16
  end
28
17
 
29
- def interpret_json_data(pod_data)
30
- @phase = (pod_data["metadata"]["deletionTimestamp"] ? "Terminating" : pod_data["status"]["phase"])
31
- @containers = pod_data["spec"]["containers"].map { |c| c["name"] }
32
-
33
- if @deploy_started && pod_data["status"]["containerStatuses"]
34
- pod_data["status"]["containerStatuses"].each do |status|
35
- waiting_state = status["state"]["waiting"] if status["state"]
36
- reason = waiting_state["reason"] if waiting_state
37
- next unless SUSPICIOUS_CONTAINER_STATES.include?(reason)
38
- @logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
39
- end
18
+ def sync(pod_data = nil)
19
+ if pod_data.blank?
20
+ raw_json, _err, st = kubectl.run("get", type, @name, "-a", "--output=json")
21
+ pod_data = JSON.parse(raw_json) if st.success?
40
22
  end
41
23
 
42
- if @phase == "Failed"
43
- @status = "#{@phase} (Reason: #{pod_data['status']['reason']})"
44
- elsif @phase == "Terminating"
45
- @status = @phase
46
- else
47
- ready_condition = pod_data["status"].fetch("conditions", []).find { |condition| condition["type"] == "Ready" }
48
- @ready = ready_condition.present? && (ready_condition["status"] == "True")
49
- @status = "#{@phase} (Ready: #{@ready})"
24
+ if pod_data.present?
25
+ @found = true
26
+ interpret_pod_status_data(pod_data["status"], pod_data["metadata"]) # sets @phase, @status and @ready
27
+ if @deploy_started
28
+ log_suspicious_states(pod_data["status"].fetch("containerStatuses", []))
29
+ end
30
+ else # reset
31
+ @found = false
32
+ @phase = @status = nil
33
+ @ready = false
50
34
  end
35
+ display_logs if unmanaged? && deploy_succeeded?
51
36
  end
52
37
 
53
38
  def deploy_succeeded?
@@ -63,17 +48,16 @@ module KubernetesDeploy
63
48
  end
64
49
 
65
50
  def exists?
66
- unmanaged? ? @found : true
51
+ @found
67
52
  end
68
53
 
69
54
  # Returns a hash in the following format:
70
55
  # {
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"
56
+ # "app" => ["array of log lines", "received from app container"],
57
+ # "nginx" => ["array of log lines", "received from nginx container"]
73
58
  # }
74
59
  def fetch_logs
75
60
  return {} unless exists? && @containers.present?
76
-
77
61
  @containers.each_with_object({}) do |container_name, container_logs|
78
62
  cmd = [
79
63
  "logs",
@@ -83,12 +67,33 @@ module KubernetesDeploy
83
67
  ]
84
68
  cmd << "--tail=#{LOG_LINE_COUNT}" unless unmanaged?
85
69
  out, _err, _st = kubectl.run(*cmd)
86
- container_logs["#{id}/#{container_name}"] = out
70
+ container_logs[container_name] = out.split("\n")
87
71
  end
88
72
  end
89
73
 
90
74
  private
91
75
 
76
+ def interpret_pod_status_data(status_data, metadata)
77
+ @status = @phase = (metadata["deletionTimestamp"] ? "Terminating" : status_data["phase"])
78
+
79
+ if @phase == "Failed" && status_data['reason'].present?
80
+ @status += " (Reason: #{status_data['reason']})"
81
+ elsif @phase != "Terminating"
82
+ ready_condition = status_data.fetch("conditions", []).find { |condition| condition["type"] == "Ready" }
83
+ @ready = ready_condition.present? && (ready_condition["status"] == "True")
84
+ @status += " (Ready: #{@ready})"
85
+ end
86
+ end
87
+
88
+ def log_suspicious_states(container_statuses)
89
+ container_statuses.each do |status|
90
+ waiting_state = status["state"]["waiting"] if status["state"]
91
+ reason = waiting_state["reason"] if waiting_state
92
+ next unless SUSPICIOUS_CONTAINER_STATES.include?(reason)
93
+ @logger.warn("#{id} has container in state #{reason} (#{waiting_state['message']})")
94
+ end
95
+ end
96
+
92
97
  def unmanaged?
93
98
  @parent.blank?
94
99
  end
@@ -104,13 +109,11 @@ module KubernetesDeploy
104
109
 
105
110
  container_logs.each do |container_identifier, logs|
106
111
  if logs.blank?
107
- @logger.warn("No logs found for #{container_identifier}")
112
+ @logger.warn("No logs found for container '#{container_identifier}'")
108
113
  else
109
114
  @logger.blank_line
110
- @logger.info("Logs from #{container_identifier}:")
111
- logs.split("\n").each do |line|
112
- @logger.info("[#{container_identifier}]\t#{line}")
113
- end
115
+ @logger.info("Logs from #{id} container '#{container_identifier}':")
116
+ logs.each { |line| @logger.info("\t#{line}") }
114
117
  @logger.blank_line
115
118
  end
116
119
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ module KubernetesDeploy
3
+ class ReplicaSet < KubernetesResource
4
+ TIMEOUT = 5.minutes
5
+
6
+ def initialize(namespace:, context:, definition:, logger:, parent: nil, deploy_started: nil)
7
+ @parent = parent
8
+ @deploy_started = deploy_started
9
+ @rollout_data = { "replicas" => 0 }
10
+ @pods = []
11
+ super(namespace: namespace, context: context, definition: definition, logger: logger)
12
+ end
13
+
14
+ def sync(rs_data = nil)
15
+ if rs_data.blank?
16
+ raw_json, _err, st = kubectl.run("get", type, @name, "--output=json")
17
+ rs_data = JSON.parse(raw_json) if st.success?
18
+ end
19
+
20
+ if rs_data.present?
21
+ @found = true
22
+ @rollout_data = { "replicas" => 0 }.merge(rs_data["status"]
23
+ .slice("replicas", "availableReplicas", "readyReplicas"))
24
+ @status = @rollout_data.map { |state_replicas, num| "#{num} #{state_replicas.chop.pluralize(num)}" }.join(", ")
25
+ @pods = find_pods(rs_data)
26
+ else # reset
27
+ @found = false
28
+ @rollout_data = { "replicas" => 0 }
29
+ @status = nil
30
+ @pods = []
31
+ end
32
+ end
33
+
34
+ def deploy_succeeded?
35
+ @rollout_data["replicas"].to_i == @rollout_data["availableReplicas"].to_i &&
36
+ @rollout_data["replicas"].to_i == @rollout_data["readyReplicas"].to_i
37
+ end
38
+
39
+ def deploy_failed?
40
+ @pods.present? && @pods.all?(&:deploy_failed?)
41
+ end
42
+
43
+ def deploy_timed_out?
44
+ super || @pods.present? && @pods.all?(&:deploy_timed_out?)
45
+ end
46
+
47
+ def exists?
48
+ @found
49
+ end
50
+
51
+ def desired_replicas
52
+ @definition["spec"]["replicas"].to_i
53
+ end
54
+
55
+ def fetch_events
56
+ own_events = super
57
+ return own_events unless @pods.present?
58
+ own_events.merge(@pods.first.fetch_events)
59
+ end
60
+
61
+ def fetch_logs
62
+ container_names.each_with_object({}) do |container_name, container_logs|
63
+ out, _err, _st = kubectl.run(
64
+ "logs",
65
+ id,
66
+ "--container=#{container_name}",
67
+ "--since-time=#{@deploy_started.to_datetime.rfc3339}",
68
+ "--tail=#{LOG_LINE_COUNT}"
69
+ )
70
+ container_logs[container_name] = out.split("\n")
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def unmanaged?
77
+ @parent.blank?
78
+ end
79
+
80
+ def container_names
81
+ @definition["spec"]["template"]["spec"]["containers"].map { |c| c["name"] }
82
+ end
83
+
84
+ def find_pods(rs_data)
85
+ label_string = rs_data["spec"]["selector"]["matchLabels"].map { |k, v| "#{k}=#{v}" }.join(",")
86
+ raw_json, _err, st = kubectl.run("get", "pods", "-a", "--output=json", "--selector=#{label_string}")
87
+ return [] unless st.success?
88
+
89
+ all_pods = JSON.parse(raw_json)["items"]
90
+ all_pods.each_with_object([]) do |pod_data, relevant_pods|
91
+ next unless pod_data["metadata"]["ownerReferences"].any? { |ref| ref["uid"] == rs_data["metadata"]["uid"] }
92
+ pod = Pod.new(
93
+ namespace: namespace,
94
+ context: context,
95
+ definition: pod_data,
96
+ logger: @logger,
97
+ parent: "#{@name.capitalize} replica set",
98
+ deploy_started: @deploy_started
99
+ )
100
+ pod.sync(pod_data)
101
+ relevant_pods << pod
102
+ end
103
+ end
104
+ end
105
+ end
@@ -16,7 +16,11 @@ module KubernetesDeploy
16
16
  end
17
17
 
18
18
  def deploy_succeeded?
19
- @num_endpoints > 0
19
+ if exposes_zero_replica_deployment?
20
+ @num_endpoints == 0
21
+ else
22
+ @num_endpoints > 0
23
+ end
20
24
  end
21
25
 
22
26
  def deploy_failed?
@@ -33,5 +37,23 @@ module KubernetesDeploy
33
37
  def exists?
34
38
  @found
35
39
  end
40
+
41
+ private
42
+
43
+ def exposes_zero_replica_deployment?
44
+ related_deployment_replicas && related_deployment_replicas == 0
45
+ end
46
+
47
+ def related_deployment_replicas
48
+ @related_deployment_replicas ||= begin
49
+ selector = @definition["spec"]["selector"].map { |k, v| "#{k}=#{v}" }.join(",")
50
+ raw_json, _err, st = kubectl.run("get", "deployments", "--selector=#{selector}", "--output=json")
51
+ return unless st.success?
52
+
53
+ deployments = JSON.parse(raw_json)["items"]
54
+ return unless deployments.length == 1
55
+ deployments.first["spec"]["replicas"].to_i
56
+ end
57
+ end
36
58
  end
37
59
  end
@@ -70,7 +70,8 @@ module KubernetesDeploy
70
70
 
71
71
  def wait_for_rollout(kubeclient_resources)
72
72
  resources = kubeclient_resources.map do |d|
73
- Deployment.new(name: d.metadata.name, namespace: @namespace, context: @context, file: nil, logger: @logger)
73
+ definition = d.to_h.deep_stringify_keys
74
+ Deployment.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
74
75
  end
75
76
  watcher = ResourceWatcher.new(resources, logger: @logger)
76
77
  watcher.run
@@ -18,6 +18,7 @@ require 'kubernetes-deploy/kubernetes_resource'
18
18
  pod_template
19
19
  bugsnag
20
20
  pod_disruption_budget
21
+ replica_set
21
22
  ).each do |subresource|
22
23
  require "kubernetes-deploy/kubernetes_resource/#{subresource}"
23
24
  end
@@ -181,14 +182,13 @@ module KubernetesDeploy
181
182
  def find_bad_file_from_kubectl_output(stderr)
182
183
  # Output example:
183
184
  # Error from server (BadRequest): error when creating "/path/to/configmap-gqq5oh.yml20170411-33615-t0t3m":
184
- match = stderr.match(%r{BadRequest.*"(?<path>\/\S+\.ya?ml\S+)"})
185
+ match = stderr.match(%r{BadRequest.*"(?<path>\/\S+\.ya?ml\S*)"})
185
186
  return unless match
186
187
 
187
188
  path = match[:path]
188
189
  if path.present? && File.file?(path)
189
- suspicious_file = File.read(path)
190
+ File.read(path)
190
191
  end
191
- [File.basename(path, ".*"), suspicious_file]
192
192
  end
193
193
 
194
194
  def deploy_has_priority_resources?(resources)
@@ -217,48 +217,38 @@ module KubernetesDeploy
217
217
  Dir.foreach(@template_dir) do |filename|
218
218
  next unless filename.end_with?(".yml.erb", ".yml", ".yaml", ".yaml.erb")
219
219
 
220
- split_templates(filename) do |tempfile|
221
- resource_id = discover_resource_via_dry_run(tempfile)
222
- type, name = resource_id.split("/", 2) # e.g. "pod/web-198612918-dzvfb"
223
- resources << KubernetesResource.for_type(type: type, name: name, namespace: @namespace, context: @context,
224
- file: tempfile, logger: @logger)
225
- @logger.info " - #{resource_id}"
220
+ split_templates(filename) do |r_def|
221
+ r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def)
222
+ validate_template_via_dry_run(r.file_path, filename)
223
+ resources << r
224
+ @logger.info " - #{r.id}"
226
225
  end
227
226
  end
228
227
  resources
229
228
  end
230
229
 
231
- def discover_resource_via_dry_run(tempfile)
232
- command = ["create", "-f", tempfile.path, "--dry-run", "--output=name"]
233
- resource_id, err, st = kubectl.run(*command, log_failure: false)
230
+ def validate_template_via_dry_run(file_path, original_filename)
231
+ command = ["create", "-f", file_path, "--dry-run", "--output=name"]
232
+ _, err, st = kubectl.run(*command, log_failure: false)
233
+ return if st.success?
234
234
 
235
- unless st.success?
236
- debug_msg = <<-DEBUG_MSG.strip_heredoc
237
- This usually means template '#{File.basename(tempfile.path, '.*')}' is not a valid Kubernetes template.
238
-
239
- Error from kubectl:
240
- #{err}
241
-
242
- Rendered template content:
243
- DEBUG_MSG
244
- debug_msg += File.read(tempfile.path)
245
- @logger.summary.add_paragraph(debug_msg)
235
+ debug_msg = <<-DEBUG_MSG.strip_heredoc
236
+ This usually means template '#{original_filename}' is not a valid Kubernetes template.
237
+ Error from kubectl:
238
+ #{err}
239
+ Rendered template content:
240
+ DEBUG_MSG
241
+ debug_msg += File.read(file_path)
242
+ @logger.summary.add_paragraph(debug_msg)
246
243
 
247
- raise FatalDeploymentError, "Kubectl dry run failed (command: #{Shellwords.join(command)})"
248
- end
249
- resource_id
244
+ raise FatalDeploymentError, "Kubectl dry run failed (command: #{Shellwords.join(command)})"
250
245
  end
251
246
 
252
247
  def split_templates(filename)
253
248
  file_content = File.read(File.join(@template_dir, filename))
254
249
  rendered_content = render_template(filename, file_content)
255
250
  YAML.load_stream(rendered_content) do |doc|
256
- next if doc.blank?
257
-
258
- f = Tempfile.new(filename)
259
- f.write(YAML.dump(doc))
260
- f.close
261
- yield f
251
+ yield doc unless doc.blank?
262
252
  end
263
253
  rescue Psych::SyntaxError => e
264
254
  debug_msg = <<-INFO.strip_heredoc
@@ -273,23 +263,14 @@ module KubernetesDeploy
273
263
  end
274
264
 
275
265
  def record_apply_failure(err)
276
- file_name, file_content = find_bad_file_from_kubectl_output(err)
277
- if file_name
278
- debug_msg = <<-HELPFUL_MESSAGE.strip_heredoc
279
- This usually means your template named '#{file_name}' is invalid.
280
-
281
- Error from kubectl:
282
- #{err}
283
-
284
- Rendered template content:
285
- HELPFUL_MESSAGE
286
- debug_msg += file_content || "Failed to read file"
287
- else
288
- debug_msg = <<-FALLBACK_MSG
289
- This usually means one of your templates is invalid, but we were unable to automatically identify which one.
290
- Please inspect the error message from kubectl:
291
- #{err}
292
- FALLBACK_MSG
266
+ file_content = find_bad_file_from_kubectl_output(err)
267
+ debug_msg = <<-HELPFUL_MESSAGE.strip_heredoc
268
+ This usually means one of your templates is invalid.
269
+ Error from kubectl:
270
+ #{err}
271
+ HELPFUL_MESSAGE
272
+ if file_content
273
+ debug_msg += "Rendered template content:\n#{file_content}"
293
274
  end
294
275
 
295
276
  @logger.summary.add_paragraph(debug_msg)
@@ -376,9 +357,9 @@ module KubernetesDeploy
376
357
  r.deploy_started = Time.now.utc
377
358
  case r.deploy_method
378
359
  when :replace
379
- _, _, replace_st = kubectl.run("replace", "-f", r.file.path, log_failure: false)
360
+ _, _, replace_st = kubectl.run("replace", "-f", r.file_path, log_failure: false)
380
361
  when :replace_force
381
- _, _, replace_st = kubectl.run("replace", "--force", "-f", r.file.path, log_failure: false)
362
+ _, _, replace_st = kubectl.run("replace", "--force", "-f", r.file_path, log_failure: false)
382
363
  else
383
364
  # Fail Fast! This is a programmer mistake.
384
365
  raise ArgumentError, "Unexpected deploy method! (#{r.deploy_method.inspect})"
@@ -386,7 +367,7 @@ module KubernetesDeploy
386
367
 
387
368
  next if replace_st.success?
388
369
  # it doesn't exist so we can't replace it
389
- _, err, create_st = kubectl.run("create", "-f", r.file.path, log_failure: false)
370
+ _, err, create_st = kubectl.run("create", "-f", r.file_path, log_failure: false)
390
371
 
391
372
  next if create_st.success?
392
373
  raise FatalDeploymentError, <<-MSG.strip_heredoc
@@ -405,7 +386,7 @@ module KubernetesDeploy
405
386
  command = ["apply"]
406
387
  resources.each do |r|
407
388
  @logger.info("- #{r.id} (timeout: #{r.timeout}s)") if resources.length > 1
408
- command.push("-f", r.file.path)
389
+ command.push("-f", r.file_path)
409
390
  r.deploy_started = Time.now.utc
410
391
  end
411
392
 
@@ -1,3 +1,3 @@
1
1
  module KubernetesDeploy
2
- VERSION = "0.7.8"
2
+ VERSION = "0.7.9"
3
3
  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.7.8
4
+ version: 0.7.9
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: 2017-06-27 00:00:00.000000000 Z
12
+ date: 2017-07-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -206,6 +206,7 @@ files:
206
206
  - lib/kubernetes-deploy/kubernetes_resource/pod_disruption_budget.rb
207
207
  - lib/kubernetes-deploy/kubernetes_resource/pod_template.rb
208
208
  - lib/kubernetes-deploy/kubernetes_resource/redis.rb
209
+ - lib/kubernetes-deploy/kubernetes_resource/replica_set.rb
209
210
  - lib/kubernetes-deploy/kubernetes_resource/service.rb
210
211
  - lib/kubernetes-deploy/resource_watcher.rb
211
212
  - lib/kubernetes-deploy/restart_task.rb