Merge branch '61927-pages-custom-domain-virtual-domain' into 'master'
Add support for custom domains to the Pages internal API See merge request gitlab-org/gitlab-ce!32735
This commit is contained in:
commit
68e8bbdb54
15 changed files with 358 additions and 9 deletions
38
app/models/pages/lookup_path.rb
Normal file
38
app/models/pages/lookup_path.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Pages
|
||||
class LookupPath
|
||||
def initialize(project, domain: nil)
|
||||
@project = project
|
||||
@domain = domain
|
||||
end
|
||||
|
||||
def project_id
|
||||
project.id
|
||||
end
|
||||
|
||||
def access_control
|
||||
project.private_pages?
|
||||
end
|
||||
|
||||
def https_only
|
||||
domain_https = domain ? domain.https? : true
|
||||
project.pages_https_only? && domain_https
|
||||
end
|
||||
|
||||
def source
|
||||
{
|
||||
type: 'file',
|
||||
path: File.join(project.full_path, 'public/')
|
||||
}
|
||||
end
|
||||
|
||||
def prefix
|
||||
'/'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :domain
|
||||
end
|
||||
end
|
28
app/models/pages/virtual_domain.rb
Normal file
28
app/models/pages/virtual_domain.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Pages
|
||||
class VirtualDomain
|
||||
def initialize(projects, domain: nil)
|
||||
@projects = projects
|
||||
@domain = domain
|
||||
end
|
||||
|
||||
def certificate
|
||||
domain&.certificate
|
||||
end
|
||||
|
||||
def key
|
||||
domain&.key
|
||||
end
|
||||
|
||||
def lookup_paths
|
||||
projects.map do |project|
|
||||
project.pages_lookup_path(domain: domain)
|
||||
end.sort_by(&:prefix).reverse
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :projects, :domain
|
||||
end
|
||||
end
|
|
@ -185,6 +185,10 @@ class PagesDomain < ApplicationRecord
|
|||
self.certificate_source = 'gitlab_provided' if key_changed?
|
||||
end
|
||||
|
||||
def pages_virtual_domain
|
||||
Pages::VirtualDomain.new([project], domain: self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_verification_code
|
||||
|
|
|
@ -61,11 +61,11 @@ class Project < ApplicationRecord
|
|||
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
||||
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
|
||||
:merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
|
||||
:merge_requests_access_level, :issues_access_level, :wiki_access_level,
|
||||
:snippets_access_level, :builds_access_level, :repository_access_level,
|
||||
to: :project_feature, allow_nil: true
|
||||
delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?,
|
||||
:issues_enabled?, :pages_enabled?, :public_pages?, :private_pages?,
|
||||
:merge_requests_access_level, :issues_access_level, :wiki_access_level,
|
||||
:snippets_access_level, :builds_access_level, :repository_access_level,
|
||||
to: :project_feature, allow_nil: true
|
||||
|
||||
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
|
||||
|
||||
|
@ -2201,6 +2201,10 @@ class Project < ApplicationRecord
|
|||
members.maintainers.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
|
||||
end
|
||||
|
||||
def pages_lookup_path(domain: nil)
|
||||
Pages::LookupPath.new(self, domain: domain)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merge_requests_allowing_collaboration(source_branch = nil)
|
||||
|
|
|
@ -129,6 +129,10 @@ class ProjectFeature < ApplicationRecord
|
|||
pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public?
|
||||
end
|
||||
|
||||
def private_pages?
|
||||
!public_pages?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Validates builds and merge requests access level
|
||||
|
|
19
lib/api/entities/internal.rb
Normal file
19
lib/api/entities/internal.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
module Internal
|
||||
module Pages
|
||||
class LookupPath < Grape::Entity
|
||||
expose :project_id, :access_control,
|
||||
:source, :https_only, :prefix
|
||||
end
|
||||
|
||||
class VirtualDomain < Grape::Entity
|
||||
expose :certificate, :key
|
||||
expose :lookup_paths, using: LookupPath
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,12 @@ module API
|
|||
namespace 'internal' do
|
||||
namespace 'pages' do
|
||||
get "/" do
|
||||
status :ok
|
||||
host = PagesDomain.find_by_domain(params[:host])
|
||||
not_found! unless host
|
||||
|
||||
virtual_domain = host.pages_virtual_domain
|
||||
|
||||
present virtual_domain, with: Entities::Internal::Pages::VirtualDomain
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
25
spec/fixtures/api/schemas/internal/pages/lookup_path.json
vendored
Normal file
25
spec/fixtures/api/schemas/internal/pages/lookup_path.json
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"project_id",
|
||||
"https_only",
|
||||
"access_control",
|
||||
"source",
|
||||
"prefix"
|
||||
],
|
||||
"properties": {
|
||||
"project_id": { "type": "integer" },
|
||||
"https_only": { "type": "boolean" },
|
||||
"access_control": { "type": "boolean" },
|
||||
"source": { "type": "object",
|
||||
"required": ["type", "path"],
|
||||
"properties" : {
|
||||
"type": { "type": "string", "enum": ["file"] },
|
||||
"path": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"prefix": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
16
spec/fixtures/api/schemas/internal/pages/virtual_domain.json
vendored
Normal file
16
spec/fixtures/api/schemas/internal/pages/virtual_domain.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"lookup_paths"
|
||||
],
|
||||
"optional": [
|
||||
"certificate",
|
||||
"key"
|
||||
],
|
||||
"properties": {
|
||||
"certificate": { "type": ["string", "null"] },
|
||||
"key": { "type": ["string", "null"] },
|
||||
"lookup_paths": { "type": "array", "items": { "$ref": "lookup_path.json" } }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
64
spec/models/pages/lookup_path_spec.rb
Normal file
64
spec/models/pages/lookup_path_spec.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Pages::LookupPath do
|
||||
let(:project) do
|
||||
instance_double(Project,
|
||||
id: 12345,
|
||||
private_pages?: true,
|
||||
pages_https_only?: true,
|
||||
full_path: 'the/full/path'
|
||||
)
|
||||
end
|
||||
|
||||
subject(:lookup_path) { described_class.new(project) }
|
||||
|
||||
describe '#project_id' do
|
||||
it 'delegates to Project#id' do
|
||||
expect(lookup_path.project_id).to eq(12345)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#access_control' do
|
||||
it 'delegates to Project#private_pages?' do
|
||||
expect(lookup_path.access_control).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#https_only' do
|
||||
subject(:lookup_path) { described_class.new(project, domain: domain) }
|
||||
|
||||
context 'when no domain provided' do
|
||||
let(:domain) { nil }
|
||||
|
||||
it 'delegates to Project#pages_https_only?' do
|
||||
expect(lookup_path.https_only).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is domain provided' do
|
||||
let(:domain) { instance_double(PagesDomain, https?: false) }
|
||||
|
||||
it 'takes into account the https setting of the domain' do
|
||||
expect(lookup_path.https_only).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#source' do
|
||||
it 'sets the source type to "file"' do
|
||||
expect(lookup_path.source[:type]).to eq('file')
|
||||
end
|
||||
|
||||
it 'sets the source path to the project full path suffixed with "public/' do
|
||||
expect(lookup_path.source[:path]).to eq('the/full/path/public/')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prefix' do
|
||||
it 'returns "/"' do
|
||||
expect(lookup_path.prefix).to eq('/')
|
||||
end
|
||||
end
|
||||
end
|
43
spec/models/pages/virtual_domain_spec.rb
Normal file
43
spec/models/pages/virtual_domain_spec.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Pages::VirtualDomain do
|
||||
describe '#certificate and #key pair' do
|
||||
let(:domain) { nil }
|
||||
let(:project) { instance_double(Project) }
|
||||
|
||||
subject(:virtual_domain) { described_class.new([project], domain: domain) }
|
||||
|
||||
it 'returns nil if there is no domain provided' do
|
||||
expect(virtual_domain.certificate).to be_nil
|
||||
expect(virtual_domain.key).to be_nil
|
||||
end
|
||||
|
||||
context 'when Pages domain is provided' do
|
||||
let(:domain) { instance_double(PagesDomain, certificate: 'certificate', key: 'key') }
|
||||
|
||||
it 'returns certificate and key from the provided domain' do
|
||||
expect(virtual_domain.certificate).to eq('certificate')
|
||||
expect(virtual_domain.key).to eq('key')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lookup_paths' do
|
||||
let(:domain) { instance_double(PagesDomain) }
|
||||
let(:project_a) { instance_double(Project) }
|
||||
let(:project_z) { instance_double(Project) }
|
||||
let(:pages_lookup_path_a) { instance_double(Pages::LookupPath, prefix: 'aaa') }
|
||||
let(:pages_lookup_path_z) { instance_double(Pages::LookupPath, prefix: 'zzz') }
|
||||
|
||||
subject(:virtual_domain) { described_class.new([project_a, project_z], domain: domain) }
|
||||
|
||||
it 'returns collection of projects pages lookup paths sorted by prefix in reverse' do
|
||||
expect(project_a).to receive(:pages_lookup_path).with(domain: domain).and_return(pages_lookup_path_a)
|
||||
expect(project_z).to receive(:pages_lookup_path).with(domain: domain).and_return(pages_lookup_path_z)
|
||||
|
||||
expect(virtual_domain.lookup_paths).to eq([pages_lookup_path_z, pages_lookup_path_a])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -556,4 +556,16 @@ describe PagesDomain do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.pages_virtual_domain' do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
subject(:pages_domain) { build(:pages_domain, project: project) }
|
||||
|
||||
it 'returns instance of Pages::VirtualDomain' do
|
||||
expect(Pages::VirtualDomain).to receive(:new).with([project], domain: pages_domain).and_call_original
|
||||
|
||||
expect(pages_domain.pages_virtual_domain).to be_a(Pages::VirtualDomain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -174,4 +174,58 @@ describe ProjectFeature do
|
|||
it { is_expected.to eq(ProjectFeature::ENABLED) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#public_pages?' do
|
||||
it 'returns true if Pages access controll is not enabled' do
|
||||
stub_config(pages: { access_control: false })
|
||||
|
||||
project_feature = described_class.new
|
||||
|
||||
expect(project_feature.public_pages?).to eq(true)
|
||||
end
|
||||
|
||||
context 'Pages access control is enabled' do
|
||||
before do
|
||||
stub_config(pages: { access_control: true })
|
||||
end
|
||||
|
||||
it 'returns true if Pages access level is public' do
|
||||
project_feature = described_class.new(pages_access_level: described_class::PUBLIC)
|
||||
|
||||
expect(project_feature.public_pages?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true if Pages access level is enabled and the project is public' do
|
||||
project = build(:project, :public)
|
||||
|
||||
project_feature = described_class.new(project: project, pages_access_level: described_class::ENABLED)
|
||||
|
||||
expect(project_feature.public_pages?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false if pages or the project are not public' do
|
||||
project = build(:project, :private)
|
||||
|
||||
project_feature = described_class.new(project: project, pages_access_level: described_class::ENABLED)
|
||||
|
||||
expect(project_feature.public_pages?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#private_pages?' do
|
||||
subject(:project_feature) { described_class.new }
|
||||
|
||||
it 'returns false if public_pages? is true' do
|
||||
expect(project_feature).to receive(:public_pages?).and_return(true)
|
||||
|
||||
expect(project_feature.private_pages?).to eq(false)
|
||||
end
|
||||
|
||||
it 'returns true if public_pages? is false' do
|
||||
expect(project_feature).to receive(:public_pages?).and_return(false)
|
||||
|
||||
expect(project_feature.private_pages?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5012,6 +5012,17 @@ describe Project do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#pages_lookup_path' do
|
||||
let(:pages_domain) { build(:pages_domain) }
|
||||
let(:project) { build(:project) }
|
||||
|
||||
it 'returns instance of Pages::LookupPath' do
|
||||
expect(Pages::LookupPath).to receive(:new).with(project, domain: pages_domain).and_call_original
|
||||
|
||||
expect(project.pages_lookup_path(domain: pages_domain)).to be_a(Pages::LookupPath)
|
||||
end
|
||||
end
|
||||
|
||||
def rugged_config
|
||||
rugged_repo(project.repository).config
|
||||
end
|
||||
|
|
|
@ -43,10 +43,32 @@ describe API::Internal::Pages do
|
|||
super(host, headers)
|
||||
end
|
||||
|
||||
it 'responds with 200 OK' do
|
||||
query_host('pages.gitlab.io')
|
||||
context 'not existing host' do
|
||||
it 'responds with 404 Not Found' do
|
||||
query_host('pages.gitlab.io')
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom domain' do
|
||||
let(:namespace) { create(:namespace, name: 'gitlab-org') }
|
||||
let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') }
|
||||
let!(:pages_domain) { create(:pages_domain, domain: 'pages.gitlab.io', project: project) }
|
||||
|
||||
it 'responds with the correct domain configuration' do
|
||||
query_host('pages.gitlab.io')
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('internal/pages/virtual_domain')
|
||||
|
||||
expect(json_response['certificate']).to eq(pages_domain.certificate)
|
||||
expect(json_response['key']).to eq(pages_domain.key)
|
||||
|
||||
lookup_path = json_response['lookup_paths'][0]
|
||||
expect(lookup_path['prefix']).to eq('/')
|
||||
expect(lookup_path['source']['path']).to eq('gitlab-org/gitlab-ce/public/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue