kubernetes_helpers.rb 15.5 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
module KubernetesHelpers
  include Gitlab::Kubernetes

6 7 8 9 10 11 12 13
  def kube_response(body)
    { body: body.to_json }
  end

  def kube_pods_response
    kube_response(kube_pods_body)
  end

14 15 16 17
  def kube_logs_response
    kube_response(kube_logs_body)
  end

18 19 20 21
  def kube_deployments_response
    kube_response(kube_deployments_body)
  end

22
  def stub_kubeclient_discover_base(api_url)
23
    WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
    WebMock
      .stub_request(:get, api_url + '/apis/extensions/v1beta1')
      .to_return(kube_response(kube_v1beta1_discovery_body))
    WebMock
      .stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1')
      .to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
  end

  def stub_kubeclient_discover(api_url)
    stub_kubeclient_discover_base(api_url)

    WebMock
      .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
      .to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
  end

  def stub_kubeclient_discover_knative_not_found(api_url)
    stub_kubeclient_discover_base(api_url)

    WebMock
      .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
      .to_return(status: [404, "Resource Not Found"])
46 47
  end

48
  def stub_kubeclient_service_pods(response = nil, options = {})
49
    stub_kubeclient_discover(service.api_url)
50 51 52 53

    namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""

    pods_url = service.api_url + "/api/v1/#{namespace_path}pods"
54 55 56 57

    WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
  end

58
  def stub_kubeclient_pods(namespace, status: nil)
59
    stub_kubeclient_discover(service.api_url)
60 61
    pods_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods"
    response = { status: status } if status
62 63 64 65

    WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
  end

66
  def stub_kubeclient_logs(pod_name, namespace, status: nil)
67
    stub_kubeclient_discover(service.api_url)
68 69
    logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
    response = { status: status } if status
70 71 72 73

    WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response)
  end

74
  def stub_kubeclient_deployments(namespace, status: nil)
75
    stub_kubeclient_discover(service.api_url)
76 77
    deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/deployments"
    response = { status: status } if status
78 79 80 81

    WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
  end

82 83 84
  def stub_kubeclient_knative_services(options = {})
    namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""

85 86
    options[:name] ||= "kubetest"
    options[:domain] ||= "example.com"
87
    options[:response] ||= kube_response(kube_knative_services_body(options))
88 89 90

    stub_kubeclient_discover(service.api_url)

91 92 93
    knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/#{namespace_path}services"

    WebMock.stub_request(:get, knative_url).to_return(options[:response])
94 95
  end

96
  def stub_kubeclient_get_secret(api_url, **options)
97
    options[:metadata_name] ||= "default-token-1"
98
    options[:namespace] ||= "default"
99

100
    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
101
      .to_return(kube_response(kube_v1_secret_body(options)))
102 103
  end

104
  def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default', status: 404)
105
    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}")
106
      .to_return(status: [status, "Internal Server Error"])
107 108
  end

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
  def stub_kubeclient_get_secret_not_found_then_found(api_url, **options)
    options[:metadata_name] ||= "default-token-1"
    options[:namespace] ||= "default"

    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
      .to_return(status: [404, "Not Found"])
      .then
      .to_return(kube_response(kube_v1_secret_body(options)))
  end

  def stub_kubeclient_get_secret_missing_token_then_with_token(api_url, **options)
    options[:metadata_name] ||= "default-token-1"
    options[:namespace] ||= "default"

    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
      .to_return(kube_response(kube_v1_secret_body(options.merge(token: nil))))
      .then
      .to_return(kube_response(kube_v1_secret_body(options)))
  end

129 130 131 132 133
  def stub_kubeclient_get_service_account(api_url, name, namespace: 'default')
    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}")
      .to_return(kube_response({}))
  end

134 135 136 137 138
  def stub_kubeclient_get_service_account_error(api_url, name, namespace: 'default', status: 404)
    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}")
      .to_return(status: [status, "Internal Server Error"])
  end

139 140 141 142 143
  def stub_kubeclient_create_service_account(api_url, namespace: 'default')
    WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts")
      .to_return(kube_response({}))
  end

144 145 146 147 148
  def stub_kubeclient_create_service_account_error(api_url, namespace: 'default')
    WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts")
      .to_return(status: [500, "Internal Server Error"])
  end

149 150 151 152 153
  def stub_kubeclient_put_service_account(api_url, name, namespace: 'default')
    WebMock.stub_request(:put, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}")
      .to_return(kube_response({}))
  end

154 155 156 157 158
  def stub_kubeclient_create_secret(api_url, namespace: 'default')
    WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets")
      .to_return(kube_response({}))
  end

