class Krane::RestartTask

Restart the pods in one or more deployments

Constants

ANNOTATION
HTTP_OK_RANGE

Attributes

task_config[R]

Public Class Methods

new(context:, namespace:, logger: nil, global_timeout: nil, kubeconfig: nil) click to toggle source

Initializes the restart task

@param context [String] Kubernetes context / cluster (required) @param namespace [String] Kubernetes namespace (required) @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger) @param global_timeout [Integer] Timeout in seconds

# File lib/krane/restart_task.rb, line 35
def initialize(context:, namespace:, logger: nil, global_timeout: nil, kubeconfig: nil)
  @logger = logger || Krane::FormattedLogger.build(namespace, context)
  @task_config = Krane::TaskConfig.new(context, namespace, @logger, kubeconfig)
  @context = context
  @namespace = namespace
  @global_timeout = global_timeout
end

Public Instance Methods

perform(**args)
Alias for: run
perform!(deployments: nil, selector: nil, verify_result: true)
Alias for: run!
run(**args) click to toggle source

Runs the task, returning a boolean representing success or failure

@return [Boolean]

# File lib/krane/restart_task.rb, line 46
def run(**args)
  perform!(**args)
  true
rescue FatalDeploymentError
  false
end
Also aliased as: perform
run!(deployments: nil, selector: nil, verify_result: true) click to toggle source

Runs the task, raising exceptions in case of issues

@param deployments [Array<String>] Array of workload names to restart @param selector [Hash] Selector(s) parsed by Krane::LabelSelector @param verify_result [Boolean] Wait for completion and verify success

@return [nil]

# File lib/krane/restart_task.rb, line 61
def run!(deployments: nil, selector: nil, verify_result: true)
  start = Time.now.utc
  @logger.reset

  @logger.phase_heading("Initializing restart")
  verify_config!
  deployments = identify_target_deployments(deployments, selector: selector)

  @logger.phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
  patch_kubeclient_deployments(deployments)

  if verify_result
    @logger.phase_heading("Waiting for rollout")
    resources = build_watchables(deployments, start)
    verify_restart(resources)
  else
    warning = "Result verification is disabled for this task"
    @logger.summary.add_paragraph(ColorizedString.new(warning).yellow)
  end
  StatsD.client.distribution('restart.duration', StatsD.duration(start), tags: tags('success', deployments))
  @logger.print_summary(:success)
rescue DeploymentTimeoutError
  StatsD.client.distribution('restart.duration', StatsD.duration(start), tags: tags('timeout', deployments))
  @logger.print_summary(:timed_out)
  raise
rescue FatalDeploymentError => error
  StatsD.client.distribution('restart.duration', StatsD.duration(start), tags: tags('failure', deployments))
  @logger.summary.add_action(error.message) if error.message != error.class.to_s
  @logger.print_summary(:failure)
  raise
end
Also aliased as: perform!

Private Instance Methods

apps_v1_kubeclient() click to toggle source
# File lib/krane/restart_task.rb, line 212
def apps_v1_kubeclient
  @apps_v1_kubeclient ||= kubeclient_builder.build_apps_v1_kubeclient(@context)
end
build_patch_payload(deployment) click to toggle source
# File lib/krane/restart_task.rb, line 174
def build_patch_payload(deployment)
  containers = deployment.spec.template.spec.containers
  {
    spec: {
      template: {
        spec: {
          containers: containers.map do |container|
            {
              name: container.name,
              env: [{ name: "RESTARTED_AT", value: Time.now.to_i.to_s }],
            }
          end,
        },
      },
    },
  }
end
build_watchables(kubeclient_resources, started) click to toggle source
# File lib/krane/restart_task.rb, line 134
def build_watchables(kubeclient_resources, started)
  kubeclient_resources.map do |d|
    definition = d.to_h.deep_stringify_keys
    r = Deployment.new(namespace: @namespace, context: @context, definition: definition, logger: @logger)
    r.deploy_started_at = started # we don't care what happened to the resource before the restart cmd ran
    r
  end
