Files
gitlab-foss/lib/gitlab/pages/url_builder.rb
2025-01-24 15:33:02 +00:00

172 lines
4.9 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Pages
class UrlBuilder
attr_reader :project_namespace
ALLOWED_ARTIFACT_EXTENSIONS = %w[.html .htm .txt .json .xml .log].freeze
ARTIFACT_URL = "%{host}/-/%{project_path}/-/jobs/%{job_id}/artifacts/%{artifact_path}"
def initialize(project, options = nil)
@project = project
namespace, _, @project_path = project.full_path.partition('/')
@project_namespace = namespace.downcase
@options = options || {}
end
def pages_url
default_url
.to_s
end
def unique_host
return unless unique_domain_enabled?
return if namespace_in_path?
hostname
end
# If the project path is the same as host, we serve it as group/user page.
#
# e.g. For Pages external url `example.io`,
# `acmecorp/acmecorp.example.io` project will publish to `http(s)://acmecorp.example.io`
# See https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#user-and-group-website-examples.
def is_namespace_homepage? # rubocop:disable Naming/PredicateName -- namespace_homepage is not an
# adjective, so adding "is_" improves understandability
project_path.downcase == "#{project_namespace}.#{instance_pages_domain}"
end
def artifact_url(artifact, job)
return unless artifact_url_available?(artifact, job)
format(
ARTIFACT_URL,
host: namespace_url,
project_path: project_path,
job_id: job.id,
artifact_path: artifact.path)
end
def artifact_url_available?(artifact, job)
config.enabled &&
config.artifacts_server &&
ALLOWED_ARTIFACT_EXTENSIONS.include?(File.extname(artifact.name)) &&
(config.access_control || job.project.public?)
end
# Defines the full hostname, eg. group.gitlab.io
def hostname
return instance_pages_domain if namespace_in_path?
return project_path if is_namespace_homepage?
return instance_pages_domain.prepend("#{subdomain}.") if subdomain.present?
instance_pages_domain
end
def path_prefix
return unless @options[:path_prefix].present?
::Gitlab::Utils.slugify(@options[:path_prefix], allow_dots: true).presence
end
private
attr_reader :project, :project_path
# Defines whether the Pages site is published on a unique subdomain
# instead of a single subdomain for the entire namespace and a path
# segment for each project.
#
# Examples:
# unique_domain_enabled? is true: "https://my-project-12345.example.com
# unique_domain_enabled? is false: "https://my-group.example.com/my-project
def unique_domain_enabled?
project.project_setting.pages_unique_domain_enabled?
end
# Defines whether the namespace should be included as part of the path
# instead of using a subdomain. Useful for self-hosted instances where
# auto-generated subdomains don't work.
#
# Examples:
# namespace_in_path? is false: "https://my-group.example.com/my-project
# namespace_in_path? is true: "https://example.com/my-group/my-project
def namespace_in_path?
config.namespace_in_path
end
def config
Gitlab.config.pages
end
def config_url
URI(config.url)
end
def protocol
config.protocol
end
def subdomain
return if namespace_in_path?
return project.project_setting.pages_unique_domain if unique_domain_enabled?
project_namespace
end
# Defines the top-most domain that is not autogenerated. For gitlab.com
# this is `gitlab.io`. If you need the fully qualified domain for the
# project, use `hostname` instead.
def instance_pages_domain
config_url.host
end
def port
config.port
end
def path
[
namespace_path_segment,
project_path_segment,
path_prefix
].compact.join('/')
end
def namespace_path_segment
return unless namespace_in_path?
return project_namespace unless unique_domain_enabled?
project.project_setting.pages_unique_domain
end
def project_path_segment
return if is_namespace_homepage? || unique_domain_enabled?
project_path
end
def default_url
config_url.tap do |url|
url.scheme = protocol
url.host = hostname
url.port = port
url.path = "/#{path}" if path.present?
end
end
def namespace_url
config_url.tap do |url|
url.port = port
if namespace_in_path?
url.path = "/#{project_namespace}"
else
url.host.prepend("#{project_namespace}.")
end
end
end
end
end
end