159 160 161 162 163 164 165 166 167 168
  def stub_kubeclient_put_secret(api_url, name, namespace: 'default')
    WebMock.stub_request(:put, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}")
      .to_return(kube_response({}))
  end

  def stub_kubeclient_get_cluster_role_binding_error(api_url, name, status: 404)
    WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}")
      .to_return(status: [status, "Internal Server Error"])
  end

169 170 171 172 173
  def stub_kubeclient_create_cluster_role_binding(api_url)
    WebMock.stub_request(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings')
      .to_return(kube_response({}))
  end

174 175 176 177 178
  def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default')
    WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
      .to_return(kube_response({}))
  end

179 180 181 182 183
  def stub_kubeclient_get_role_binding_error(api_url, name, namespace: 'default', status: 404)
    WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
      .to_return(status: [status, "Internal Server Error"])
  end

184 185 186 187 188
  def stub_kubeclient_create_role_binding(api_url, namespace: 'default')
    WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings")
      .to_return(kube_response({}))
  end

189 190 191 192 193
  def stub_kubeclient_put_role_binding(api_url, name, namespace: 'default')
    WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
      .to_return(kube_response({}))
  end

194 195 196 197 198 199 200 201 202 203
  def stub_kubeclient_create_namespace(api_url)
    WebMock.stub_request(:post, api_url + "/api/v1/namespaces")
      .to_return(kube_response({}))
  end

  def stub_kubeclient_get_namespace(api_url, namespace: 'default')
    WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}")
      .to_return(kube_response({}))
  end

204 205 206 207 208
  def stub_kubeclient_put_role(api_url, name, namespace: 'default')
    WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}")
      .to_return(kube_response({}))
  end

209
  def kube_v1_secret_body(**options)
210 211 212
    {
      "kind" => "SecretList",
      "apiVersion": "v1",
213
      "metadata": {
214
        "name": options.fetch(:metadata_name, "default-token-1"),
215 216 217
        "namespace": "kube-system"
      },
      "data": {
218
        "token": options.fetch(:token, Base64.encode64('token-sample-123'))
219
      }
220 221 222
    }
  end

223
  def kube_v1_discovery_body
