class Krane::Pod

Constants

FAILED_PHASE_NAME
TIMEOUT
TRANSIENT_FAILURE_REASONS

Attributes

stream_logs[RW]

Public Class Methods

new(namespace:, context:, definition:, logger:, statsd_tags: nil, parent: nil, deploy_started_at: nil, stream_logs: false) click to toggle source
Calls superclass method Krane::KubernetesResource::new
# File lib/krane/kubernetes_resource/pod.rb, line 14
def initialize(namespace:, context:, definition:, logger:,
  statsd_tags: nil, parent: nil, deploy_started_at: nil, stream_logs: false)
  @parent = parent
  @deploy_started_at = deploy_started_at

  @containers = definition.fetch("spec", {}).fetch("containers", []).map { |c| Container.new(c) }
  unless @containers.present?
    logger.summary.add_paragraph("Rendered template content:\n#{definition.to_yaml}")
    raise FatalDeploymentError, "Template is missing required field spec.containers"
  end
  @containers += definition["spec"].fetch("initContainers", []).map { |c| Container.new(c, init_container: true) }
  @stream_logs = stream_logs
  super(namespace: namespace, context: context, definition: definition,
        logger: logger, statsd_tags: statsd_tags)
end

Public Instance Methods

after_sync() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 42
def after_sync
  if @stream_logs
    logs.print_latest
  elsif unmanaged? && deploy_succeeded?
    logs.print_all
  end
end
deploy_failed?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 63
def deploy_failed?
  failure_message.present?
end
deploy_succeeded?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 55
def deploy_succeeded?
  if unmanaged?
    phase == "Succeeded"
  else
    phase == "Running" && ready?
  end
end
failure_message() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 79
def failure_message
  doomed_containers = @containers.select(&:doomed?)
  if doomed_containers.present?
    container_problems = if unmanaged?
      "The following containers encountered errors:\n"
    else
      "The following containers are in a state that is unlikely to be recoverable:\n"
    end
    doomed_containers.each do |c|
      red_name = ColorizedString.new(c.name).red
      container_problems += "> #{red_name}: #{c.doom_reason}\n"
    end
  end
  "#{phase_failure_message} #{container_problems}".strip.presence
end
fetch_debug_logs() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 95
def fetch_debug_logs
  logs.sync
  logs
end
node_name() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 104
def node_name
  @instance_data.dig('spec', 'nodeName')
end
print_debug_logs?() click to toggle source
status() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 50
def status
  return phase if reason.blank?
  "#{phase} (Reason: #{reason})"
end
sync(_cache) click to toggle source
Calls superclass method Krane::KubernetesResource#sync
# File lib/krane/kubernetes_resource/pod.rb, line 30
def sync(_cache)
  super
  raise_predates_deploy_error if exists? && unmanaged? && !deploy_started?

  if exists?
    logs.sync if unmanaged?
    update_container_statuses(@instance_data["status"])
  else # reset
    @containers.each(&:reset_status)
  end
end
timeout_message() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 67
def timeout_message
  if readiness_probe_failure?
    probe_failure_msgs = @containers.map(&:readiness_fail_reason).compact
    header = "The following containers have not passed their readiness probes on at least one pod:\n"
    header + probe_failure_msgs.join("\n")
  elsif failed_schedule_reason.present?
    "Pod could not be scheduled because #{failed_schedule_reason}"
  else
    STANDARD_TIMEOUT_MESSAGE
  end
end

Private Instance Methods

failed_phase?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 120
def failed_phase?
  phase == FAILED_PHASE_NAME
end
failed_schedule_reason() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 110
def failed_schedule_reason
  if phase == "Pending"
    conditions = @instance_data.dig('status', 'conditions') || []
    unschedulable = conditions.find do |condition|
      condition["type"] == "PodScheduled" && condition["status"] == "False"
    end
    unschedulable&.dig('message')
  end
end
logs() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 143
def logs
  @logs ||= Krane::RemoteLogs.new(
    logger: @logger,
    parent_id: id,
    container_names: @containers.map(&:name),
    namespace: @namespace,
    context: @context
  )
end
phase() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 153
def phase
  @instance_data.dig("status", "phase") || "Unknown"
end
phase_failure_message() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 129
def phase_failure_message
  if failed_phase? && !transient_failure_reason?
    return "Pod status: #{status}."
  end

  return unless unmanaged?

  if terminating?
    "Pod status: Terminating."
  elsif disappeared?
    "Pod status: Disappeared."
  end
end
raise_predates_deploy_error() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 189
    def raise_predates_deploy_error
      example_color = :green
      msg = <<-STRING.strip_heredoc
        Unmanaged pods like #{id} must have unique names on every deploy in order to work as intended.
        The recommended way to achieve this is to include "<%= deployment_id %>" in the pod's name, like this:
          #{ColorizedString.new('kind: Pod').colorize(example_color)}
          #{ColorizedString.new('metadata:').colorize(example_color)}
            #{ColorizedString.new("name: #{@name}-<%= deployment_id %>").colorize(example_color)}
      STRING
      @logger.summary.add_paragraph(msg)
      raise FatalDeploymentError, "#{id} existed before the deploy started"
    end
readiness_probe_failure?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 161
def readiness_probe_failure?
  return false if ready? || unmanaged?
  return false if phase != "Running"
  @containers.any?(&:readiness_fail_reason)
end
ready?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 167
def ready?
  return false unless (status_data = @instance_data["status"])
  ready_condition = status_data.fetch("conditions", []).find { |condition| condition["type"] == "Ready" }
  ready_condition.present? && (ready_condition["status"] == "True")
end
reason() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 157
def reason
  @instance_data.dig('status', 'reason')
end
transient_failure_reason?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 124
def transient_failure_reason?
  return false if unmanaged?
  TRANSIENT_FAILURE_REASONS.include?(reason)
end
unmanaged?() click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 185
def unmanaged?
  @parent.blank?
end
update_container_statuses(status_data) click to toggle source
# File lib/krane/kubernetes_resource/pod.rb, line 173
def update_container_statuses(status_data)
  @containers.each do |c|
    key = c.init_container? ? "initContainerStatuses" : "containerStatuses"
    if status_data.key?(key)
      data = status_data[key].find { |st| st["name"] == c.name }
      c.update_status(data)
    else
      c.reset_status
    end
  end
end