Welcome to mirror list, hosted at ThFree Co, Russian Federation.

kubernetes_service.rb « pod_logs « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 31e26912c735a90159ac1c086f6999cea7bbdb41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true

module PodLogs
  class KubernetesService < PodLogs::BaseService
    LOGS_LIMIT = 500.freeze
    REPLACEMENT_CHAR = "\u{FFFD}"

    EncodingHelperError = Class.new(StandardError)

    steps :check_arguments,
          :get_raw_pods,
          :get_pod_names,
          :check_pod_name,
          :check_container_name,
          :pod_logs,
          :encode_logs_to_utf8,
          :split_logs,
          :filter_return_keys

    self.reactive_cache_worker_finder = ->(id, _cache_key, namespace, params) { new(::Clusters::Cluster.find(id), namespace, params: params) }

    private

    def check_pod_name(result)
      # If pod_name is not received as parameter, get the pod logs of the first
      # pod of this namespace.
      result[:pod_name] ||= result[:pods].first

      unless result[:pod_name]
        return error(_('No pods available'))
      end

      unless result[:pod_name].length.to_i <= K8S_NAME_MAX_LENGTH
        return error(_('pod_name cannot be larger than %{max_length}'\
          ' chars' % { max_length: K8S_NAME_MAX_LENGTH }))
      end

      unless result[:pods].include?(result[:pod_name])
        return error(_('Pod does not exist'))
      end

      success(result)
    end

    def check_container_name(result)
      pod_details = result[:raw_pods].find { |p| p.metadata.name == result[:pod_name] }
      containers = pod_details.spec.containers.map(&:name)

      # select first container if not specified
      result[:container_name] ||= containers.first

      unless result[:container_name]
        return error(_('No containers available'))
      end

      unless result[:container_name].length.to_i <= K8S_NAME_MAX_LENGTH
        return error(_('container_name cannot be larger than'\
          ' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH }))
      end

      unless containers.include?(result[:container_name])
        return error(_('Container does not exist'))
      end

      success(result)
    end

    def pod_logs(result)
      result[:logs] = cluster.kubeclient.get_pod_log(
        result[:pod_name],
        namespace,
        container: result[:container_name],
        tail_lines: LOGS_LIMIT,
        timestamps: true
      ).body

      success(result)
    rescue Kubeclient::ResourceNotFoundError
      error(_('Pod not found'))
    rescue Kubeclient::HttpError => e
      ::Gitlab::ErrorTracking.track_exception(e)

      error(_('Kubernetes API returned status code: %{error_code}') % {
        error_code: e.error_code
      })
    end

    # Check https://gitlab.com/gitlab-org/gitlab/issues/34965#note_292261879
    # for more details on why this is necessary.
    def encode_logs_to_utf8(result)
      return success(result) if result[:logs].nil?
      return success(result) if result[:logs].encoding == Encoding::UTF_8

      result[:logs] = encode_utf8(result[:logs])

      success(result)
    rescue EncodingHelperError
      error(_('Unable to convert Kubernetes logs encoding to UTF-8'))
    end

    def split_logs(result)
      result[:logs] = result[:logs].strip.lines(chomp: true).map do |line|
        # message contains a RFC3339Nano timestamp, then a space, then the log line.
        # resolution of the nanoseconds can vary, so we split on the first space
        values = line.split(' ', 2)
        {
          timestamp: values[0],
          message: values[1],
          pod: result[:pod_name]
        }
      end

      success(result)
    end

    def encode_utf8(logs)
      utf8_logs = Gitlab::EncodingHelper.encode_utf8(logs.dup, replace: REPLACEMENT_CHAR)

      # Gitlab::EncodingHelper.encode_utf8 can return '' or nil if an exception
      # is raised while encoding. We prefer to return an error rather than wrongly
      # display blank logs.
      no_utf8_logs = logs.present? && utf8_logs.blank?
      unexpected_encoding = utf8_logs&.encoding != Encoding::UTF_8

      if no_utf8_logs || unexpected_encoding
        raise EncodingHelperError, 'Could not convert Kubernetes logs to UTF-8'
      end

      utf8_logs
    end
  end
end