gitlab-org--gitlab-foss/spec/support/helpers/kubernetes_helpers.rb
Dylan Griffith 4855667dad Retry fetching Kubernetes Secret token
Since Kubernetes is creating the Secret and token asynchronously it is
necessary that we implement some delay or retrying logic to avoid a race
condition where we fetch a Secret before the token is even set. There
does not appear to be any way for us to force it to be set with any
synchronous API call so retrying seems to be the only option.
2019-06-21 16:36:34 +10:00

445 lines
15 KiB
Ruby

module KubernetesHelpers
include Gitlab::Kubernetes
def kube_response(body)
{ body: body.to_json }
end
def kube_pods_response
kube_response(kube_pods_body)
end
def kube_logs_response
kube_response(kube_logs_body)
end
def kube_deployments_response
kube_response(kube_deployments_body)
end
def stub_kubeclient_discover_base(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
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"])
end
def stub_kubeclient_service_pods(response = nil, options = {})
stub_kubeclient_discover(service.api_url)
namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
pods_url = service.api_url + "/api/v1/#{namespace_path}pods"
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
def stub_kubeclient_pods(namespace, status: nil)
stub_kubeclient_discover(service.api_url)
pods_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods"
response = { status: status } if status
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
def stub_kubeclient_logs(pod_name, namespace, status: nil)
stub_kubeclient_discover(service.api_url)
logs_url = service.api_url + "/api/v1/namespaces/#{namespace}/pods/#{pod_name}/log?tailLines=#{Clusters::Platforms::Kubernetes::LOGS_LIMIT}"
response = { status: status } if status
WebMock.stub_request(:get, logs_url).to_return(response || kube_logs_response)
end
def stub_kubeclient_deployments(namespace, status: nil)
stub_kubeclient_discover(service.api_url)
deployments_url = service.api_url + "/apis/extensions/v1beta1/namespaces/#{namespace}/deployments"
response = { status: status } if status
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
def stub_kubeclient_knative_services(options = {})
namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
options[:name] ||= "kubetest"
options[:domain] ||= "example.com"
options[:response] ||= kube_response(kube_knative_services_body(options))
stub_kubeclient_discover(service.api_url)
knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/#{namespace_path}services"
WebMock.stub_request(:get, knative_url).to_return(options[:response])
end
def stub_kubeclient_get_secret(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)))
end
def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default', status: 404)
WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}")
.to_return(status: [status, "Internal Server Error"])
end
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
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
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
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
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
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
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
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
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
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
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
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
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
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
def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
"apiVersion": "v1",
"metadata": {
"name": options.fetch(:metadata_name, "default-token-1"),
"namespace": "kube-system"
},
"data": {
"token": options.fetch(:token, Base64.encode64('token-sample-123'))
}
}
end
def kube_v1_discovery_body
{
"kind" => "APIResourceList",
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
{ "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
{ "name" => "services", "namespaced" => true, "kind" => "Service" },
{ "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" }
]
}
end
def kube_v1beta1_discovery_body
{
"kind" => "APIResourceList",
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
{ "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
{ "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" }
]
}
end
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
def kube_pods_body
{
"kind" => "PodList",
"items" => [kube_pod]
}
end
def kube_logs_body
"Log 1\nLog 2\nLog 3"
end
def kube_deployments_body
{
"kind" => "DeploymentList",
"items" => [kube_deployment]
}
end
def kube_knative_pods_body(name, namespace)
{
"kind" => "PodList",
"items" => [kube_knative_pod(name: name, namespace: namespace)]
}
end
def kube_knative_services_body(**options)
{
"kind" => "List",
"items" => [kube_service(options)]
}
end
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_pod(name: "kube-pod", environment_slug: "production", namespace: "project-namespace", project_slug: "project-path-slug", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
"namespace" => namespace,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
"annotations" => {
"app.gitlab.com/env" => environment_slug,
"app.gitlab.com/app" => project_slug
},
"labels" => {
"track" => track
}.compact
},
"spec" => {
"containers" => [
{ "name" => "container-0" },
{ "name" => "container-1" }
]
},
"status" => { "phase" => status }
}
end
# 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
def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
"annotations" => {
"app.gitlab.com/env" => environment_slug,
"app.gitlab.com/app" => project_slug
},
"labels" => {
"track" => track
}.compact
},
"spec" => { "replicas" => 3 },
"status" => {
"observedGeneration" => 4,
"replicas" => 3,
"updatedReplicas" => 3,
"availableReplicas" => 3
}
}
end
def kube_service(name: "kubetest", namespace: "default", domain: "example.com")
{
"metadata" => {
"creationTimestamp" => "2018-11-21T06:16:33Z",
"name" => name,
"namespace" => namespace,
"selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
},
"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
def kube_terminals(service, pod)
pod_name = pod['metadata']['name']
pod_namespace = pod['metadata']['namespace']
containers = pod['spec']['containers']
containers.map do |container|
terminal = {
selectors: { pod: pod_name, container: container['name'] },
url: container_exec_url(service.api_url, pod_namespace, pod_name, container['name']),
subprotocols: ['channel.k8s.io'],
headers: { 'Authorization' => ["Bearer #{service.token}"] },
created_at: DateTime.parse(pod['metadata']['creationTimestamp']),
max_session_time: 0
}
terminal[:ca_pem] = service.ca_pem if service.ca_pem.present?
terminal
end
end
def kube_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_deployments(kube_deployment)
end
def empty_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_deployments
end
end