end
fetch_deployments(list) click to toggle source
# File lib/krane/restart_task.rb, line 162
def fetch_deployments(list)
  list.map do |name|
    record = nil
    begin
      record = apps_v1_kubeclient.get_deployment(name, @namespace)
    rescue Kubeclient::ResourceNotFoundError
      raise FatalRestartError, "Deployment `#{name}` not found in namespace `#{@namespace}`"
    end
    record
  end
end
identify_target_deployments(deployment_names, selector: nil) click to toggle source
# File lib/krane/restart_task.rb, line 100
def identify_target_deployments(deployment_names, selector: nil)
  if deployment_names.nil?
    deployments = if selector.nil?
      @logger.info("Configured to restart all deployments with the `#{ANNOTATION}` annotation")
      apps_v1_kubeclient.get_deployments(namespace: @namespace)
    else
      selector_string = selector.to_s
      @logger.info(
        "Configured to restart all deployments with the `#{ANNOTATION}` annotation and #{selector_string} selector"
      )
      apps_v1_kubeclient.get_deployments(namespace: @namespace, label_selector: selector_string)
    end
    deployments.select! { |d| d.metadata.annotations[ANNOTATION] }

    if deployments.none?
      raise FatalRestartError, "no deployments with the `#{ANNOTATION}` annotation found in namespace #{@namespace}"
    end
  elsif deployment_names.empty?
    raise FatalRestartError, "Configured to restart deployments by name, but list of names was blank"
  elsif !selector.nil?
    raise FatalRestartError, "Can't specify deployment names and selector at the same time"
  else
    deployment_names = deployment_names.uniq
    list = deployment_names.join(', ')
    @logger.info("Configured to restart deployments by name: #{list}")

    deployments = fetch_deployments(deployment_names)
    if deployments.none?
      raise FatalRestartError, "no deployments with names #{list} found in namespace #{@namespace}"
    end
  end
  deployments
end
kubeclient() click to toggle source
# File lib/krane/restart_task.rb, line 216
def kubeclient
  @kubeclient ||= kubeclient_builder.build_v1_kubeclient(@context)
end
kubectl() click to toggle source
# File lib/krane/restart_task.rb, line 220
def kubectl
  @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
end
patch_deployment_with_restart(record) click to toggle source
# File lib/krane/restart_task.rb, line 143
def patch_deployment_with_restart(record)
  apps_v1_kubeclient.patch_deployment(
    record.metadata.name,
    build_patch_payload(record),
    @namespace
  )
end
patch_kubeclient_deployments(deployments) click to toggle source
# File lib/krane/restart_task.rb, line 151
def patch_kubeclient_deployments(deployments)
  deployments.each do |record|
    begin
      patch_deployment_with_restart(record)
      @logger.info("Triggered `#{record.metadata.name}` restart")
    rescue Kubeclient::HttpError => e
      raise RestartAPIError.new(record.metadata.name, e.message)
    end
  end
end
tags(status, deployments) click to toggle source
# File lib/krane/restart_task.rb, line 96
def tags(status, deployments)
  %W(namespace:#{@namespace} context:#{@context} status:#{status} deployments:#{deployments.to_a.length}})
end
v1beta1_kubeclient() click to toggle source
# File lib/krane/restart_task.rb, line 224
def v1beta1_kubeclient
  @v1beta1_kubeclient ||= kubeclient_builder.build_v1beta1_kubeclient(@context)
end
verify_config!() click to toggle source
# File lib/krane/restart_task.rb, line 203
def verify_config!
  task_config_validator = TaskConfigValidator.new(@task_config, kubectl, kubeclient_builder)
  unless task_config_validator.valid?
    @logger.summary.add_action("Configuration invalid")
    @logger.summary.add_paragraph(task_config_validator.errors.map { |err| "- #{err}" }.join("\n"))
    raise Krane::TaskConfigurationError
  end
end
verify_restart(resources) click to toggle source
# File lib/krane/restart_task.rb, line 192
def verify_restart(resources)
  ResourceWatcher.new(resources: resources, operation_name: "restart",
    timeout: @global_timeout, task_config: @task_config).run
  failed_resources = resources.reject(&:deploy_succeeded?)
  success = failed_resources.empty?
  if !success && failed_resources.all?(&:deploy_timed_out?)
    raise DeploymentTimeoutError
  end
  raise FatalDeploymentError unless success
end