Class: KubernetesDeploy::Kubectl

Inherits:
Object
  • Object
show all
Defined in:
lib/kubernetes-deploy/kubectl.rb

Defined Under Namespace

Classes: ResourceNotFoundError

Constant Summary collapse

ERROR_MATCHERS =
{
  not_found: /NotFound/,
  client_timeout: /Client\.Timeout exceeded while awaiting headers/,
}
DEFAULT_TIMEOUT =
15
MAX_RETRY_DELAY =
16

Instance Method Summary collapse

Constructor Details

#initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT, output_is_sensitive_default: false) ⇒ Kubectl

Returns a new instance of Kubectl.

Raises:

  • (ArgumentError)


15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/kubernetes-deploy/kubectl.rb', line 15

def initialize(namespace:, context:, logger:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
  output_is_sensitive_default: false)
  @namespace = namespace
  @context = context
  @logger = logger
  @log_failure_by_default = log_failure_by_default
  @default_timeout = default_timeout
  @output_is_sensitive_default = output_is_sensitive_default

  raise ArgumentError, "namespace is required" if namespace.blank?
  raise ArgumentError, "context is required" if context.blank?
end

Instance Method Details

#client_versionObject



79
80
81
# File 'lib/kubernetes-deploy/kubectl.rb', line 79

def client_version
  version_info[:client]
end

#retry_delay(attempt) ⇒ Object



65
66
67
68
# File 'lib/kubernetes-deploy/kubectl.rb', line 65

def retry_delay(attempt)
  # exponential backoff starting at 1s with cap at 16s, offset by up to 0.5s
  [2**(attempt - 1), MAX_RETRY_DELAY].min - Random.rand(0.5).round(1)
end

#run(*args, log_failure: nil, use_context: true, use_namespace: true, output: nil, raise_if_not_found: false, attempts: 1, output_is_sensitive: nil, retry_whitelist: nil) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/kubernetes-deploy/kubectl.rb', line 28

def run(*args, log_failure: nil, use_context: true, use_namespace: true, output: nil,
  raise_if_not_found: false, attempts: 1, output_is_sensitive: nil, retry_whitelist: nil)
  log_failure = @log_failure_by_default if log_failure.nil?
  output_is_sensitive = @output_is_sensitive_default if output_is_sensitive.nil?
  cmd = build_command_from_options(args, use_namespace, use_context, output)
  out, err, st = nil

  (1..attempts).to_a.each do |current_attempt|
    @logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
    out, err, st = Open3.capture3(*cmd)
    @logger.debug("Kubectl out: " + out.gsub(/\s+/, ' ')) unless output_is_sensitive

    break if st.success?
    raise(ResourceNotFoundError, err) if err.match(ERROR_MATCHERS[:not_found]) && raise_if_not_found

    if log_failure
      warning = if current_attempt == attempts
        "The following command failed (attempt #{current_attempt}/#{attempts})"
      elsif retriable_err?(err, retry_whitelist)
        "The following command failed and will be retried (attempt #{current_attempt}/#{attempts})"
      else
        "The following command failed and cannot be retried"
      end
      @logger.warn("#{warning}: #{Shellwords.join(cmd)}")
      @logger.warn(err) unless output_is_sensitive
    else
      @logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
    end
    StatsD.increment('kubectl.error', 1, tags: { context: @context, namespace: @namespace, cmd: cmd[1] })

    break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
    sleep(retry_delay(current_attempt))
  end

  [out.chomp, err.chomp, st]
end

#server_versionObject



83
84
85
# File 'lib/kubernetes-deploy/kubectl.rb', line 83

def server_version
  version_info[:server]
end

#version_infoObject



70
71
72
73
74
75
76
77
# File 'lib/kubernetes-deploy/kubectl.rb', line 70

def version_info
  @version_info ||=
    begin
      response, _, status = run("version", use_namespace: false, log_failure: true)
      raise KubectlError, "Could not retrieve kubectl version info" unless status.success?
      extract_version_info_from_kubectl_response(response)
    end
end