224 225
    {
      "kind" => "APIResourceList",
226
      "resources" => [
227
        { "name" => "pods", "namespaced" => true, "kind" => "Pod" },
228
        { "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
229
        { "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
230
        { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
231 232
        { "name" => "services", "namespaced" => true, "kind" => "Service" },
        { "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" }
233 234 235 236 237 238 239 240 241 242
      ]
    }
  end

  def kube_v1beta1_discovery_body
    {
      "kind" => "APIResourceList",
      "resources" => [
        { "name" => "pods", "namespaced" => true, "kind" => "Pod" },
        { "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
243
        { "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
244
        { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
245 246 247 248 249 250 251 252 253 254 255 256 257
        { "name" => "services", "namespaced" => true, "kind" => "Service" }
      ]
    }
  end

  def kube_v1_rbac_authorization_discovery_body
    {
      "kind" => "APIResourceList",
      "resources" => [
        { "name" => "clusterrolebindings", "namespaced" => false, "kind" => "ClusterRoleBinding" },
        { "name" => "clusterroles", "namespaced" => false, "kind" => "ClusterRole" },
        { "name" => "rolebindings", "namespaced" => true, "kind" => "RoleBinding" },
        { "name" => "roles", "namespaced" => true, "kind" => "Role" }
258
      ]
259 260 261
    }
  end

262 263 264 265 266 267 268 269 270 271 272 273
  def kube_v1alpha1_serving_knative_discovery_body
    {
      "kind" => "APIResourceList",
      "resources" => [
        { "name" => "revisions", "namespaced" => true, "kind" => "Revision" },
        { "name" => "services", "namespaced" => true, "kind" => "Service" },
        { "name" => "configurations", "namespaced" => true, "kind" => "Configuration" },
        { "name" => "routes", "namespaced" => true, "kind" => "Route" }
      ]
    }
  end

274 275 276 277 278
  def kube_pods_body
    {
      "kind" => "PodList",
      "items" => [kube_pod]
    }
279 280
  end

281 282 283 284
  def kube_logs_body
    "Log 1\nLog 2\nLog 3"
  end

285 286 287 288 289 290 291
  def kube_deployments_body
    {
      "kind" => "DeploymentList",
      "items" => [kube_deployment]
    }
  end

292 293 294 295 296 297 298
  def kube_knative_pods_body(name, namespace)
    {
      "kind" => "PodList",
      "items" => [kube_knative_pod(name: name, namespace: namespace)]
    }
  end

299 300 301 302 303 304 305
  def kube_knative_services_body(**options)
    {
      "kind" => "List",
      "items" => [kube_service(options)]
    }
  end

306 307
  # This is a partial response, it will have many more elements in reality but
  # these are the ones we care about at the moment
308
  def kube_pod(name: "kube-pod", environment_slug: "production", namespace: "project-namespace", project_slug: "project-path-slug", status: "Running", track: nil)
309 310
    {
      "metadata" => {
311
        "name" => name,
312
        "namespace" => namespace,
313
        "generate_name" => "generated-name-with-suffix",
314
        "creationTimestamp" => "2016-11-25T19:55:19Z",
315 316 317 318
        "annotations" => {
          "app.gitlab.com/env" => environment_slug,
          "app.gitlab.com/app" => project_slug
        },
319 320
        "labels" => {
          "track" => track
321
        }.compact
322 323 324 325
      },
      "spec" => {
        "containers" => [
          { "name" => "container-0" },
326 327
          { "name" => "container-1" }
        ]
328
      },
329 330 331 332
      "status" => { "phase" => status }
    }
  end

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
  # Similar to a kube_pod, but should contain a running service
  def kube_knative_pod(name: "kube-pod", namespace: "default", status: "Running")
    {
      "metadata" => {
        "name" => name,
        "namespace" => namespace,
        "generate_name" => "generated-name-with-suffix",
        "creationTimestamp" => "2016-11-25T19:55:19Z",
        "labels" => {
          "serving.knative.dev/service" => name
        }
      },
      "spec" => {
        "containers" => [
          { "name" => "container-0" },
          { "name" => "container-1" }
        ]
      },
      "status" => { "phase" => status }
    }
  end

355
  def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil)
356 357 358 359
    {
      "metadata" => {
        "name" => name,
        "generation" => 4,
360 361 362 363
        "annotations" => {
          "app.gitlab.com/env" => environment_slug,
          "app.gitlab.com/app" => project_slug
        },
364 365 366 367 368 369 370 371 372 373 374
        "labels" => {
          "track" => track
        }.compact
      },
      "spec" => { "replicas" => 3 },
      "status" => {
        "observedGeneration" => 4,
        "replicas" => 3,
        "updatedReplicas" => 3,
        "availableReplicas" => 3
      }
375 376 377
    }
  end

378 379 380
  def kube_service(name: "kubetest", namespace: "default", domain: "example.com")
    {
      "metadata" => {
381 382 383 384
        "creationTimestamp" => "2018-11-21T06:16:33Z",
        "name" => name,
        "namespace" => namespace,
        "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
      },
      "spec" => {
        "generation" => 2
      },
      "status" => {
        "domain" => "#{name}.#{namespace}.#{domain}",
        "domainInternal" => "#{name}.#{namespace}.svc.cluster.local",
        "latestCreatedRevisionName" => "#{name}-00002",
        "latestReadyRevisionName" => "#{name}-00002",
        "observedGeneration" => 2
      }
    }
  end

  def kube_service_full(name: "kubetest", namespace: "kube-ns", domain: "example.com")
    {
      "metadata" => {
        "creationTimestamp" => "2018-11-21T06:16:33Z",
        "name" => name,
        "namespace" => namespace,
        "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}",
        "annotation" => {
          "description" => "This is a test description"
        }
      },
      "spec" => {
        "generation" => 2,
        "build" => {
          "template" => "go-1.10.3"
        }
      },
      "status" => {
        "domain" => "#{name}.#{namespace}.#{domain}",
        "domainInternal" => "#{name}.#{namespace}.svc.cluster.local",
        "latestCreatedRevisionName" => "#{name}-00002",
        "latestReadyRevisionName" => "#{name}-00002",
        "observedGeneration" => 2
      }
    }
  end

426 427
  def kube_terminals(service, pod)
    pod_name = pod['metadata']['name']
428
    pod_namespace = pod['metadata']['namespace']
429 430 431 432 433
    containers = pod['spec']['containers']

    containers.map do |container|
      terminal = {
        selectors: { pod: pod_name, container: container['name'] },
434
        url:  container_exec_url(service.api_url, pod_namespace, pod_name, container['name']),
435 436
        subprotocols: ['channel.k8s.io'],
        headers: { 'Authorization' => ["Bearer #{service.token}"] },
437 438
        created_at: DateTime.parse(pod['metadata']['creationTimestamp']),
        max_session_time: 0
439 440 441 442 443
      }
      terminal[:ca_pem] = service.ca_pem if service.ca_pem.present?
      terminal
    end
  end
444 445 446 447 448 449

  def kube_deployment_rollout_status
    ::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
  end

  def empty_deployment_rollout_status
450
    ::Gitlab::Kubernetes::RolloutStatus.from_deployments
451
  end
452
end