Commit c07311d4 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'jej-pages-to-ce' into 'master'

Adding GitLab Pages to CE

Closes #14605, gitlab-com/infrastructure#1058, gitlab-ee#1333, and #323

See merge request !8463
parents 853314c1 b988faaf
0.2.4
...@@ -47,6 +47,9 @@ gem 'rqrcode-rails3', '~> 0.1.7' ...@@ -47,6 +47,9 @@ gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.0.0' gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
# Browser detection # Browser detection
gem 'browser', '~> 2.2' gem 'browser', '~> 2.2'
......
...@@ -785,6 +785,9 @@ GEM ...@@ -785,6 +785,9 @@ GEM
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (>= 4, < 6) unicorn (>= 4, < 6)
uniform_notifier (1.10.0) uniform_notifier (1.10.0)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.1.0) version_sorter (2.1.0)
virtus (1.0.5) virtus (1.0.5)
axiom-types (~> 0.1) axiom-types (~> 0.1)
...@@ -998,6 +1001,7 @@ DEPENDENCIES ...@@ -998,6 +1001,7 @@ DEPENDENCIES
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 5.1.0) unicorn (~> 5.1.0)
unicorn-worker-killer (~> 0.4.4) unicorn-worker-killer (~> 0.4.4)
validates_hostname (~> 1.0.6)
version_sorter (~> 2.1.0) version_sorter (~> 2.1.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
......
...@@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:plantuml_url, :plantuml_url,
:max_artifacts_size, :max_artifacts_size,
:max_attachment_size, :max_attachment_size,
:max_pages_size,
:metrics_enabled, :metrics_enabled,
:metrics_host, :metrics_host,
:metrics_method_call_threshold, :metrics_method_call_threshold,
......
class Projects::PagesController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_read_pages!, only: [:show]
before_action :authorize_update_pages!, except: [:show]
def show
@domains = @project.pages_domains.order(:domain)
end
def destroy
project.remove_pages
project.pages_domains.destroy_all
respond_to do |format|
format.html do
redirect_to(namespace_project_pages_path(@project.namespace, @project),
notice: 'Pages were removed')
end
end
end
end
class Projects::PagesDomainsController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_update_pages!, except: [:show]
before_action :domain, only: [:show, :destroy]
def show
end
def new
@domain = @project.pages_domains.new
end
def create
@domain = @project.pages_domains.create(pages_domain_params)
if @domain.valid?
redirect_to namespace_project_pages_path(@project.namespace, @project)
else
render 'new'
end
end
def destroy
@domain.destroy
respond_to do |format|
format.html do
redirect_to(namespace_project_pages_path(@project.namespace, @project),
notice: 'Domain was removed')
end
format.js
end
end
private
def pages_domain_params
params.require(:pages_domain).permit(
:certificate,
:key,
:domain
)
end
def domain
@domain ||= @project.pages_domains.find_by(domain: params[:id].to_s)
end
end
...@@ -256,7 +256,7 @@ module Ci ...@@ -256,7 +256,7 @@ module Ci
end end
def project_id def project_id
pipeline.project_id gl_project_id
end end
def project_name def project_name
...@@ -451,6 +451,7 @@ module Ci ...@@ -451,6 +451,7 @@ module Ci
build_data = Gitlab::DataBuilder::Build.build(self) build_data = Gitlab::DataBuilder::Build.build(self)
project.execute_hooks(build_data.dup, :build_hooks) project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks)
PagesService.new(build_data).execute
project.running_or_pending_build_count(force: true) project.running_or_pending_build_count(force: true)
end end
......
...@@ -130,6 +130,7 @@ class Namespace < ActiveRecord::Base ...@@ -130,6 +130,7 @@ class Namespace < ActiveRecord::Base
end end
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
Gitlab::PagesTransfer.new.rename_namespace(path_was, path)
remove_exports! remove_exports!
......
class PagesDomain < ActiveRecord::Base
belongs_to :project
validates :domain, hostname: true
validates_uniqueness_of :domain, case_sensitive: false
validates :certificate, certificate: true, allow_nil: true, allow_blank: true
validates :key, certificate_key: true, allow_nil: true, allow_blank: true
validate :validate_pages_domain
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }
attr_encrypted :key,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
after_create :update
after_save :update
after_destroy :update
def to_param
domain
end
def url
return unless domain
if certificate
"https://#{domain}"
else
"http://#{domain}"
end
end
def has_matching_key?
return false unless x509
return false unless pkey
# We compare the public key stored in certificate with public key from certificate key
x509.check_private_key(pkey)
end
def has_intermediates?
return false unless x509
# self-signed certificates doesn't have the certificate chain
return true if x509.verify(x509.public_key)
store = OpenSSL::X509::Store.new
store.set_default_paths
# This forces to load all intermediate certificates stored in `certificate`
Tempfile.open('certificate_chain') do |f|
f.write(certificate)
f.flush
store.add_file(f.path)
end
store.verify(x509)
rescue OpenSSL::X509::StoreError
false
end
def expired?
return false unless x509
current = Time.new
current < x509.not_before || x509.not_after < current
end
def subject
return unless x509
x509.subject.to_s
end
def certificate_text
@certificate_text ||= x509.try(:to_text)
end
private
def update
::Projects::UpdatePagesConfigurationService.new(project).execute
end
def validate_matching_key
unless has_matching_key?
self.errors.add(:key, "doesn't match the certificate")
end
end
def validate_intermediates
unless has_intermediates?
self.errors.add(:certificate, 'misses intermediates')
end
end
def validate_pages_domain
return unless domain
if domain.downcase.ends_with?(".#{Settings.pages.host}".downcase)
self.errors.add(:domain, "*.#{Settings.pages.host} is restricted")
end
end
def x509
return unless certificate
@x509 ||= OpenSSL::X509::Certificate.new(certificate)
rescue OpenSSL::X509::CertificateError
nil
end
def pkey
return unless key
@pkey ||= OpenSSL::PKey::RSA.new(key)
rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
nil
end
end
...@@ -53,6 +53,8 @@ class Project < ActiveRecord::Base ...@@ -53,6 +53,8 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at) update_column(:last_activity_at, self.created_at)
end end
after_destroy :remove_pages
# update visibility_level of forks # update visibility_level of forks
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
def update_forks_visibility_level def update_forks_visibility_level
...@@ -148,6 +150,7 @@ class Project < ActiveRecord::Base ...@@ -148,6 +150,7 @@ class Project < ActiveRecord::Base
has_many :lfs_objects, through: :lfs_objects_projects has_many :lfs_objects, through: :lfs_objects_projects
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group has_many :invited_groups, through: :project_group_links, source: :group
has_many :pages_domains, dependent: :destroy
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy, as: :source has_many :notification_settings, dependent: :destroy, as: :source
...@@ -955,6 +958,7 @@ class Project < ActiveRecord::Base ...@@ -955,6 +958,7 @@ class Project < ActiveRecord::Base
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.path)
end end
# Expires various caches before a project is renamed. # Expires various caches before a project is renamed.
...@@ -1156,6 +1160,45 @@ class Project < ActiveRecord::Base ...@@ -1156,6 +1160,45 @@ class Project < ActiveRecord::Base
ensure_runners_token! ensure_runners_token!
end end
def pages_deployed?
Dir.exist?(public_pages_path)
end
def pages_url
# The hostname always needs to be in downcased
# All web servers convert hostname to lowercase
host = "#{namespace.path}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
"#{prefix}#{namespace.path}."
end.downcase
# If the project path is the same as host, we serve it as group page
return url if host == path
"#{url}/#{path}"
end
def pages_path
File.join(Settings.pages.path, path_with_namespace)
end
def public_pages_path
File.join(pages_path, 'public')
end
def remove_pages
# 1. We rename pages to temporary directory
# 2. We wait 5 minutes, due to NFS caching
# 3. We asynchronously remove pages with force
temp_path = "#{path}.#{SecureRandom.hex}.deleted"
if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path)
PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path)
end
end
def wiki def wiki
@wiki ||= ProjectWiki.new(self, self.owner) @wiki ||= ProjectWiki.new(self, self.owner)
end end
......
...@@ -110,6 +110,9 @@ class ProjectPolicy < BasePolicy ...@@ -110,6 +110,9 @@ class ProjectPolicy < BasePolicy
can! :admin_pipeline can! :admin_pipeline
can! :admin_environment can! :admin_environment
can! :admin_deployment can! :admin_deployment
can! :admin_pages
can! :read_pages
can! :update_pages
end end
def public_access! def public_access!
...@@ -136,6 +139,7 @@ class ProjectPolicy < BasePolicy ...@@ -136,6 +139,7 @@ class ProjectPolicy < BasePolicy
can! :remove_fork_project can! :remove_fork_project
can! :destroy_merge_request can! :destroy_merge_request
can! :destroy_issue can! :destroy_issue
can! :remove_pages
end end
def team_member_owner_access! def team_member_owner_access!
......
class PagesService
attr_reader :data
def initialize(data)
@data = data
end
def execute
return unless Settings.pages.enabled
return unless data[:build_name] == 'pages'
return unless data[:build_status] == 'success'
PagesWorker.perform_async(:deploy, data[:build_id])
end
end
...@@ -64,6 +64,9 @@ module Projects ...@@ -64,6 +64,9 @@ module Projects
# Move uploads # Move uploads
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
# Move pages
Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path project.old_path_with_namespace = old_path
SystemHooksService.new.execute_hooks_for(project, :transfer) SystemHooksService.new.execute_hooks_for(project, :transfer)
......
module Projects
class UpdatePagesConfigurationService < BaseService
attr_reader :project
def initialize(project)
@project = project
end
def execute
update_file(pages_config_file, pages_config.to_json)
reload_daemon
success
rescue => e
error(e.message)
end
private
def pages_config
{
domains: pages_domains_config
}
end
def pages_domains_config
project.pages_domains.map do |domain|
{
domain: domain.domain,
certificate: domain.certificate,
key: domain.key,
}
end
end
def reload_daemon
# GitLab Pages daemon constantly watches for modification time of `pages.path`
# It reloads configuration when `pages.path` is modified
update_file(pages_update_file, SecureRandom.hex(64))
end
def pages_path
@pages_path ||= project.pages_path
end
def pages_config_file
File.join(pages_path, 'config.json')
end
def pages_update_file
File.join(::Settings.pages.path, '.update')
end
def update_file(file, data)
unless data
FileUtils.remove(file, force: true)
return
end
temp_file = "#{file}.#{SecureRandom.hex(16)}"
File.open(temp_file, 'w') do |f|
f.write(data)
end
FileUtils.move(temp_file, file, force: true)
ensure
# In case if the updating fails
FileUtils.remove(temp_file, force: true)
end
end
end
module Projects
class UpdatePagesService < BaseService
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'
attr_reader :build
def initialize(project, build)
@project, @build = project, build
end
def execute
# Create status notifying the deployment of pages
@status = create_status
@status.enqueue!
@status.run!
raise 'missing pages artifacts' unless build.artifacts_file?
raise 'pages are outdated' unless latest?
# Create temporary directory in which we will extract the artifacts
FileUtils.mkdir_p(tmp_path)
Dir.mktmpdir(nil, tmp_path) do |archive_path|
extract_archive!(archive_path)
# Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public')
raise 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise 'pages are outdated' unless latest?
deploy_page!(archive_public_path)
success
end
rescue => e
error(e.message)
end
private
def success
@status.success
super
end
def error(message, http_status = nil)
@status.allow_failure = !latest?
@status.description = message
@status.drop
super
end
def create_status
GenericCommitStatus.new(
project: project,
pipeline: build.pipeline,
user: build.user,
ref: build.ref,
stage: 'deploy',
name: 'pages:deploy'
)
end
def extract_archive!(temp_path)
if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz')
extract_tar_archive!(temp_path)
elsif artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path)
else
raise 'unsupported artifacts format'
end
end
def extract_tar_archive!(temp_path)
results = Open3.pipeline(%W(gunzip -c #{artifacts}),
%W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
%W(tar -x -C #{temp_path} #{SITE_PATH}),
err: '/dev/null')
raise 'pages failed to extract' unless results.compact.all?(&:success?)
end
def extract_zip_archive!(temp_path)
raise 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size
raise "artifacts for pages are too large: #{public_entry.total_size}"
end
# Requires UnZip at least 6.00 Info-ZIP.
# -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*')
unless system(*%W(unzip -n #{artifacts} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
end
end
def deploy_page!(archive_public_path)
# Do atomic move of pages
# Move and removal may not be atomic, but they are significantly faster then extracting and removal
# 1. We move deployed public to previous public path (file removal is slow)
# 2. We move temporary public to be deployed public
# 3. We remove previous public path
FileUtils.mkdir_p(pages_path)
begin
FileUtils.move(public_path, previous_public_path)
rescue
end
FileUtils.move(archive_public_path, public_path)
ensure
FileUtils.rm_r(previous_public_path, force: true)
end
def latest?
# check if sha for the ref is still the most recent one
# this helps in case when multiple deployments happens
sha == latest_sha
end
def blocks
# Calculate dd parameters: we limit the size of pages
1 + max_size / BLOCK_SIZE
end
def max_size
current_application_settings.max_pages_size.megabytes || MAX_SIZE
end
def tmp_path
@tmp_path ||= File.join(::Settings.pages.path, 'tmp')
end
def pages_path
@pages_path ||= project.pages_path
end
def public_path
@public_path ||= File.join(pages_path, 'public')
end
def previous_public_path
@previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}")
end
def ref
build.ref
end
def artifacts
build.artifacts_file.path
end
def latest_sha
project.commit(build.ref).try(:sha).to_s
end
def sha
build.sha
end
end
end
# UrlValidator
#
# Custom validator for private keys.
#
# class Project < ActiveRecord::Base
# validates :certificate_key, certificate_key: true
# end
#
class CertificateKeyValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_private_key_pem?(value)
record.errors.add(attribute, "must be a valid PEM private key")
end
end
private
def valid_private_key_pem?(value)
return false unless value
pkey = OpenSSL::PKey::RSA.new(value)
pkey.private?
rescue OpenSSL::PKey::PKeyError
false
end
end
# UrlValidator
#
# Custom validator for private keys.
#
# class Project < ActiveRecord::Base
# validates :certificate_key, certificate: true
# end
#
class CertificateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_certificate_pem?(value)
record.errors.add(attribute, "must be a valid PEM certificate")
end
end
private
def valid_certificate_pem?(value)
return false unless value
OpenSSL::X509::Certificate.new(value).present?
rescue OpenSSL::X509::CertificateError
false
end
end
...@@ -186,6 +186,14 @@ ...@@ -186,6 +186,14 @@
= f.text_area :help_page_text, class: 'form-control', rows: 4 = f.text_area :help_page_text, class: 'form-control', rows: 4
.help-block Markdown enabled .help-block Markdown enabled
%fieldset
%legend Pages
.form-group
= f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :max_pages_size, class: 'form-control'
.help-block Zero for unlimited
%fieldset %fieldset
%legend Continuous Integration %legend Continuous Integration
.form-group .form-group
......
...@@ -34,3 +34,7 @@ ...@@ -34,3 +34,7 @@
= link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span %span
CI/CD Pipelines CI/CD Pipelines
= nav_link(controller: :pages) do
= link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages', data: {placement: 'right'} do
%span
Pages
...@@ -133,6 +133,7 @@ ...@@ -133,6 +133,7 @@
%hr %hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
.row.prepend-top-default .row.prepend-top-default
%hr %hr
.row.prepend-top-default .row.prepend-top-default
......
- if @project.pages_deployed?
.panel.panel-default
.panel-heading
Access pages
.panel-body
%p
%strong
Congratulations! Your pages are served under:
%p= link_to @project.pages_url, @project.pages_url
- @project.pages_domains.each do |domain|
%p= link_to domain.url, domain.url
- if @project.pages_deployed?
- if can?(current_user, :remove_pages, @project)
.panel.panel-default.panel.panel-danger
.panel-heading Remove pages
.errors-holder
.panel-body
%p
Removing the pages will prevent from exposing them to outside world.
.form-actions
= link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else
.nothing-here-block Only the project owner can remove pages
.panel.panel-default
.nothing-here-block
GitLab Pages are disabled.
Ask your system's administrator to enable it.
- if can?(current_user, :update_pages, @project) && @domains.any?
.panel.panel-default
.panel-heading
Domains (#{@domains.count})
%ul.well-list
- @domains.each do |domain|
%li
.pull-right
= link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped"
= link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix
%span= link_to domain.domain, domain.url
%p
- if domain.subject
%span.label.label-gray Certificate: #{domain.subject}
- if domain.expired?
%span.label.label-danger Expired
- if can?(current_user, :update_pages, @project)
.panel.panel-default
.panel-heading
Domains
.nothing-here-block
Support for domains and certificates is disabled.
Ask your system's administrator to enable it.
- unless @project.pages_deployed?
.panel.panel-info
.panel-heading
Configure pages
.panel-body
%p
Learn how to upload your static site and have it served by
GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}.
- page_title 'Pages'
%h3.page_title
Pages
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
= link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do
%i.fa.fa-plus
New Domain
%p.light
With GitLab Pages you can host your static websites on GitLab.
Combined with the power of GitLab CI and the help of GitLab Runner
you can deploy static pages for your individual projects, your user or your group.
%hr.clearfix
- if Gitlab.config.pages.enabled
= render 'access'
= render 'use'
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
= render 'list'
- else
= render 'no_domains'
= render 'destroy'
- else
= render 'disabled'
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f|
- if @domain.errors.any?
#error_explanation
.alert.alert-danger
- @domain.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :domain, class: 'control-label' do
Domain
.col-sm-10
= f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control'
- if Gitlab.config.pages.external_https
.form-group
= f.label :certificate, class: 'control-label' do
Certificate (PEM)
.col-sm-10
= f.text_area :certificate, rows: 5, class: 'form-control'
%span.help-inline Upload a certificate for your domain with all intermediates
.form-group
= f.label :key, class: 'control-label' do
Key (PEM)
.col-sm-10
= f.text_area :key, rows: 5, class: 'form-control'
%span.help-inline Upload a private key for your certificate
- else
.nothing-here-block
Support for custom certificates is disabled.
Ask your system's administrator to enable it.
.form-actions
= f.submit 'Create New Domain', class: "btn btn-save"
- page_title 'New Pages Domain'
%h3.page_title
New Pages Domain
%hr.clearfix
%div
= render 'form'
- page_title "#{@domain.domain}", 'Pages Domains'
%h3.page-title
Pages Domain
.table-holder
%table.table
%tr
%td
Domain
%td
= link_to @domain.domain, @domain.url
%tr
%td
DNS
%td
%p
To access the domain create a new DNS record:
%pre
#{@domain.domain} CNAME #{@domain.project.namespace.path}.#{Settings.pages.host}.
%tr
%td
Certificate
%td
- if @domain.certificate_text
%pre
= @domain.certificate_text
- else
.light
missing
class PagesWorker
include Sidekiq::Worker
sidekiq_options queue: :pages, retry: false
def perform(action, *arg)
send(action, *arg)
end
def deploy(build_id)
build = Ci::Build.find_by(id: build_id)
result = Projects::UpdatePagesService.new(build.project, build).execute
if result[:status] == :success
result = Projects::UpdatePagesConfigurationService.new(build.project).execute
end
result
end
def remove(namespace_path, project_path)
full_path = File.join(Settings.pages.path, namespace_path, project_path)
FileUtils.rm_r(full_path, force: true)
end
end
---
title: Added GitLab Pages to CE
merge_request: 8463
author:
...@@ -153,6 +153,21 @@ production: &base ...@@ -153,6 +153,21 @@ production: &base
# The location where LFS objects are stored (default: shared/lfs-objects). # The location where LFS objects are stored (default: shared/lfs-objects).
# storage_path: shared/lfs-objects # storage_path: shared/lfs-objects
## GitLab Pages
pages:
enabled: false
# The location where pages are stored (default: shared/pages).
# path: shared/pages
# The domain under which the pages are served:
# http://group.example.com/project
# or project path can be a group page: group.example.com
host: example.com
port: 80 # Set to 443 if you serve the pages with HTTPS
https: false # Set to true if you serve the pages with HTTPS
# external_http: "1.1.1.1:80" # If defined, enables custom domain support in GitLab Pages
# external_https: "1.1.1.1:443" # If defined, enables custom domain and certificate support in GitLab Pages
## Mattermost ## Mattermost
## For enabling Add to Mattermost button ## For enabling Add to Mattermost button
mattermost: mattermost:
......
...@@ -6,7 +6,7 @@ class Settings < Settingslogic ...@@ -6,7 +6,7 @@ class Settings < Settingslogic
class << self class << self
def gitlab_on_standard_port? def gitlab_on_standard_port?
gitlab.port.to_i == (gitlab.https ? 443 : 80) on_standard_port?(gitlab)
end end
def host_without_www(url) def host_without_www(url)
...@@ -14,7 +14,7 @@ class Settings < Settingslogic ...@@ -14,7 +14,7 @@ class Settings < Settingslogic
end end
def build_gitlab_ci_url def build_gitlab_ci_url
if gitlab_on_standard_port? if on_standard_port?(gitlab)
custom_port = nil custom_port = nil
else else
custom_port = ":#{gitlab.port}" custom_port = ":#{gitlab.port}"
...@@ -27,6 +27,10 @@ class Settings < Settingslogic ...@@ -27,6 +27,10 @@ class Settings < Settingslogic
].join('') ].join('')
end end
def build_pages_url
base_url(pages).join('')
end
def build_gitlab_shell_ssh_path_prefix def build_gitlab_shell_ssh_path_prefix
user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}"
...@@ -42,11 +46,11 @@ class Settings < Settingslogic ...@@ -42,11 +46,11 @@ class Settings < Settingslogic
end end
def build_base_gitlab_url def build_base_gitlab_url
base_gitlab_url.join('') base_url(gitlab).join('')
end end
def build_gitlab_url def build_gitlab_url
(base_gitlab_url + [gitlab.relative_url_root]).join('') (base_url(gitlab) + [gitlab.relative_url_root]).join('')
end end
# check that values in `current` (string or integer) is a contant in `modul`. # check that values in `current` (string or integer) is a contant in `modul`.
...@@ -74,15 +78,19 @@ class Settings < Settingslogic ...@@ -74,15 +78,19 @@ class Settings < Settingslogic
private private
def base_gitlab_url def base_url(config)
custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" custom_port = on_standard_port?(config) ? nil : ":#{config.port}"
[ gitlab.protocol, [ config.protocol,
"://", "://",
gitlab.host, config.host,
custom_port custom_port
] ]
end end
def on_standard_port?(config)
config.port.to_i == (config.https ? 443 : 80)
end
# Extract the host part of the given +url+. # Extract the host part of the given +url+.
def host(url) def host(url)
url = url.downcase url = url.downcase
...@@ -254,6 +262,20 @@ Settings.registry['issuer'] ||= nil ...@@ -254,6 +262,20 @@ Settings.registry['issuer'] ||= nil
Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':')
Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root) Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root)
#
# Pages
#
Settings['pages'] ||= Settingslogic.new({})
Settings.pages['enabled'] = false if Settings.pages['enabled'].nil?
Settings.pages['path'] = File.expand_path(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"), Rails.root)
Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['host'] ||= "example.com"
Settings.pages['port'] ||= Settings.pages.https ? 443 : 80
Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http"
Settings.pages['url'] ||= Settings.send(:build_pages_url)
Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil?
Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil?
# #
# Git LFS # Git LFS
# #
......
...@@ -39,6 +39,10 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -39,6 +39,10 @@ constraints(ProjectUrlConstrainer.new) do
end end
end end
resource :pages, only: [:show, :destroy] do
resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ }
end
resources :compare, only: [:index, :create] do resources :compare, only: [:index, :create] do
collection do collection do
get :diff_for_path get :diff_for_path
......
...@@ -50,3 +50,4 @@ ...@@ -50,3 +50,4 @@
- [reactive_caching, 1] - [reactive_caching, 1]
- [cronjob, 1] - [cronjob, 1]
- [default, 1] - [default, 1]
- [pages, 1]
class AddPagesSizeToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default :application_settings, :max_pages_size, :integer, default: 100, allow_null: false
end
def down
remove_column(:application_settings, :max_pages_size)
end
end
class CreatePagesDomain < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :pages_domains do |t|
t.integer :project_id
t.text :certificate
t.text :encrypted_key
t.string :encrypted_key_iv
t.string :encrypted_key_salt
t.string :domain
end
add_index :pages_domains, :domain, unique: true
end
end
...@@ -98,17 +98,18 @@ ActiveRecord::Schema.define(version: 20170204181513) do ...@@ -98,17 +98,18 @@ ActiveRecord::Schema.define(version: 20170204181513) do
t.text "help_page_text_html" t.text "help_page_text_html"
t.text "shared_runners_text_html" t.text "shared_runners_text_html"
t.text "after_sign_up_text_html" t.text "after_sign_up_text_html"
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "housekeeping_enabled", default: true, null: false t.boolean "housekeeping_enabled", default: true, null: false
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_incremental_repack_period", default: 10, null: false
t.integer "housekeeping_full_repack_period", default: 50, null: false t.integer "housekeeping_full_repack_period", default: 50, null: false
t.integer "housekeeping_gc_period", default: 200, null: false t.integer "housekeeping_gc_period", default: 200, null: false
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "html_emails_enabled", default: true t.boolean "html_emails_enabled", default: true
t.string "plantuml_url" t.string "plantuml_url"
t.boolean "plantuml_enabled" t.boolean "plantuml_enabled"
t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false t.integer "terminal_max_session_time", default: 0, null: false
end end
...@@ -857,6 +858,17 @@ ActiveRecord::Schema.define(version: 20170204181513) do ...@@ -857,6 +858,17 @@ ActiveRecord::Schema.define(version: 20170204181513) do
add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
create_table "pages_domains", force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
t.text "encrypted_key"
t.string "encrypted_key_iv"
t.string "encrypted_key_salt"
t.string "domain"
end
add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
create_table "personal_access_tokens", force: :cascade do |t| create_table "personal_access_tokens", force: :cascade do |t|
t.integer "user_id", null: false t.integer "user_id", null: false
t.string "token", null: false t.string "token", null: false
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry.
- [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [GitLab Pages](user/project/pages/index.md) Using GitLab Pages.
- [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab. - [Importing to GitLab](workflow/importing/README.md) Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
- [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Markdown](user/markdown.md) GitLab's advanced formatting system. - [Markdown](user/markdown.md) GitLab's advanced formatting system.
...@@ -53,6 +54,7 @@ ...@@ -53,6 +54,7 @@
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
- [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Pages configuration](administration/pages/index.md) Configure GitLab Pages.
- [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. - [GitLab performance monitoring with InfluxDB](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
- [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics. - [GitLab performance monitoring with Prometheus](administration/monitoring/performance/prometheus.md) Configure GitLab and Prometheus for measuring performance metrics.
- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. - [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests.
......
...@@ -66,4 +66,4 @@ Read more on high-availability configuration: ...@@ -66,4 +66,4 @@ Read more on high-availability configuration:
configure custom domains with custom SSL, which would not be possible configure custom domains with custom SSL, which would not be possible
if SSL was terminated at the load balancer. if SSL was terminated at the load balancer.
[gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html [gitlab-pages]: ../pages/index.md
# GitLab Pages Administration
> **Notes:**
- [Introduced][ee-80] in GitLab EE 8.3.
- Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
- GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
- This guide is for Omnibus GitLab installations. If you have installed
GitLab from source, follow the [Pages source installation document](source.md).
---
This document describes how to set up the _latest_ GitLab Pages feature. Make
sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
version as it may include new features and changes needed to be made in your
configuration.
If you are looking for ways to upload your static content in GitLab Pages, you
probably want to read the [user documentation][pages-userguide].
## Overview
GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
written in Go that can listen on an external IP address and provide support for
custom domains and custom certificates. It supports dynamic certificates through
SNI and exposes pages using HTTP2 by default.
You are encouraged to read its [README][pages-readme] to fully understand how
it works.
---
In the case of custom domains, the Pages daemon needs to listen on ports `80`
and/or `443`. For that reason, there is some flexibility in the way which you
can set it up:
1. Run the pages daemon in the same server as GitLab, listening on a secondary IP
1. Run the pages daemon in a separate server. In that case, the
[Pages path](#change-storage-path) must also be present in the server that
the pages daemon is installed, so you will have to share it via network.
1. Run the pages daemon in the same server as GitLab, listening on the same IP
but on different ports. In that case, you will have to proxy the traffic with
a loadbalancer. If you choose that route note that you should use TCP load
balancing for HTTPS. If you use TLS-termination (HTTPS-load balancing) the
pages will not be able to be served with user provided certificates. For
HTTP it's OK to use HTTP or TCP load balancing.
In this document, we will proceed assuming the first option.
## Prerequisites
Before proceeding with the Pages configuration, you will need to:
1. Have a separate domain under which the GitLab Pages will be served. In this
document we assume that to be `example.io`.
1. Configure a **wildcard DNS record**.
1. (Optional) Have a **wildcard certificate** for that domain if you decide to
serve Pages under HTTPS.
1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md)
so that your users don't have to bring their own.
### DNS configuration
GitLab Pages expect to run on their own virtual host. In your DNS server/provider
you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this:
```
*.example.io. 1800 IN A 1.2.3.4
```
where `example.io` is the domain under which GitLab Pages will be served
and `1.2.3.4` is the IP address of your GitLab instance.
> **Note:**
You should not use the GitLab domain to serve user pages. For more information
see the [security section](#security).
[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record
## Configuration
Depending on your needs, you can install GitLab Pages in four different ways.
### Option 1. Custom domains with HTTPS support
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `https://page.example.io` and `https://page.com` | yes | redirects to HTTPS | yes | yes |
Pages enabled, daemon is enabled AND pages has external IP support enabled.
In that case, the pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains and TLS are supported.
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
pages_external_url "https://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
pages_nginx['enable'] = false
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
gitlab_pages['external_http'] = '1.1.1.2:80'
gitlab_pages['external_https'] = '1.1.1.2:443'
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
1. [Reconfigure GitLab][reconfigure]
### Option 2. Custom domains without HTTPS support
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `http://page.example.io` and `http://page.com` | no | yes | no | yes |
Pages enabled, daemon is enabled AND pages has external IP support enabled.
In that case, the pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
world. Custom domains and TLS are supported.
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
pages_external_url "http://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
pages_nginx['enable'] = false
gitlab_pages['external_http'] = '1.1.1.2:80'
```
where `1.1.1.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` the secondary IP where the GitLab Pages daemon listens to.
1. [Reconfigure GitLab][reconfigure]
### Option 3. Wildcard HTTPS domain without custom domains
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `https://page.example.io` | yes | no | no | no |
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
daemon. Pages daemon doesn't listen to the outside world.
1. Place the certificate and key inside `/etc/gitlab/ssl`
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
```ruby
pages_external_url 'https://example.io'
pages_nginx['redirect_http_to_https'] = true
pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt"
pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key"
```
where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
respectively.
1. [Reconfigure GitLab][reconfigure]
### Option 4. Wildcard HTTP domain without custom domains
| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP |
| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `http://page.example.io` | no | no | no | no |
Pages enabled, daemon is enabled and NGINX will proxy all requests to the
daemon. Pages daemon doesn't listen to the outside world.
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
```ruby
pages_external_url 'http://example.io'
```
1. [Reconfigure GitLab][reconfigure]
## Change storage path
Follow the steps below to change the default path where GitLab Pages' contents
are stored.
1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`.
If you wish to store them in another location you must set it up in
`/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['pages_path'] = "/mnt/storage/pages"
```
1. [Reconfigure GitLab][reconfigure]
## Set maximum pages size
The maximum size of the unpacked archive per project can be configured in the
Admin area under the Application settings in the **Maximum size of pages (MB)**.
The default is 100MB.
## Backup
Pages are part of the [regular backup][backup] so there is nothing to configure.
## Security
You should strongly consider running GitLab pages under a different hostname
than GitLab to prevent XSS attacks.
## Changelog
GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features
where added, like custom CNAME and TLS support, and many more are likely to
come. Below is a brief changelog. If no changes were introduced or a version is
missing from the changelog, assume that the documentation is the same as the
latest previous version.
---
**GitLab 8.17 ([documentation][8-17-docs])**
- GitLab Pages were ported to Community Edition in GitLab 8.17.
- Documentation was refactored to be more modular and easy to follow.
**GitLab 8.5 ([documentation][8-5-docs])**
- In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the
recommended way to set up GitLab Pages.
- The [NGINX configs][] have changed to reflect this change. So make sure to
update them.
- Custom CNAME and TLS certificates support.
- Documentation was moved to one place.
**GitLab 8.3 ([documentation][8-3-docs])**
- GitLab Pages feature was introduced.
[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md
[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md
[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md
[backup]: ../raketasks/backup_restore.md
[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages
[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx
[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md
[pages-userguide]: ../../user/project/pages/index.md
[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../administration/restart_gitlab.md#installations-from-source
[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4
This diff is collapsed.
...@@ -1319,6 +1319,35 @@ with an API call. ...@@ -1319,6 +1319,35 @@ with an API call.
[Read more in the triggers documentation.](../triggers/README.md) [Read more in the triggers documentation.](../triggers/README.md)
### pages
`pages` is a special job that is used to upload static content to GitLab that
can be used to serve your website. It has a special syntax, so the two
requirements below must be met:
1. Any static content must be placed under a `public/` directory
1. `artifacts` with a path to the `public/` directory must be defined
The example below simply moves all files from the root of the project to the
`public/` directory. The `.public` workaround is so `cp` doesn't also copy
`public/` to itself in an infinite loop:
```
pages:
stage: deploy
script:
- mkdir .public
- cp -r * .public
- mv .public public
artifacts:
paths:
- public
only:
- master
```
Read more on [GitLab Pages user documentation](../../pages/README.md).
## Validate the .gitlab-ci.yml ## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint. Each instance of GitLab CI has an embedded debug tool called Lint.
......
...@@ -313,6 +313,9 @@ sudo usermod -aG redis git ...@@ -313,6 +313,9 @@ sudo usermod -aG redis git
# Change the permissions of the directory where CI artifacts are stored # Change the permissions of the directory where CI artifacts are stored
sudo chmod -R u+rwX shared/artifacts/ sudo chmod -R u+rwX shared/artifacts/
# Change the permissions of the directory where GitLab Pages are stored
sudo chmod -R ug+rwX shared/pages/
# Copy the example Unicorn config # Copy the example Unicorn config
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
...@@ -484,6 +487,10 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat ...@@ -484,6 +487,10 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat
# or else sudo rm -f /etc/nginx/sites-enabled/default # or else sudo rm -f /etc/nginx/sites-enabled/default
sudo editor /etc/nginx/sites-available/gitlab sudo editor /etc/nginx/sites-available/gitlab
If you intend to enable GitLab pages, there is a separate Nginx config you need
to use. Read all about the needed configuration at the
[GitLab Pages administration guide](../administration/pages/index.md).
**Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details.
### Test Configuration ### Test Configuration
......
This document was moved to [user/project/pages](../user/project/pages/index.md).
This document was moved to [administration/pages](../administration/pages/index.md).
...@@ -84,6 +84,29 @@ Deleting tmp directories...[DONE] ...@@ -84,6 +84,29 @@ Deleting tmp directories...[DONE]
Deleting old backups... [SKIPPING] Deleting old backups... [SKIPPING]
``` ```
## Exclude specific directories from the backup
You can choose what should be backed up by adding the environment variable `SKIP`.
The available options are:
* `db`
* `uploads` (attachments)
* `repositories`
* `builds` (CI build output logs)
* `artifacts` (CI build artifacts)
* `lfs` (LFS objects)
* `pages` (pages content)
Use a comma to specify several options at the same time:
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
# if you've installed GitLab from source
sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=production
```
## Upload backups to remote (cloud) storage ## Upload backups to remote (cloud) storage
Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
......
...@@ -91,7 +91,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -91,7 +91,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) 1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) 1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
1. [GitLab Pages Documentation](https://docs.gitlab.com/ee/pages/README.html) 1. [GitLab Pages Documentation](https://docs.gitlab.com/ce/user/project/pages/)
#### 2.2. GitLab Issues #### 2.2. GitLab Issues
......
...@@ -172,7 +172,7 @@ Move on to understanding some of GitLab's more advanced features. You can make u ...@@ -172,7 +172,7 @@ Move on to understanding some of GitLab's more advanced features. You can make u
- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings - Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html) - Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
- Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html) - Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html)
- Create your first [GitLab Page](https://docs.gitlab.com/ee/pages/administration.html) - Create your first [GitLab Page](https://docs.gitlab.com/ce/administration/pages/)
- Get to know the GitLab Codebase by reading through the source code: - Get to know the GitLab Codebase by reading through the source code:
- Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce) - Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce) and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
......
...@@ -62,11 +62,14 @@ The following table depicts the various user permission levels in a project. ...@@ -62,11 +62,14 @@ The following table depicts the various user permission levels in a project.
| Manage runners | | | | ✓ | ✓ | | Manage runners | | | | ✓ | ✓ |
| Manage build triggers | | | | ✓ | ✓ | | Manage build triggers | | | | ✓ | ✓ |
| Manage variables | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ |
| Manage pages | | | | ✓ | ✓ |
| Manage pages domains and certificates | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ | | Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ | | Remove project | | | | | ✓ |
| Force push to protected branches [^3] | | | | | | | Force push to protected branches [^3] | | | | | |
| Remove protected branches [^3] | | | | | | | Remove protected branches [^3] | | | | | |
| Remove pages | | | | | ✓ |
## Group ## Group
......
doc/user/project/pages/img/pages_create_project.png

32.8 KB

doc/user/project/pages/img/pages_create_user_page.png

85 KB

doc/user/project/pages/img/pages_dns_details.png

33.9 KB

doc/user/project/pages/img/pages_multiple_domains.png

62.2 KB

doc/user/project/pages/img/pages_new_domain_button.png

49.9 KB

doc/user/project/pages/img/pages_remove.png

26.6 KB

doc/user/project/pages/img/pages_upload_cert.png

101 KB

This diff is collapsed.
...@@ -53,6 +53,13 @@ Feature: Project Active Tab ...@@ -53,6 +53,13 @@ Feature: Project Active Tab
And no other sub navs should be active And no other sub navs should be active
And the active main tab should be Settings And the active main tab should be Settings
Scenario: On Project Settings/Pages
Given I visit my project's settings page
And I click the "Pages" tab
Then the active sub nav should be Pages
And no other sub navs should be active
And the active main tab should be Settings
Scenario: On Project Members Scenario: On Project Members
Given I visit my project's members page Given I visit my project's members page
Then the active sub nav should be Members Then the active sub nav should be Members
......
Feature: Project Pages
Background:
Given I sign in as a user
And I own a project
Scenario: Pages are disabled
Given pages are disabled
When I visit the Project Pages
Then I should see that GitLab Pages are disabled
Scenario: I can see the pages usage if not deployed
Given pages are enabled
When I visit the Project Pages
Then I should see the usage of GitLab Pages
Scenario: I can access the pages if deployed
Given pages are enabled
And pages are deployed
When I visit the Project Pages
Then I should be able to access the Pages
Scenario: I should message that domains support is disabled
Given pages are enabled
And pages are deployed
And support for external domains is disabled
When I visit the Project Pages
Then I should see that support for domains is disabled
Scenario: I should see a new domain button
Given pages are enabled
And pages are exposed on external HTTP address
When I visit the Project Pages
And I should be able to add a New Domain
Scenario: I should be able to add a new domain
Given pages are enabled
And pages are exposed on external HTTP address
When I visit add a new Pages Domain
And I fill the domain
And I click on "Create New Domain"
Then I should see a new domain added
Scenario: I should be able to add a new domain for project in group namespace
Given I own a project in some group namespace
And pages are enabled
And pages are exposed on external HTTP address
When I visit add a new Pages Domain
And I fill the domain
And I click on "Create New Domain"
Then I should see a new domain added
Scenario: I should be denied to add the same domain twice
Given pages are enabled
And pages are exposed on external HTTP address
And pages domain is added
When I visit add a new Pages Domain
And I fill the domain
And I click on "Create New Domain"
Then I should see error message that domain already exists
Scenario: I should message that certificates support is disabled when trying to add a new domain
Given pages are enabled
And pages are exposed on external HTTP address
And pages domain is added
When I visit add a new Pages Domain
Then I should see that support for certificates is disabled
Scenario: I should be able to add a new domain with certificate
Given pages are enabled
And pages are exposed on external HTTPS address
When I visit add a new Pages Domain
And I fill the domain
And I fill the certificate and key
And I click on "Create New Domain"
Then I should see a new domain added
Scenario: I can remove the pages if deployed
Given pages are enabled
And pages are deployed
When I visit the Project Pages
And I click Remove Pages
Then The Pages should get removed
...@@ -35,6 +35,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -35,6 +35,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Deploy Keys') click_link('Deploy Keys')
end end
step 'I click the "Pages" tab' do
click_link('Pages')
end
step 'the active sub nav should be Members' do step 'the active sub nav should be Members' do
ensure_active_sub_nav('Members') ensure_active_sub_nav('Members')
end end
...@@ -47,6 +51,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -47,6 +51,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
ensure_active_sub_nav('Deploy Keys') ensure_active_sub_nav('Deploy Keys')
end end
step 'the active sub nav should be Pages' do
ensure_active_sub_nav('Pages')
end
# Sub Tabs: Commits # Sub Tabs: Commits
step 'I click the "Compare" tab' do step 'I click the "Compare" tab' do
......
class Spinach::Features::ProjectPages < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
step 'pages are enabled' do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
allow(Gitlab.config.pages).to receive(:host).and_return('example.com')
allow(Gitlab.config.pages).to receive(:port).and_return(80)
allow(Gitlab.config.pages).to receive(:https).and_return(false)
end
step 'pages are disabled' do
allow(Gitlab.config.pages).to receive(:enabled).and_return(false)
end
step 'I visit the Project Pages' do
visit namespace_project_pages_path(@project.namespace, @project)
end
step 'I should see that GitLab Pages are disabled' do
expect(page).to have_content('GitLab Pages are disabled')
end
step 'I should see the usage of GitLab Pages' do
expect(page).to have_content('Configure pages')
end
step 'pages are deployed' do
pipeline = @project.ensure_pipeline('HEAD', @project.commit('HEAD').sha)
build = build(:ci_build,
project: @project,
pipeline: pipeline,
ref: 'HEAD',
artifacts_file: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip'),
artifacts_metadata: fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta')
)
result = ::Projects::UpdatePagesService.new(@project, build).execute
expect(result[:status]).to eq(:success)
end
step 'I should be able to access the Pages' do
expect(page).to have_content('Access pages')
end
step 'I should see that support for domains is disabled' do
expect(page).to have_content('Support for domains and certificates is disabled')
end
step 'support for external domains is disabled' do
allow(Gitlab.config.pages).to receive(:external_http).and_return(nil)
allow(Gitlab.config.pages).to receive(:external_https).and_return(nil)
end
step 'pages are exposed on external HTTP address' do
allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80')
allow(Gitlab.config.pages).to receive(:external_https).and_return(nil)
end
step 'pages are exposed on external HTTPS address' do
allow(Gitlab.config.pages).to receive(:external_http).and_return('1.1.1.1:80')
allow(Gitlab.config.pages).to receive(:external_https).and_return('1.1.1.1:443')
end
step 'I should be able to add a New Domain' do
expect(page).to have_content('New Domain')
end
step 'I visit add a new Pages Domain' do
visit new_namespace_project_pages_domain_path(@project.namespace, @project)
end
step 'I fill the domain' do
fill_in 'Domain', with: 'my.test.domain.com'
end
step 'I click on "Create New Domain"' do
click_button 'Create New Domain'
end
step 'I should see a new domain added' do
expect(page).to have_content('Domains (1)')
expect(page).to have_content('my.test.domain.com')
end
step 'pages domain is added' do
@project.pages_domains.create!(domain: 'my.test.domain.com')
end
step 'I should see error message that domain already exists' do
expect(page).to have_content('Domain has already been taken')
end
step 'I should see that support for certificates is disabled' do
expect(page).to have_content('Support for custom certificates is disabled')
end
step 'I fill the certificate and key' do
fill_in 'Certificate (PEM)', with: '-----BEGIN CERTIFICATE-----
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
YHi2yesCrOvVXt+lgPTd
-----END CERTIFICATE-----'
fill_in 'Key (PEM)', with: '-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----'
end
step 'I click Remove Pages' do
click_link 'Remove pages'
end
step 'The Pages should get removed' do
expect(@project.pages_deployed?).to be_falsey
end
end
...@@ -7,6 +7,12 @@ module SharedProject ...@@ -7,6 +7,12 @@ module SharedProject
@project.team << [@user, :master] @project.team << [@user, :master]
end end
step "I own a project in some group namespace" do
@group = create(:group, name: 'some group')
@project = create(:project, namespace: @group)
@project.team << [@user, :master]
end
step "project exists in some group namespace" do step "project exists in some group namespace" do
@group = create(:group, name: 'some group') @group = create(:group, name: 'some group')
@project = create(:project, :repository, namespace: @group, public_builds: false) @project = create(:project, :repository, namespace: @group, public_builds: false)
......
...@@ -57,6 +57,7 @@ module API ...@@ -57,6 +57,7 @@ module API
requires :shared_runners_text, type: String, desc: 'Shared runners text ' requires :shared_runners_text, type: String, desc: 'Shared runners text '
end end
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have" optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have"
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
given metrics_enabled: ->(val) { val } do given metrics_enabled: ->(val) { val } do
...@@ -116,7 +117,7 @@ module API ...@@ -116,7 +117,7 @@ module API
:send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled,
:after_sign_up_text, :signin_enabled, :require_two_factor_authentication, :after_sign_up_text, :signin_enabled, :require_two_factor_authentication,
:home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text,
:shared_runners_enabled, :max_artifacts_size, :container_registry_token_expire_delay, :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay,
:metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
:akismet_enabled, :admin_notification_email, :sentry_enabled, :akismet_enabled, :admin_notification_email, :sentry_enabled,
:repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,
......
module Backup module Backup
class Manager class Manager
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts lfs registry] ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry]
FOLDERS_TO_BACKUP = %w[repositories db] FOLDERS_TO_BACKUP = %w[repositories db]
FILE_NAME_SUFFIX = '_gitlab_backup.tar' FILE_NAME_SUFFIX = '_gitlab_backup.tar'
......
require 'backup/files'
module Backup
class Pages < Files
def initialize
super('pages', Gitlab.config.pages.path)
end
def create_files_dir
Dir.mkdir(app_files_dir, 0700)
end
end
end
module Gitlab
class PagesTransfer < ProjectTransfer
def root_dir
Gitlab.config.pages.path
end
end
end
module Gitlab
class ProjectTransfer
def move_project(project_path, namespace_path_was, namespace_path)
new_namespace_folder = File.join(root_dir, namespace_path)
FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
from = File.join(root_dir, namespace_path_was, project_path)
to = File.join(root_dir, namespace_path, project_path)
move(from, to, "")
end
def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir)
end
def rename_namespace(path_was, path)
move(path_was, path)
end
def root_dir
raise NotImplementedError
end
private
def move(path_was, path, base_dir = nil)
base_dir = root_dir unless base_dir
from = File.join(base_dir, path_was)
to = File.join(base_dir, path)
FileUtils.mv(from, to)
rescue Errno::ENOENT
false
end
end
end
module Gitlab module Gitlab
class UploadsTransfer class UploadsTransfer < ProjectTransfer
def move_project(project_path, namespace_path_was, namespace_path)
new_namespace_folder = File.join(root_dir, namespace_path)
FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
from = File.join(root_dir, namespace_path_was, project_path)
to = File.join(root_dir, namespace_path, project_path)
move(from, to, "")
end
def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir)
end
def rename_namespace(path_was, path)
move(path_was, path)
end
private
def move(path_was, path, base_dir = nil)
base_dir = root_dir unless base_dir
from = File.join(base_dir, path_was)
to = File.join(base_dir, path)
FileUtils.mv(from, to)
rescue Errno::ENOENT
false
end
def root_dir def root_dir
File.join(Rails.root, "public", "uploads") File.join(Rails.root, "public", "uploads")
end end
......
...@@ -42,6 +42,11 @@ gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd) ...@@ -42,6 +42,11 @@ gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
gitlab_pages_enabled=false
gitlab_pages_dir=$(cd $app_root/../gitlab-pages 2> /dev/null && pwd)
gitlab_pages_pid_path="$pid_path/gitlab-pages.pid"
gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
gitlab_pages_log="$app_root/log/gitlab-pages.log"
shell_path="/bin/bash" shell_path="/bin/bash"
# Read configuration variable file if it is present # Read configuration variable file if it is present
...@@ -89,13 +94,20 @@ check_pids(){ ...@@ -89,13 +94,20 @@ check_pids(){
mpid=0 mpid=0
fi fi
fi fi
if [ "$gitlab_pages_enabled" = true ]; then
if [ -f "$gitlab_pages_pid_path" ]; then
gppid=$(cat "$gitlab_pages_pid_path")
else
gppid=0
fi
fi
} }
## Called when we have started the two processes and are waiting for their pid files. ## Called when we have started the two processes and are waiting for their pid files.
wait_for_pids(){ wait_for_pids(){
# We are sleeping a bit here mostly because sidekiq is slow at writing its pid # We are sleeping a bit here mostly because sidekiq is slow at writing its pid
i=0; i=0;
while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ] || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; }; do
sleep 0.1; sleep 0.1;
i=$((i+1)) i=$((i+1))
if [ $((i%10)) = 0 ]; then if [ $((i%10)) = 0 ]; then
...@@ -144,7 +156,15 @@ check_status(){ ...@@ -144,7 +156,15 @@ check_status(){
mail_room_status="-1" mail_room_status="-1"
fi fi
fi fi
if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then if [ "$gitlab_pages_enabled" = true ]; then
if [ $gppid -ne 0 ]; then
kill -0 "$gppid" 2>/dev/null
gitlab_pages_status="$?"
else
gitlab_pages_status="-1"
fi
fi
if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; }; then
gitlab_status=0 gitlab_status=0
else else
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
...@@ -186,12 +206,19 @@ check_stale_pids(){ ...@@ -186,12 +206,19 @@ check_stale_pids(){
exit 1 exit 1
fi fi
fi fi
if [ "$gitlab_pages_enabled" = true ] && [ "$gppid" != "0" ] && [ "$gitlab_pages_status" != "0" ]; then
echo "Removing stale GitLab Pages job dispatcher pid. This is most likely caused by GitLab Pages crashing the last time it ran."
if ! rm "$gitlab_pages_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
fi
fi
} }
## If no parts of the service is running, bail out. ## If no parts of the service is running, bail out.
exit_if_not_running(){ exit_if_not_running(){
check_stale_pids check_stale_pids
if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then
echo "GitLab is not running." echo "GitLab is not running."
exit exit
fi fi
...@@ -213,6 +240,9 @@ start_gitlab() { ...@@ -213,6 +240,9 @@ start_gitlab() {
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
echo "Starting GitLab MailRoom" echo "Starting GitLab MailRoom"
fi fi
if [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then
echo "Starting GitLab Pages"
fi
# Then check if the service is running. If it is: don't start again. # Then check if the service is running. If it is: don't start again.
if [ "$web_status" = "0" ]; then if [ "$web_status" = "0" ]; then
...@@ -252,6 +282,16 @@ start_gitlab() { ...@@ -252,6 +282,16 @@ start_gitlab() {
fi fi
fi fi
if [ "$gitlab_pages_enabled" = true ]; then
if [ "$gitlab_pages_status" = "0" ]; then
echo "The GitLab Pages is already running with pid $spid, not restarting"
else
$app_root/bin/daemon_with_pidfile $gitlab_pages_pid_path \
$gitlab_pages_dir/gitlab-pages $gitlab_pages_options \
>> $gitlab_pages_log 2>&1 &
fi
fi
# Wait for the pids to be planted # Wait for the pids to be planted
wait_for_pids wait_for_pids
# Finally check the status to tell wether or not GitLab is running # Finally check the status to tell wether or not GitLab is running
...@@ -278,13 +318,17 @@ stop_gitlab() { ...@@ -278,13 +318,17 @@ stop_gitlab() {
echo "Shutting down GitLab MailRoom" echo "Shutting down GitLab MailRoom"
RAILS_ENV=$RAILS_ENV bin/mail_room stop RAILS_ENV=$RAILS_ENV bin/mail_room stop
fi fi
if [ "$gitlab_pages_status" = "0" ]; then
echo "Shutting down gitlab-pages"
kill -- $(cat $gitlab_pages_pid_path)
fi
# If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script.
while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; do
sleep 1 sleep 1
check_status check_status
printf "." printf "."
if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then
printf "\n" printf "\n"
break break
fi fi
...@@ -298,6 +342,7 @@ stop_gitlab() { ...@@ -298,6 +342,7 @@ stop_gitlab() {
if [ "$mail_room_enabled" = true ]; then if [ "$mail_room_enabled" = true ]; then
rm "$mail_room_pid_path" 2>/dev/null rm "$mail_room_pid_path" 2>/dev/null
fi fi
rm -f "$gitlab_pages_pid_path"
print_status print_status
} }
...@@ -305,7 +350,7 @@ stop_gitlab() { ...@@ -305,7 +350,7 @@ stop_gitlab() {
## Prints the status of GitLab and its components. ## Prints the status of GitLab and its components.
print_status() { print_status() {
check_status check_status
if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then
echo "GitLab is not running." echo "GitLab is not running."
return return
fi fi
...@@ -331,7 +376,14 @@ print_status() { ...@@ -331,7 +376,14 @@ print_status() {
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi fi
fi fi
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then if [ "$gitlab_pages_enabled" = true ]; then
if [ "$gitlab_pages_status" = "0" ]; then
echo "The GitLab Pages with pid $mpid is running."
else
printf "The GitLab Pages is \033[31mnot running\033[0m.\n"
fi
fi
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; }; then
printf "GitLab and all its components are \033[32mup and running\033[0m.\n" printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi fi
} }
...@@ -362,7 +414,7 @@ reload_gitlab(){ ...@@ -362,7 +414,7 @@ reload_gitlab(){
## Restarts Sidekiq and Unicorn. ## Restarts Sidekiq and Unicorn.
restart_gitlab(){ restart_gitlab(){
check_status check_status
if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; then
stop_gitlab stop_gitlab
fi fi
start_gitlab start_gitlab
......
...@@ -47,6 +47,30 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" ...@@ -47,6 +47,30 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
# The GitLab Pages Daemon needs either a separate IP address on which it will
# listen or use different ports than 80 or 443 that will be forwarded to GitLab
# Pages Daemon.
#
# To enable HTTP support for custom domains add the `-listen-http` directive
# in `gitlab_pages_options` below.
# The value of -listen-http must be set to `gitlab.yml > pages > external_http`
# as well. For example:
#
# -listen-http 1.1.1.1:80
#
# To enable HTTPS support for custom domains add the `-listen-https`,
# `-root-cert` and `-root-key` directives in `gitlab_pages_options` below.
# The value of -listen-https must be set to `gitlab.yml > pages > external_https`
# as well. For example:
#
# -listen-https 1.1.1.1:443 -root-cert /path/to/example.com.crt -root-key /path/to/example.com.key
#
# The -pages-domain must be specified the same as in `gitlab.yml > pages > host`.
# Set `gitlab_pages_enabled=true` if you want to enable the Pages feature.
gitlab_pages_enabled=false
gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
gitlab_pages_log="$app_root/log/gitlab-pages.log"
# mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. # mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled.
# This is required for the Reply by email feature. # This is required for the Reply by email feature.
# The default is "false" # The default is "false"
......
## GitLab
##
## Pages serving host
server {
listen 0.0.0.0:80;
listen [::]:80 ipv6only=on;
## Replace this with something like pages.gitlab.com
server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$;
## Individual nginx logs for GitLab pages
access_log /var/log/nginx/gitlab_pages_access.log;
error_log /var/log/nginx/gitlab_pages_error.log;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# The same address as passed to GitLab Pages: `-listen-proxy`
proxy_pass http://localhost:8090/;
}
# Define custom error pages
error_page 403 /403.html;
error_page 404 /404.html;
}
## GitLab
##
## Redirects all HTTP traffic to the HTTPS host
server {
## Either remove "default_server" from the listen line below,
## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab
## to be served if you visit any address that your server responds to, eg.
## the ip address of the server (http://x.x.x.x/)
listen 0.0.0.0:80;
listen [::]:80 ipv6only=on;
## Replace this with something like pages.gitlab.com
server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$;
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$http_host$request_uri;
access_log /var/log/nginx/gitlab_pages_access.log;
error_log /var/log/nginx/gitlab_pages_access.log;
}
## Pages serving host
server {
listen 0.0.0.0:443 ssl;
listen [::]:443 ipv6only=on ssl http2;
## Replace this with something like pages.gitlab.com
server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$;
server_tokens off; ## Don't show the nginx version number, a security best practice
## Strong SSL Security
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab-pages.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab-pages.key;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
## See app/controllers/application_controller.rb for headers set
## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL.
## Replace with your ssl_trusted_certificate. For more info see:
## - https://medium.com/devops-programming/4445f4862461
## - https://www.ruby-forum.com/topic/4419319
## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx
# ssl_stapling on;
# ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
## [Optional] Generate a stronger DHE parameter:
## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
## Individual nginx logs for GitLab pages
access_log /var/log/nginx/gitlab_pages_access.log;
error_log /var/log/nginx/gitlab_pages_error.log;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# The same address as passed to GitLab Pages: `-listen-proxy`
proxy_pass http://localhost:8090/;
}
# Define custom error pages
error_page 403 /403.html;
error_page 404 /404.html;
}
...@@ -13,6 +13,7 @@ namespace :gitlab do ...@@ -13,6 +13,7 @@ namespace :gitlab do
Rake::Task["gitlab:backup:uploads:create"].invoke Rake::Task["gitlab:backup:uploads:create"].invoke
Rake::Task["gitlab:backup:builds:create"].invoke Rake::Task["gitlab:backup:builds:create"].invoke
Rake::Task["gitlab:backup:artifacts:create"].invoke Rake::Task["gitlab:backup:artifacts:create"].invoke
Rake::Task["gitlab:backup:pages:create"].invoke
Rake::Task["gitlab:backup:lfs:create"].invoke Rake::Task["gitlab:backup:lfs:create"].invoke
Rake::Task["gitlab:backup:registry:create"].invoke Rake::Task["gitlab:backup:registry:create"].invoke
...@@ -56,6 +57,7 @@ namespace :gitlab do ...@@ -56,6 +57,7 @@ namespace :gitlab do
Rake::Task['gitlab:backup:uploads:restore'].invoke unless backup.skipped?('uploads') Rake::Task['gitlab:backup:uploads:restore'].invoke unless backup.skipped?('uploads')
Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds') Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds')
Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts') Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts')
Rake::Task["gitlab:backup:pages:restore"].invoke unless backup.skipped?("pages")
Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs') Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs')
Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry') Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry')
Rake::Task['gitlab:shell:setup'].invoke Rake::Task['gitlab:shell:setup'].invoke
...@@ -159,6 +161,25 @@ namespace :gitlab do ...@@ -159,6 +161,25 @@ namespace :gitlab do
end end
end end
namespace :pages do
task create: :environment do
$progress.puts "Dumping pages ... ".blue
if ENV["SKIP"] && ENV["SKIP"].include?("pages")
$progress.puts "[SKIPPED]".cyan
else
Backup::Pages.new.dump
$progress.puts "done".green
end
end
task restore: :environment do
$progress.puts "Restoring pages ... ".blue
Backup::Pages.new.restore
$progress.puts "done".green
end
end
namespace :lfs do namespace :lfs do
task create: :environment do task create: :environment do
$progress.puts "Dumping lfs objects ... ".color(:blue) $progress.puts "Dumping lfs objects ... ".color(:blue)
......
require 'spec_helper'
describe Projects::PagesDomainsController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:request_params) do
{
namespace_id: project.namespace,
project_id: project
}
end
before do
sign_in(user)
project.team << [user, :master]
end
describe 'GET show' do
let!(:pages_domain) { create(:pages_domain, project: project) }
it "displays the 'show' page" do
get(:show, request_params.merge(id: pages_domain.domain))
expect(response).to have_http_status(200)
expect(response).to render_template('show')
end
end
describe 'GET new' do
it "displays the 'new' page" do
get(:new, request_params)
expect(response).to have_http_status(200)
expect(response).to render_template('new')
end
end
describe 'POST create' do
let(:pages_domain_params) do
build(:pages_domain, :with_certificate, :with_key).slice(:key, :certificate, :domain)
end
it "creates a new pages domain" do
expect do
post(:create, request_params.merge(pages_domain: pages_domain_params))
end.to change { PagesDomain.count }.by(1)
expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
end
end
describe 'DELETE destroy' do
let!(:pages_domain) { create(:pages_domain, project: project) }
it "deletes the pages domain" do
expect do
delete(:destroy, request_params.merge(id: pages_domain.domain))
end.to change { PagesDomain.count }.by(-1)
expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
end
end
end
FactoryGirl.define do
factory :pages_domain, class: 'PagesDomain' do
domain 'my.domain.com'
trait :with_certificate do
certificate '-----BEGIN CERTIFICATE-----
MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
YHi2yesCrOvVXt+lgPTd
-----END CERTIFICATE-----'
end
trait :with_key do
key '-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
nNp/xedE1YxutQ==
-----END PRIVATE KEY-----'
end
trait :with_missing_chain do
# This certificate is signed with different key
# And misses the CA to build trust chain
certificate '-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIBAjANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdUZXN0
IENBMB4XDTE2MDIxMjE0MjMwMFoXDTE3MDIxMTE0MjMwMFowHTEbMBkGA1UEAxMS
dGVzdC1jZXJ0aWZpY2F0ZS0yMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAw8RWetIUT0YymSuKvBpClzDv/jQdX0Ch+2iF7f4Lm3lcmoUuXgyhl/WRe5K9
ONuMHPQlZbeavEbvWb0BsU7geInhsjd/zAu3EP17jfSIXToUdSD20wcSG/yclLdZ
qhb6NCtHTJKFUI8BktoS7kafkdvmeem/UJFzlvcA6VMyGDkS8ZN39a45R1jGmPEl
Yk0g1jW7lSKcBLjU1O/Csv59LyWXqBP6jR1vB8ijlUf1IyK8gOk7NHF13GHl7Z3A
/8zwuEt/pB3yK92o71P+FnSEcJ23zcAalz6H9ajVTzRr/AXttineBNVYnEuPXW+V
Rsboe+bBO/e4pVKXnQ1F3aMT7QIDAQABo28wbTAMBgNVHRMBAf8EAjAAMB0GA1Ud
DgQWBBSFwo3rhc26lD8ZVaBVcUY1NyCOLDALBgNVHQ8EBAMCBeAwEQYJYIZIAYb4
QgEBBAQDAgZAMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZI
hvcNAQEFBQADggEBABppUhunuT7qArM9gZ2gLgcOK8qyZWU8AJulvloaCZDvqGVs
Qom0iEMBrrt5+8bBevNiB49Tz7ok8NFgLzrlEnOw6y6QGjiI/g8sRKEiXl+ZNX8h
s8VN6arqT348OU8h2BixaXDmBF/IqZVApGhR8+B4fkCt0VQmdzVuHGbOQXMWJCpl
WlU8raZoPIqf6H/8JA97pM/nk/3CqCoHsouSQv+jGY4pSL22RqsO0ylIM0LDBbmF
m4AEaojTljX1tMJAF9Rbiw/omam5bDPq2JWtosrz/zB69y5FaQjc6FnCk0M4oN/+
VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w=
-----END CERTIFICATE-----'
end
trait :with_trusted_chain do
# This contains
# [Intermediate #2 (SHA-2)] 'Comodo RSA Domain Validation Secure Server CA'
# [Intermediate #1 (SHA-2)] 'COMODO RSA Certification Authority'
certificate '-----BEGIN CERTIFICATE-----
MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
+AZxAeKCINT+b72x
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
pu/xO28QOG8=
-----END CERTIFICATE-----'
end
trait :with_expired_certificate do
certificate '-----BEGIN CERTIFICATE-----
MIIBsDCCARmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAeMRwwGgYDVQQDExNleHBp
cmVkLWNlcnRpZmljYXRlMB4XDTE1MDIxMjE0MzMwMFoXDTE2MDIwMTE0MzMwMFow
HjEcMBoGA1UEAxMTZXhwaXJlZC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEF
AAOBjQAwgYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2ge
NR1qlNFaSvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLyS
NT438kdTnY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEA
ATANBgkqhkiG9w0BAQUFAAOBgQBNj+vWvneyW1KkbVK+b/cVmnYPSfbkHrYK6m8X
Hq9LkWn6WP4EHsesHyslgTQZF8C7kVLTbLn2noLnOE+Mp3vcWlZxl3Yk6aZMhKS+
Iy6oRpHaCF/2obZdIdgf9rlyz0fkqyHJc9GkioSoOhJZxEV2SgAkap8yS0sX2tJ9
ZDXgrA==
-----END CERTIFICATE-----'
end
end
end
require 'spec_helper'
feature 'Pages', feature: true do
given(:project) { create(:empty_project) }
given(:user) { create(:user) }
given(:role) { :master }
background do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
project.team << [user, role]
login_as(user)
end
shared_examples 'no pages deployed' do
scenario 'does not see anything to destroy' do
visit namespace_project_pages_path(project.namespace, project)
expect(page).not_to have_link('Remove pages')
expect(page).not_to have_text('Only the project owner can remove pages')
end
end
context 'when user is the owner' do
background do
project.namespace.update(owner: user)
end
context 'when pages deployed' do
background do
allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
end
scenario 'sees "Remove pages" link' do
visit namespace_project_pages_path(project.namespace, project)
expect(page).to have_link('Remove pages')
end
end
it_behaves_like 'no pages deployed'
end
context 'when the user is not the owner' do
context 'when pages deployed' do
background do
allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
end
scenario 'sees "Only the project owner can remove pages" text' do
visit namespace_project_pages_path(project.namespace, project)
expect(page).to have_text('Only the project owner can remove pages')
end
end
it_behaves_like 'no pages deployed'
end
end
File added
File added
File added
File added
File added
File added
...@@ -192,6 +192,7 @@ project: ...@@ -192,6 +192,7 @@ project:
- environments - environments
- deployments - deployments
- project_feature - project_feature
- pages_domains
- authorized_users - authorized_users
- project_authorizations - project_authorizations
- route - route
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::UploadsTransfer, lib: true do describe Gitlab::ProjectTransfer, lib: true do
before do before do
@root_dir = File.join(Rails.root, "public", "uploads") @root_dir = File.join(Rails.root, "public", "uploads")
@upload_transfer = Gitlab::UploadsTransfer.new @project_transfer = Gitlab::ProjectTransfer.new
allow(@project_transfer).to receive(:root_dir).and_return(@root_dir)
@project_path_was = "test_project_was" @project_path_was = "test_project_was"
@project_path = "test_project" @project_path = "test_project"
...@@ -21,7 +22,7 @@ describe Gitlab::UploadsTransfer, lib: true do ...@@ -21,7 +22,7 @@ describe Gitlab::UploadsTransfer, lib: true do
describe '#move_project' do describe '#move_project' do
it "moves project upload to another namespace" do it "moves project upload to another namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
@upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) @project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path) expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
...@@ -31,7 +32,7 @@ describe Gitlab::UploadsTransfer, lib: true do ...@@ -31,7 +32,7 @@ describe Gitlab::UploadsTransfer, lib: true do
describe '#rename_project' do describe '#rename_project' do
it "renames project" do it "renames project" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
@upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path) @project_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path) expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
...@@ -41,7 +42,7 @@ describe Gitlab::UploadsTransfer, lib: true do ...@@ -41,7 +42,7 @@ describe Gitlab::UploadsTransfer, lib: true do
describe '#rename_namespace' do describe '#rename_namespace' do
it "renames namespace" do it "renames namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
@upload_transfer.rename_namespace(@namespace_path_was, @namespace_path) @project_transfer.rename_namespace(@namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path) expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy expect(Dir.exist?(expected_path)).to be_truthy
......
require 'spec_helper'
describe PagesDomain, models: true do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
describe :validate_domain do
subject { build(:pages_domain, domain: domain) }
context 'is unique' do
let(:domain) { 'my.domain.com' }
it { is_expected.to validate_uniqueness_of(:domain) }
end
context 'valid domain' do
let(:domain) { 'my.domain.com' }
it { is_expected.to be_valid }
end
context 'valid hexadecimal-looking domain' do
let(:domain) { '0x12345.com'}
it { is_expected.to be_valid }
end
context 'no domain' do
let(:domain) { nil }
it { is_expected.not_to be_valid }
end
context 'invalid domain' do
let(:domain) { '0123123' }
it { is_expected.not_to be_valid }
end
context 'domain from .example.com' do
let(:domain) { 'my.domain.com' }
before { allow(Settings.pages).to receive(:host).and_return('domain.com') }
it { is_expected.not_to be_valid }
end
end
describe 'validate certificate' do
subject { domain }
context 'when only certificate is specified' do
let(:domain) { build(:pages_domain, :with_certificate) }
it { is_expected.not_to be_valid }
end
context 'when only key is specified' do
let(:domain) { build(:pages_domain, :with_key) }
it { is_expected.not_to be_valid }
end
context 'with matching key' do
let(:domain) { build(:pages_domain, :with_certificate, :with_key) }
it { is_expected.to be_valid }
end
context 'for not matching key' do
let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) }
it { is_expected.not_to be_valid }
end
end
describe :url do
subject { domain.url }
context 'without the certificate' do
let(:domain) { build(:pages_domain) }
it { is_expected.to eq('http://my.domain.com') }
end
context 'with a certificate' do
let(:domain) { build(:pages_domain, :with_certificate) }
it { is_expected.to eq('https://my.domain.com') }
end
end
describe :has_matching_key? do
subject { domain.has_matching_key? }
context 'for matching key' do
let(:domain) { build(:pages_domain, :with_certificate, :with_key) }
it { is_expected.to be_truthy }
end
context 'for invalid key' do
let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) }
it { is_expected.to be_falsey }
end
end
describe :has_intermediates? do
subject { domain.has_intermediates? }
context 'for self signed' do
let(:domain) { build(:pages_domain, :with_certificate) }
it { is_expected.to be_truthy }
end
context 'for missing certificate chain' do
let(:domain) { build(:pages_domain, :with_missing_chain) }
it { is_expected.to be_falsey }
end
context 'for trusted certificate chain' do
# We only validate that we can to rebuild the trust chain, for certificates
# We assume that 'AddTrustExternalCARoot' needed to validate the chain is in trusted store.
# It will be if ca-certificates is installed on Debian/Ubuntu/Alpine
let(:domain) { build(:pages_domain, :with_trusted_chain) }
it { is_expected.to be_truthy }
end
end
describe :expired? do
subject { domain.expired? }
context 'for valid' do
let(:domain) { build(:pages_domain, :with_certificate) }
it { is_expected.to be_falsey }
end
context 'for expired' do
let(:domain) { build(:pages_domain, :with_expired_certificate) }
it { is_expected.to be_truthy }
end
end
describe :subject do
let(:domain) { build(:pages_domain, :with_certificate) }
subject { domain.subject }
it { is_expected.to eq('/CN=test-certificate') }
end
describe :certificate_text do
let(:domain) { build(:pages_domain, :with_certificate) }
subject { domain.certificate_text }
# We test only existence of output, since the output is long
it { is_expected.not_to be_empty }
end
end
...@@ -60,6 +60,7 @@ describe Project, models: true do ...@@ -60,6 +60,7 @@ describe Project, models: true do
it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) } it { is_expected.to have_many(:triggers) }
it { is_expected.to have_many(:pages_domains) }
it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) }
it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) }
...@@ -1067,6 +1068,22 @@ describe Project, models: true do ...@@ -1067,6 +1068,22 @@ describe Project, models: true do
end end
end end
describe '#pages_deployed?' do
let(:project) { create :empty_project }
subject { project.pages_deployed? }
context 'if public folder does exist' do
before { allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) }
it { is_expected.to be_truthy }
end
context "if public folder doesn't exist" do
it { is_expected.to be_falsey }
end
end
describe '.search' do describe '.search' do
let(:project) { create(:empty_project, description: 'kitten mittens') } let(:project) { create(:empty_project, description: 'kitten mittens') }
...@@ -1844,4 +1861,31 @@ describe Project, models: true do ...@@ -1844,4 +1861,31 @@ describe Project, models: true do
def enable_lfs def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end end
describe '#pages_url' do
let(:group) { create :group, name: group_name }
let(:project) { create :empty_project, namespace: group, name: project_name }
let(:domain) { 'Example.com' }
subject { project.pages_url }
before do
allow(Settings.pages).to receive(:host).and_return(domain)
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
end
context 'group page' do
let(:group_name) { 'Group' }
let(:project_name) { 'group.example.com' }
it { is_expected.to eq("http://group.example.com") }
end
context 'project page' do
let(:group_name) { 'Group' }
let(:project_name) { 'Project' }
it { is_expected.to eq("http://group.example.com/project") }
end
end
end end
...@@ -27,35 +27,42 @@ describe 'project routing' do ...@@ -27,35 +27,42 @@ describe 'project routing' do
# let(:actions) { [:index] } # let(:actions) { [:index] }
# let(:controller) { 'issues' } # let(:controller) { 'issues' }
# end # end
#
# # Different controller name and path
# it_behaves_like 'RESTful project resources' do
# let(:controller) { 'pages_domains' }
# let(:controller_path) { 'pages/domains' }
# end
shared_examples 'RESTful project resources' do shared_examples 'RESTful project resources' do
let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
let(:controller_path) { controller }
it 'to #index' do it 'to #index' do
expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index)
end end
it 'to #create' do it 'to #create' do
expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create)
end end
it 'to #new' do it 'to #new' do
expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new)
end end
it 'to #edit' do it 'to #edit' do
expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
end end
it 'to #show' do it 'to #show' do
expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show)
end end
it 'to #update' do it 'to #update' do
expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update)
end end
it 'to #destroy' do it 'to #destroy' do
expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
end end
end end
...@@ -539,4 +546,20 @@ describe 'project routing' do ...@@ -539,4 +546,20 @@ describe 'project routing' do
'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq')
end end
end end
describe Projects::PagesDomainsController, 'routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:show, :new, :create, :destroy] }
let(:controller) { 'pages_domains' }
let(:controller_path) { 'pages/domains' }
end
it 'to #destroy with a valid domain name' do
expect(delete('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com')
end
it 'to #show with a valid domain' do
expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com')
end
end
end end
require 'spec_helper'
describe PagesService, services: true do
let(:build) { create(:ci_build) }
let(:data) { Gitlab::DataBuilder::Build.build(build) }
let(:service) { PagesService.new(data) }
before do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
end
context 'execute asynchronously for pages job' do
before { build.name = 'pages' }
context 'on success' do
before { build.success }
it 'executes worker' do
expect(PagesWorker).to receive(:perform_async)
service.execute
end
end
%w(pending running failed canceled).each do |status|
context "on #{status}" do
before { build.status = status }
it 'does not execute worker' do
expect(PagesWorker).not_to receive(:perform_async)
service.execute
end
end
end
end
context 'for other jobs' do
before do
build.name = 'other job'
build.success
end
it 'does not execute worker' do
expect(PagesWorker).not_to receive(:perform_async)
service.execute
end
end
end
...@@ -9,6 +9,8 @@ describe Projects::TransferService, services: true do ...@@ -9,6 +9,8 @@ describe Projects::TransferService, services: true do
before do before do
allow_any_instance_of(Gitlab::UploadsTransfer). allow_any_instance_of(Gitlab::UploadsTransfer).
to receive(:move_project).and_return(true) to receive(:move_project).and_return(true)
allow_any_instance_of(Gitlab::PagesTransfer).
to receive(:move_project).and_return(true)
group.add_owner(user) group.add_owner(user)
@result = transfer_project(project, user, group) @result = transfer_project(project, user, group)
end end
......
require 'spec_helper'
describe Projects::UpdatePagesConfigurationService, services: true do
let(:project) { create(:empty_project) }
subject { described_class.new(project) }
describe "#update" do
let(:file) { Tempfile.new('pages-test') }
after do
file.close
file.unlink
end
it 'updates the .update file' do
# Access this reference to ensure scoping works
Projects::Settings # rubocop:disable Lint/Void
expect(subject).to receive(:pages_config_file).and_return(file.path)
expect(subject).to receive(:reload_daemon).and_call_original
expect(subject.execute).to eq({ status: :success })
end
end
end
require "spec_helper"
describe Projects::UpdatePagesService do
let(:project) { create :project }
let(:pipeline) { create :ci_pipeline, project: project, sha: project.commit('HEAD').sha }
let(:build) { create :ci_build, pipeline: pipeline, ref: 'HEAD' }
let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') }
subject { described_class.new(project, build) }
before do
project.remove_pages
end
%w(tar.gz zip).each do |format|
context "for valid #{format}" do
let(:file) { fixture_file_upload(Rails.root + "spec/fixtures/pages.#{format}") }
let(:empty_file) { fixture_file_upload(Rails.root + "spec/fixtures/pages_empty.#{format}") }
let(:metadata) do
filename = Rails.root + "spec/fixtures/pages.#{format}.meta"
fixture_file_upload(filename) if File.exist?(filename)
end
before do
build.update_attributes(artifacts_file: file)
build.update_attributes(artifacts_metadata: metadata)
end
it 'succeeds' do
expect(project.pages_deployed?).to be_falsey
expect(execute).to eq(:success)
expect(project.pages_deployed?).to be_truthy
end
it 'limits pages size' do
stub_application_setting(max_pages_size: 1)
expect(execute).not_to eq(:success)
end
it 'removes pages after destroy' do
expect(PagesWorker).to receive(:perform_in)
expect(project.pages_deployed?).to be_falsey
expect(execute).to eq(:success)
expect(project.pages_deployed?).to be_truthy
project.destroy
expect(project.pages_deployed?).to be_falsey
end
it 'fails if sha on branch is not latest' do
pipeline.update_attributes(sha: 'old_sha')
build.update_attributes(artifacts_file: file)
expect(execute).not_to eq(:success)
end
it 'fails for empty file fails' do
build.update_attributes(artifacts_file: empty_file)
expect(execute).not_to eq(:success)
end
end
end
it 'fails to remove project pages when no pages is deployed' do
expect(PagesWorker).not_to receive(:perform_in)
expect(project.pages_deployed?).to be_falsey
project.destroy
end
it 'fails if no artifacts' do
expect(execute).not_to eq(:success)
end
it 'fails for invalid archive' do
build.update_attributes(artifacts_file: invalid_file)
expect(execute).not_to eq(:success)
end
def execute
subject.execute[:status]
end
end
...@@ -28,7 +28,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -28,7 +28,7 @@ describe 'gitlab:app namespace rake task' do
end end
def reenable_backup_sub_tasks def reenable_backup_sub_tasks
%w{db repo uploads builds artifacts lfs registry}.each do |subtask| %w{db repo uploads builds artifacts pages lfs registry}.each do |subtask|
Rake::Task["gitlab:backup:#{subtask}:create"].reenable Rake::Task["gitlab:backup:#{subtask}:create"].reenable
end end
end end
...@@ -71,6 +71,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -71,6 +71,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:pages:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
...@@ -202,7 +203,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -202,7 +203,7 @@ describe 'gitlab:app namespace rake task' do
it 'sets correct permissions on the tar contents' do it 'sets correct permissions on the tar contents' do
tar_contents, exit_status = Gitlab::Popen.popen( tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz}
) )
expect(exit_status).to eq(0) expect(exit_status).to eq(0)
expect(tar_contents).to match('db/') expect(tar_contents).to match('db/')
...@@ -210,14 +211,15 @@ describe 'gitlab:app namespace rake task' do ...@@ -210,14 +211,15 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('repositories/') expect(tar_contents).to match('repositories/')
expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).to match('artifacts.tar.gz')
expect(tar_contents).to match('pages.tar.gz')
expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).to match('registry.tar.gz') expect(tar_contents).to match('registry.tar.gz')
expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
end end
it 'deletes temp directories' do it 'deletes temp directories' do
temp_dirs = Dir.glob( temp_dirs = Dir.glob(
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}') File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs,registry}')
) )
expect(temp_dirs).to be_empty expect(temp_dirs).to be_empty
...@@ -304,7 +306,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -304,7 +306,7 @@ describe 'gitlab:app namespace rake task' do
it "does not contain skipped item" do it "does not contain skipped item" do
tar_contents, _exit_status = Gitlab::Popen.popen( tar_contents, _exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz}
) )
expect(tar_contents).to match('db/') expect(tar_contents).to match('db/')
...@@ -312,6 +314,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -312,6 +314,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('builds.tar.gz')
expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).to match('artifacts.tar.gz')
expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).to match('pages.tar.gz')
expect(tar_contents).to match('registry.tar.gz') expect(tar_contents).to match('registry.tar.gz')
expect(tar_contents).not_to match('repositories/') expect(tar_contents).not_to match('repositories/')
end end
...@@ -327,6 +330,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -327,6 +330,7 @@ describe 'gitlab:app namespace rake task' do
expect(Rake::Task['gitlab:backup:uploads:restore']).not_to receive :invoke expect(Rake::Task['gitlab:backup:uploads:restore']).not_to receive :invoke
expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:pages:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke
expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke
expect(Rake::Task['gitlab:shell:setup']).to receive :invoke expect(Rake::Task['gitlab:shell:setup']).to receive :invoke
......
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