kubernetes.rb 4.27 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
module Gitlab
  # Helper methods to do with Kubernetes network services & resources
  module Kubernetes
6 7 8 9
    def self.build_header_hash
      Hash.new { |h, k| h[k] = [] }
    end

10 11 12 13 14 15 16
    # This is the comand that is run to start a terminal session. Kubernetes
    # expects `command=foo&command=bar, not `command[]=foo&command[]=bar`
    EXEC_COMMAND = URI.encode_www_form(
      ['sh', '-c', 'bash || sh'].map { |value| ['command', value] }
    )

    # Filters an array of pods (as returned by the kubernetes API) by their labels
17 18 19 20 21
    def filter_by_label(items, labels = {})
      items.select do |item|
        metadata = item.fetch("metadata", {})
        item_labels = metadata.fetch("labels", nil)
        next unless item_labels
22

23
        labels.all? { |k, v| item_labels[k.to_s] == v }
24 25 26
      end
    end

27 28 29 30 31 32 33 34 35 36 37 38 39
    # Filters an array of pods (as returned by the kubernetes API) by their annotations
    def filter_by_annotation(items, annotations = {})
      items.select do |item|
        metadata = item.fetch("metadata", {})
        item_annotations = metadata.fetch("annotations", nil)
        next unless item_annotations

        annotations.all? { |k, v| item_annotations[k.to_s] == v }
      end
    end

    # Filters an array of pods (as returned by the kubernetes API) by their project and environment
    def filter_by_project_environment(items, app, env)
40
      filter_by_annotation(items, {
41 42 43 44 45
        'app.gitlab.com/app' => app,
        'app.gitlab.com/env' => env
      })
    end

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    # Converts a pod (as returned by the kubernetes API) into a terminal
    def terminals_for_pod(api_url, namespace, pod)
      metadata = pod.fetch("metadata", {})
      status   = pod.fetch("status", {})
      spec     = pod.fetch("spec", {})

      containers = spec["containers"]
      pod_name   = metadata["name"]
      phase      = status["phase"]

      return unless containers.present? && pod_name.present? && phase == "Running"

      created_at = DateTime.parse(metadata["creationTimestamp"]) rescue nil

      containers.map do |container|
        {
          selectors:    { pod: pod_name, container: container["name"] },
          url:          container_exec_url(api_url, namespace, pod_name, container["name"]),
          subprotocols: ['channel.k8s.io'],
65
          headers:      ::Gitlab::Kubernetes.build_header_hash,
66
          created_at:   created_at
67 68 69 70
        }
      end
    end

71
    def add_terminal_auth(terminal, token:, max_session_time:, ca_pem: nil)
72
      terminal[:headers] ||= ::Gitlab::Kubernetes.build_header_hash
73
      terminal[:headers]['Authorization'] << "Bearer #{token}"
74
      terminal[:max_session_time] = max_session_time
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
      terminal[:ca_pem] = ca_pem if ca_pem.present?
    end

    def container_exec_url(api_url, namespace, pod_name, container_name)
      url = URI.parse(api_url)
      url.path = [
        url.path.sub(%r{/+\z}, ''),
        'api', 'v1',
        'namespaces', ERB::Util.url_encode(namespace),
        'pods', ERB::Util.url_encode(pod_name),
        'exec'
      ].join('/')

      url.query = {
        container: container_name,
        tty: true,
        stdin: true,
        stdout: true,
93
        stderr: true
94 95 96 97 98 99 100 101 102 103 104
      }.to_query + '&' + EXEC_COMMAND

      case url.scheme
      when 'http'
        url.scheme = 'ws'
      when 'https'
        url.scheme = 'wss'
      end

      url.to_s
    end
105 106

    def to_kubeconfig(url:, namespace:, token:, ca_pem: nil)
107 108
      return unless token.present?

109 110 111 112 113 114
      config = {
        apiVersion: 'v1',
        clusters: [
          name: 'gitlab-deploy',
          cluster: {
            server: url
Lin Jen-Shin's avatar
Lin Jen-Shin committed
115
          }
116 117 118 119 120 121 122
        ],
        contexts: [
          name: 'gitlab-deploy',
          context: {
            cluster: 'gitlab-deploy',
            namespace: namespace,
            user: 'gitlab-deploy'
Lin Jen-Shin's avatar
Lin Jen-Shin committed
123
          }
124
        ],
Lin Jen-Shin's avatar
Lin Jen-Shin committed
125
        'current-context': 'gitlab-deploy',
126 127 128 129
        kind: 'Config',
        users: [
          {
            name: 'gitlab-deploy',
Lin Jen-Shin's avatar
Lin Jen-Shin committed
130
            user: { token: token }
131 132 133 134 135 136
          }
        ]
      }

      kubeconfig_embed_ca_pem(config, ca_pem) if ca_pem

137
      YAML.dump(config.deep_stringify_keys)
138 139 140 141 142 143
    end

    private

    def kubeconfig_embed_ca_pem(config, ca_pem)
      cluster = config.dig(:clusters, 0, :cluster)
144
      cluster[:'certificate-authority-data'] = Base64.strict_encode64(ca_pem)
145
    end
146 147
  end
end