Commit ed50a778 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '38336-translate-knative-urls' into 'master'

Translate function URL if cluster has serverless domain

Closes #38336

See merge request gitlab-org/gitlab!22710
parents fcc2bdb4 2721d7df
...@@ -8,11 +8,15 @@ module Projects ...@@ -8,11 +8,15 @@ module Projects
def index def index
respond_to do |format| respond_to do |format|
format.json do format.json do
functions = finder.execute functions = finder.execute.select do |function|
can?(@current_user, :read_cluster, function.cluster)
end
serialized_functions = serialize_function(functions)
render json: { render json: {
knative_installed: finder.knative_installed, knative_installed: finder.knative_installed,
functions: serialize_function(functions) functions: serialized_functions
}.to_json }.to_json
end end
...@@ -23,11 +27,14 @@ module Projects ...@@ -23,11 +27,14 @@ module Projects
end end
def show def show
@service = serialize_function(finder.service(params[:environment_id], params[:id])) function = finder.service(params[:environment_id], params[:id])
@prometheus = finder.has_prometheus?(params[:environment_id]) return not_found unless function && can?(@current_user, :read_cluster, function.cluster)
@service = serialize_function(function)
return not_found if @service.nil? return not_found if @service.nil?
@prometheus = finder.has_prometheus?(params[:environment_id])
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: @service render json: @service
......
...@@ -93,24 +93,32 @@ module Projects ...@@ -93,24 +93,32 @@ module Projects
.services .services
.select { |svc| svc["metadata"]["name"] == name } .select { |svc| svc["metadata"]["name"] == name }
add_metadata(finder, services).first unless services.nil? attributes = add_metadata(finder, services).first
next unless attributes
Gitlab::Serverless::Service.new(attributes)
end end
end end
def knative_services def knative_services
services_finders.map do |finder| services_finders.map do |finder|
services = finder.services attributes = add_metadata(finder, finder.services)
add_metadata(finder, services) unless services.nil? attributes&.map do |attributes|
Gitlab::Serverless::Service.new(attributes)
end
end end
end end
def add_metadata(finder, services) def add_metadata(finder, services)
return if services.nil?
add_pod_count = services.one? add_pod_count = services.one?
services.each do |s| services.each do |s|
s["environment_scope"] = finder.cluster.environment_scope s["environment_scope"] = finder.cluster.environment_scope
s["cluster_id"] = finder.cluster.id s["environment"] = finder.environment
s["cluster"] = finder.cluster
if add_pod_count if add_pod_count
s["podcount"] = finder s["podcount"] = finder
......
...@@ -290,6 +290,12 @@ module Clusters ...@@ -290,6 +290,12 @@ module Clusters
end end
end end
def serverless_domain
strong_memoize(:serverless_domain) do
self.application_knative&.serverless_domain_cluster
end
end
private private
def unique_management_project_environment_scope def unique_management_project_environment_scope
......
...@@ -5,91 +5,31 @@ module Projects ...@@ -5,91 +5,31 @@ module Projects
class ServiceEntity < Grape::Entity class ServiceEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
expose :name do |service| expose :name
service.dig('metadata', 'name') expose :namespace
end expose :environment_scope
expose :podcount
expose :namespace do |service| expose :created_at
service.dig('metadata', 'namespace') expose :image
end expose :description
expose :url
expose :environment_scope do |service|
service.dig('environment_scope')
end
expose :cluster_id do |service|
service.dig('cluster_id')
end
expose :detail_url do |service| expose :detail_url do |service|
project_serverless_path( project_serverless_path(
request.project, request.project,
service.dig('environment_scope'), service.environment_scope,
service.dig('metadata', 'name')) service.name)
end
expose :podcount do |service|
service.dig('podcount')
end end
expose :metrics_url do |service| expose :metrics_url do |service|
project_serverless_metrics_path( project_serverless_metrics_path(
request.project, request.project,
service.dig('environment_scope'), service.environment_scope,
service.dig('metadata', 'name')) + ".json" service.name, format: :json)
end
expose :created_at do |service|
service.dig('metadata', 'creationTimestamp')
end
expose :url do |service|
knative_06_07_url(service) || knative_05_url(service)
end
expose :description do |service|
knative_07_description(service) || knative_05_06_description(service)
end end
expose :image do |service| expose :cluster_id do |service|
service.dig( service.cluster&.id
'spec',
'runLatest',
'configuration',
'build',
'template',
'name')
end
private
def knative_07_description(service)
service.dig(
'spec',
'template',
'metadata',
'annotations',
'Description'
)
end
def knative_05_url(service)
"http://#{service.dig('status', 'domain')}"
end
def knative_06_07_url(service)
service.dig('status', 'url')
end
def knative_05_06_description(service)
service.dig(
'spec',
'runLatest',
'configuration',
'revisionTemplate',
'metadata',
'annotations',
'Description')
end end
end end
end end
......
# frozen_string_literal: true
class Gitlab::Serverless::Service
include Gitlab::Utils::StrongMemoize
def initialize(attributes)
@attributes = attributes
end
def name
@attributes.dig('metadata', 'name')
end
def namespace
@attributes.dig('metadata', 'namespace')
end
def environment_scope
@attributes.dig('environment_scope')
end
def environment
@attributes.dig('environment')
end
def podcount
@attributes.dig('podcount')
end
def created_at
strong_memoize(:created_at) do
timestamp = @attributes.dig('metadata', 'creationTimestamp')
DateTime.parse(timestamp) if timestamp
end
end
def image
@attributes.dig(
'spec',
'runLatest',
'configuration',
'build',
'template',
'name')
end
def description
knative_07_description || knative_05_06_description
end
def cluster
@attributes.dig('cluster')
end
def url
proxy_url || knative_06_07_url || knative_05_url
end
private
def proxy_url
if cluster&.serverless_domain
Gitlab::Serverless::FunctionURI.new(function: name, cluster: cluster.serverless_domain, environment: environment)
end
end
def knative_07_description
@attributes.dig(
'spec',
'template',
'metadata',
'annotations',
'Description'
)
end
def knative_05_06_description
@attributes.dig(
'spec',
'runLatest',
'configuration',
'revisionTemplate',
'metadata',
'annotations',
'Description')
end
def knative_05_url
domain = @attributes.dig('status', 'domain')
return unless domain
"http://#{domain}"
end
def knative_06_07_url
@attributes.dig('status', 'url')
end
end
...@@ -14,9 +14,11 @@ describe Projects::Serverless::FunctionsController do ...@@ -14,9 +14,11 @@ describe Projects::Serverless::FunctionsController do
let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
let(:knative_services_finder) { environment.knative_services_finder } let(:knative_services_finder) { environment.knative_services_finder }
let(:function_description) { 'A serverless function' } let(:function_description) { 'A serverless function' }
let(:function_name) { 'some-function-name' }
let(:knative_stub_options) do let(:knative_stub_options) do
{ namespace: namespace.namespace, name: cluster.project.name, description: function_description } { namespace: namespace.namespace, name: function_name, description: function_description }
end end
let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
let(:namespace) do let(:namespace) do
create(:cluster_kubernetes_namespace, create(:cluster_kubernetes_namespace,
...@@ -87,25 +89,65 @@ describe Projects::Serverless::FunctionsController do ...@@ -87,25 +89,65 @@ describe Projects::Serverless::FunctionsController do
end end
context 'when functions were found' do context 'when functions were found' do
let(:functions) { ["asdf"] } let(:functions) { [{}, {}] }
before do before do
stub_kubeclient_knative_services(namespace: namespace.namespace) stub_kubeclient_knative_services(namespace: namespace.namespace, cluster_id: cluster.id, name: function_name)
get :index, params: params({ format: :json })
end end
it 'returns functions' do it 'returns functions' do
get :index, params: params({ format: :json })
expect(json_response["functions"]).not_to be_empty expect(json_response["functions"]).not_to be_empty
end end
it { expect(response).to have_gitlab_http_status(:ok) } it 'filters out the functions whose cluster the user does not have permission to read' do
allow(controller).to receive(:can?).and_return(true)
expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
get :index, params: params({ format: :json })
expect(json_response["functions"]).to be_empty
end
it 'returns a successful response status' do
get :index, params: params({ format: :json })
expect(response).to have_gitlab_http_status(:ok)
end
context 'when there is serverless domain for a cluster' do
let!(:serverless_domain_cluster) do
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
end
it 'returns JSON with function details with serverless domain URL' do
get :index, params: params({ format: :json })
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["functions"]).not_to be_empty
expect(json_response["functions"]).to all(
include(
'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
)
)
end
end
context 'when there is no serverless domain for a cluster' do
it 'keeps function URL as it was' do
expect(Gitlab::Serverless::Domain).not_to receive(:new)
get :index, params: params({ format: :json })
expect(response).to have_gitlab_http_status(:ok)
end
end
end end
end end
end end
describe 'GET #show' do describe 'GET #show' do
context 'invalid data' do context 'with function that does not exist' do
it 'has a bad function name' do it 'returns 404' do
get :show, params: params({ format: :json, environment_id: "*", id: "foo" }) get :show, params: params({ format: :json, environment_id: "*", id: "foo" })
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
...@@ -113,15 +155,50 @@ describe Projects::Serverless::FunctionsController do ...@@ -113,15 +155,50 @@ describe Projects::Serverless::FunctionsController do
context 'with valid data', :use_clean_rails_memory_store_caching do context 'with valid data', :use_clean_rails_memory_store_caching do
shared_examples 'GET #show with valid data' do shared_examples 'GET #show with valid data' do
it 'has a valid function name' do context 'when there is serverless domain for a cluster' do
get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name }) let!(:serverless_domain_cluster) do
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
end
it 'returns JSON with function details with serverless domain URL' do
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to include(
'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
)
end
it 'returns 404 when user does not have permission to read the cluster' do
allow(controller).to receive(:can?).and_return(true)
expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when there is no serverless domain for a cluster' do
it 'keeps function URL as it was' do
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to include(
'url' => "http://#{function_name}.#{namespace.namespace}.example.com"
)
end
end
it 'return json with function details' do
get :show, params: params({ format: :json, environment_id: "*", id: function_name })
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to include( expect(json_response).to include(
'name' => project.name, 'name' => function_name,
'url' => "http://#{project.name}.#{namespace.namespace}.example.com", 'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
'description' => function_description, 'description' => function_description,
'podcount' => 1 'podcount' => 0
) )
end end
end end
...@@ -180,8 +257,8 @@ describe Projects::Serverless::FunctionsController do ...@@ -180,8 +257,8 @@ describe Projects::Serverless::FunctionsController do
'knative_installed' => 'checking', 'knative_installed' => 'checking',
'functions' => [ 'functions' => [
a_hash_including( a_hash_including(
'name' => project.name, 'name' => function_name,
'url' => "http://#{project.name}.#{namespace.namespace}.example.com", 'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
'description' => function_description 'description' => function_description
) )
] ]
......
...@@ -153,8 +153,8 @@ describe Projects::Serverless::FunctionsFinder do ...@@ -153,8 +153,8 @@ describe Projects::Serverless::FunctionsFinder do
*knative_services_finder.cache_args) *knative_services_finder.cache_args)
result = finder.service(cluster.environment_scope, cluster.project.name) result = finder.service(cluster.environment_scope, cluster.project.name)
expect(result).not_to be_empty expect(result).to be_present
expect(result["metadata"]["name"]).to be_eql(cluster.project.name) expect(result.name).to be_eql(cluster.project.name)
end end
it 'has metrics', :use_clean_rails_memory_store_caching do it 'has metrics', :use_clean_rails_memory_store_caching do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Serverless::Service do
let(:cluster) { create(:cluster) }
let(:environment) { create(:environment) }
let(:attributes) do
{
'apiVersion' => 'serving.knative.dev/v1alpha1',
'kind' => 'Service',
'metadata' => {
'creationTimestamp' => '2019-10-22T21:19:13Z',
'name' => 'kubetest',
'namespace' => 'project1-1-environment1'
},
'spec' => {
'runLatest' => {
'configuration' => {
'build' => {
'template' => {
'name' => 'some-image'
}
}
}
}
},
'environment_scope' => '*',
'cluster' => cluster,
'environment' => environment,
'podcount' => 0
}
end
it 'exposes methods extracting data from the attributes hash' do
service = Gitlab::Serverless::Service.new(attributes)
expect(service.name).to eq('kubetest')
expect(service.namespace).to eq('project1-1-environment1')
expect(service.environment_scope).to eq('*')
expect(service.podcount).to eq(0)
expect(service.created_at).to eq(DateTime.parse('2019-10-22T21:19:13Z'))
expect(service.image).to eq('some-image')
expect(service.cluster).to eq(cluster)
expect(service.environment).to eq(environment)
end
it 'returns nil for missing attributes' do
service = Gitlab::Serverless::Service.new({})
[:name, :namespace, :environment_scope, :cluster, :podcount, :created_at, :image, :description, :url, :environment].each do |method|
expect(service.send(method)).to be_nil
end
end
describe '#description' do
it 'extracts the description in knative 7 format if available' do
attributes = {
'spec' => {
'template' => {
'metadata' => {
'annotations' => {
'Description' => 'some description'
}
}
}
}
}
service = Gitlab::Serverless::Service.new(attributes)
expect(service.description).to eq('some description')
end
it 'extracts the description in knative 5/6 format if 7 is not available' do
attributes = {
'spec' => {
'runLatest' => {
'configuration' => {
'revisionTemplate' => {
'metadata' => {
'annotations' => {
'Description' => 'some description'
}
}
}
}
}
}
}
service = Gitlab::Serverless::Service.new(attributes)
expect(service.description).to eq('some description')
end
end
describe '#url' do
it 'returns proxy URL if cluster has serverless domain' do
# cluster = create(:cluster)
knative = create(:clusters_applications_knative, :installed, cluster: cluster)
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster))
expect(Gitlab::Serverless::FunctionURI).to receive(:new).with(
function: service.name,
cluster: service.cluster.serverless_domain,
environment: service.environment
).and_return('https://proxy.example.com')
expect(service.url).to eq('https://proxy.example.com')
end
it 'returns the URL from the knative 6/7 format' do
attributes = {
'status' => {
'url' => 'https://example.com'
}
}
service = Gitlab::Serverless::Service.new(attributes)
expect(service.url).to eq('https://example.com')
end
it 'returns the URL from the knative 5 format' do
attributes = {
'status' => {
'domain' => 'example.com'
}
}
service = Gitlab::Serverless::Service.new(attributes)
expect(service.url).to eq('http://example.com')
end
end
end
...@@ -557,7 +557,7 @@ module KubernetesHelpers ...@@ -557,7 +557,7 @@ module KubernetesHelpers
end end
# noinspection RubyStringKeysInHashInspection # noinspection RubyStringKeysInHashInspection
def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 9)
{ "apiVersion" => "serving.knative.dev/v1alpha1", { "apiVersion" => "serving.knative.dev/v1alpha1",
"kind" => "Service", "kind" => "Service",
"metadata" => "metadata" =>
...@@ -612,12 +612,12 @@ module KubernetesHelpers ...@@ -612,12 +612,12 @@ module KubernetesHelpers
"url" => "http://#{name}.#{namespace}.#{domain}" "url" => "http://#{name}.#{namespace}.#{domain}"
}, },
"environment_scope" => environment, "environment_scope" => environment,
"cluster_id" => 9, "cluster_id" => cluster_id,
"podcount" => 0 } "podcount" => 0 }
end end
# noinspection RubyStringKeysInHashInspection # noinspection RubyStringKeysInHashInspection
def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
{ "apiVersion" => "serving.knative.dev/v1alpha1", { "apiVersion" => "serving.knative.dev/v1alpha1",
"kind" => "Service", "kind" => "Service",
"metadata" => "metadata" =>
...@@ -664,12 +664,12 @@ module KubernetesHelpers ...@@ -664,12 +664,12 @@ module KubernetesHelpers
"traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }], "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
"url" => "http://#{name}.#{namespace}.#{domain}" }, "url" => "http://#{name}.#{namespace}.#{domain}" },
"environment_scope" => environment, "environment_scope" => environment,
"cluster_id" => 5, "cluster_id" => cluster_id,
"podcount" => 0 } "podcount" => 0 }
end end
# noinspection RubyStringKeysInHashInspection # noinspection RubyStringKeysInHashInspection
def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
{ "apiVersion" => "serving.knative.dev/v1alpha1", { "apiVersion" => "serving.knative.dev/v1alpha1",
"kind" => "Service", "kind" => "Service",
"metadata" => "metadata" =>
...@@ -716,12 +716,12 @@ module KubernetesHelpers ...@@ -716,12 +716,12 @@ module KubernetesHelpers
"traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }], "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
"url" => "http://#{name}.#{namespace}.#{domain}" }, "url" => "http://#{name}.#{namespace}.#{domain}" },
"environment_scope" => environment, "environment_scope" => environment,
"cluster_id" => 5, "cluster_id" => cluster_id,
"podcount" => 0 } "podcount" => 0 }
end end
# noinspection RubyStringKeysInHashInspection # noinspection RubyStringKeysInHashInspection
def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 8)
{ "apiVersion" => "serving.knative.dev/v1alpha1", { "apiVersion" => "serving.knative.dev/v1alpha1",
"kind" => "Service", "kind" => "Service",
"metadata" => "metadata" =>
...@@ -771,7 +771,7 @@ module KubernetesHelpers ...@@ -771,7 +771,7 @@ module KubernetesHelpers
"observedGeneration" => 1, "observedGeneration" => 1,
"traffic" => [{ "percent" => 100, "revisionName" => "#{name}-58qgr" }] }, "traffic" => [{ "percent" => 100, "revisionName" => "#{name}-58qgr" }] },
"environment_scope" => environment, "environment_scope" => environment,
"cluster_id" => 8, "cluster_id" => cluster_id,
"podcount" => 0 } "podcount" => 0 }
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment