Add GitLab Pages
- The pages are created when build artifacts for `pages` job are uploaded - Pages serve the content under: http://group.pages.domain.com/project - Pages can be used to serve the group page, special project named as host: group.pages.domain.com - User can provide own 403 and 404 error pages by creating 403.html and 404.html in group page project - Pages can be explicitly removed from the project by clicking Remove Pages in Project Settings - The size of pages is limited by Application Setting: max pages size, which limits the maximum size of unpacked archive (default: 100MB) - The public/ is extracted from artifacts and content is served as static pages - Pages asynchronous worker use `dd` to limit the unpacked tar size - Pages needs to be explicitly enabled and domain needs to be specified in gitlab.yml - Pages are part of backups - Pages notify the deployment status using Commit Status API - Pages use a new sidekiq queue: pages - Pages use a separate nginx config which needs to be explicitly added
This commit is contained in:
parent
659cceb0e8
commit
120f9abaa1
26 changed files with 441 additions and 6 deletions
|
@ -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,
|
||||||
|
|
|
@ -151,6 +151,16 @@ class ProjectsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_pages
|
||||||
|
return access_denied! unless can?(current_user, :remove_pages, @project)
|
||||||
|
|
||||||
|
@project.remove_pages
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to project_path(@project) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def housekeeping
|
def housekeeping
|
||||||
::Projects::HousekeepingService.new(@project).execute
|
::Projects::HousekeepingService.new(@project).execute
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -457,6 +457,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)
|
||||||
|
UpdatePagesService.new(build_data).execute
|
||||||
project.running_or_pending_build_count(force: true)
|
project.running_or_pending_build_count(force: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -1160,6 +1162,29 @@ class Project < ActiveRecord::Base
|
||||||
ensure_runners_token!
|
ensure_runners_token!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pages_url
|
||||||
|
if Dir.exist?(public_pages_path)
|
||||||
|
host = "#{namespace.path}.#{Settings.pages.domain}"
|
||||||
|
|
||||||
|
# If the project path is the same as host, leave the short version
|
||||||
|
return "http://#{host}" if host == path
|
||||||
|
|
||||||
|
"http://#{host}/#{path}"
|
||||||
|
end
|
||||||
|
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
|
||||||
|
FileUtils.rm_r(pages_path, force: true)
|
||||||
|
end
|
||||||
|
|
||||||
def wiki
|
def wiki
|
||||||
@wiki ||= ProjectWiki.new(self, self.owner)
|
@wiki ||= ProjectWiki.new(self, self.owner)
|
||||||
end
|
end
|
||||||
|
|
|
@ -136,6 +136,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!
|
||||||
|
|
15
app/services/update_pages_service.rb
Normal file
15
app/services/update_pages_service.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class UpdatePagesService
|
||||||
|
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(data[:build_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
|
@ -133,6 +133,41 @@
|
||||||
%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"
|
||||||
|
|
||||||
|
- if Settings.pages.enabled
|
||||||
|
.pages-settings
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading Pages
|
||||||
|
.errors-holder
|
||||||
|
.panel-body
|
||||||
|
- if @project.pages_url
|
||||||
|
%strong
|
||||||
|
Congratulations. Your pages are served at:
|
||||||
|
%p= link_to @project.pages_url, @project.pages_url
|
||||||
|
- else
|
||||||
|
%p
|
||||||
|
To publish pages create .gitlab-ci.yml with
|
||||||
|
%strong pages job
|
||||||
|
and send public/ folder to GitLab.
|
||||||
|
%p
|
||||||
|
Use existing tools:
|
||||||
|
%ul
|
||||||
|
%li
|
||||||
|
%pre
|
||||||
|
:plain
|
||||||
|
pages:
|
||||||
|
image: jekyll
|
||||||
|
script: jekyll build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
|
||||||
|
- if @project.pages_url && can?(current_user, :remove_pages, @project)
|
||||||
|
.form-actions
|
||||||
|
= link_to 'Remove pages', remove_pages_namespace_project_path(@project.namespace, @project),
|
||||||
|
data: { confirm: "Are you sure that you want to remove pages for this project?" },
|
||||||
|
method: :post, class: "btn btn-warning"
|
||||||
|
|
||||||
.row.prepend-top-default
|
.row.prepend-top-default
|
||||||
%hr
|
%hr
|
||||||
.row.prepend-top-default
|
.row.prepend-top-default
|
||||||
|
|
123
app/workers/pages_worker.rb
Normal file
123
app/workers/pages_worker.rb
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
class PagesWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
include Gitlab::CurrentSettings
|
||||||
|
|
||||||
|
BLOCK_SIZE = 32.kilobytes
|
||||||
|
MAX_SIZE = 1.terabyte
|
||||||
|
|
||||||
|
sidekiq_options queue: :pages
|
||||||
|
|
||||||
|
def perform(build_id)
|
||||||
|
@build_id = build_id
|
||||||
|
return unless valid?
|
||||||
|
|
||||||
|
# Create status notifying the deployment of pages
|
||||||
|
@status = GenericCommitStatus.new(
|
||||||
|
project: project,
|
||||||
|
commit: build.commit,
|
||||||
|
user: build.user,
|
||||||
|
ref: build.ref,
|
||||||
|
stage: 'deploy',
|
||||||
|
name: 'pages:deploy'
|
||||||
|
)
|
||||||
|
@status.run!
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(tmp_path)
|
||||||
|
|
||||||
|
# Calculate dd parameters: we limit the size of pages
|
||||||
|
max_size = current_application_settings.max_pages_size.megabytes
|
||||||
|
max_size ||= MAX_SIZE
|
||||||
|
blocks = 1 + max_size / BLOCK_SIZE
|
||||||
|
|
||||||
|
# Create temporary directory in which we will extract the artifacts
|
||||||
|
Dir.mktmpdir(nil, tmp_path) do |temp_path|
|
||||||
|
# We manually extract the archive and limit the archive size with dd
|
||||||
|
results = Open3.pipeline(%W(gunzip -c #{artifacts}),
|
||||||
|
%W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
|
||||||
|
%W(tar -x -C #{temp_path} public/))
|
||||||
|
return unless results.compact.all?(&:success?)
|
||||||
|
|
||||||
|
# Check if we did extract public directory
|
||||||
|
temp_public_path = File.join(temp_path, 'public')
|
||||||
|
return unless Dir.exists?(temp_public_path)
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(pages_path)
|
||||||
|
|
||||||
|
# Lock file for time of deployment to prevent the two processes from doing the concurrent deployment
|
||||||
|
File.open(lock_path, File::RDWR|File::CREAT, 0644) do |f|
|
||||||
|
f.flock(File::LOCK_EX)
|
||||||
|
return unless valid?
|
||||||
|
|
||||||
|
# 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
|
||||||
|
if File.exists?(public_path)
|
||||||
|
FileUtils.move(public_path, previous_public_path)
|
||||||
|
end
|
||||||
|
FileUtils.move(temp_public_path, public_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.exists?(previous_public_path)
|
||||||
|
FileUtils.rm_r(previous_public_path, force: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
@status.success
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
@status.drop if @status && @status.active?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
# check if sha for the ref is still the most recent one
|
||||||
|
# this helps in case when multiple deployments happens
|
||||||
|
build && build.artifacts_file? && sha == latest_sha
|
||||||
|
end
|
||||||
|
|
||||||
|
def build
|
||||||
|
@build ||= Ci::Build.find_by(id: @build_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def project
|
||||||
|
@project ||= build.project
|
||||||
|
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 lock_path
|
||||||
|
@lock_path ||= File.join(pages_path, 'deploy.lock')
|
||||||
|
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
|
|
@ -153,6 +153,17 @@ 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
|
||||||
|
domain: example.com
|
||||||
|
|
||||||
## Mattermost
|
## Mattermost
|
||||||
## For enabling Add to Mattermost button
|
## For enabling Add to Mattermost button
|
||||||
mattermost:
|
mattermost:
|
||||||
|
|
|
@ -254,6 +254,12 @@ 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('shared/pages/', Rails.root)
|
||||||
|
Settings.pages['domain'] ||= "example.com"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Git LFS
|
# Git LFS
|
||||||
#
|
#
|
||||||
|
|
|
@ -329,6 +329,7 @@ constraints(ProjectUrlConstrainer.new) do
|
||||||
post :archive
|
post :archive
|
||||||
post :unarchive
|
post :unarchive
|
||||||
post :housekeeping
|
post :housekeeping
|
||||||
|
post :remove_pages
|
||||||
post :toggle_star
|
post :toggle_star
|
||||||
post :preview_markdown
|
post :preview_markdown
|
||||||
post :export
|
post :export
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddPagesSizeToApplicationSettings < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :application_settings, :max_pages_size, :integer, default: 100, null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do
|
||||||
t.boolean "shared_runners_enabled", default: true, null: false
|
t.boolean "shared_runners_enabled", default: true, null: false
|
||||||
t.integer "max_artifacts_size", default: 100, null: false
|
t.integer "max_artifacts_size", default: 100, null: false
|
||||||
t.string "runners_registration_token"
|
t.string "runners_registration_token"
|
||||||
|
t.integer "max_pages_size", default: 100, null: false
|
||||||
t.boolean "require_two_factor_authentication", default: false
|
t.boolean "require_two_factor_authentication", default: false
|
||||||
t.integer "two_factor_grace_period", default: 48
|
t.integer "two_factor_grace_period", default: 48
|
||||||
t.boolean "metrics_enabled", default: false
|
t.boolean "metrics_enabled", default: false
|
||||||
|
|
|
@ -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](pages/README.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.
|
||||||
|
|
|
@ -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 CI artifacts 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,16 @@ 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
|
||||||
|
|
||||||
|
Copy the GitLab pages site config:
|
||||||
|
|
||||||
|
sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages
|
||||||
|
sudo ln -s /etc/nginx/sites-available/gitlab-pages /etc/nginx/sites-enabled/gitlab-pages
|
||||||
|
|
||||||
|
# Change YOUR_GITLAB_PAGES\.DOMAIN to the fully-qualified
|
||||||
|
# domain name under which the pages will be served.
|
||||||
|
# The . (dot) replace with \. (backslash+dot)
|
||||||
|
sudo editor /etc/nginx/sites-available/gitlab-pages
|
||||||
|
|
||||||
**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
|
||||||
|
|
12
doc/pages/README.md
Normal file
12
doc/pages/README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# GitLab Pages
|
||||||
|
|
||||||
|
To start using GitLab Pages add to your project .gitlab-ci.yml with special pages job.
|
||||||
|
|
||||||
|
pages:
|
||||||
|
image: jekyll
|
||||||
|
script: jekyll build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
|
||||||
|
TODO
|
13
lib/backup/pages.rb
Normal file
13
lib/backup/pages.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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
|
27
lib/support/nginx/gitlab-pages
Normal file
27
lib/support/nginx/gitlab-pages
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
## Pages serving host
|
||||||
|
server {
|
||||||
|
listen 0.0.0.0:80;
|
||||||
|
listen [::]:80 ipv6only=on;
|
||||||
|
|
||||||
|
## Replace this with something like pages.gitlab.com
|
||||||
|
server_name ~^(?<group>.*)\.YOUR_GITLAB_PAGES\.DOMAIN$;
|
||||||
|
root /home/git/gitlab/shared/pages/${group};
|
||||||
|
|
||||||
|
## Individual nginx logs for GitLab pages
|
||||||
|
access_log /var/log/nginx/gitlab_pages_access.log;
|
||||||
|
error_log /var/log/nginx/gitlab_pages_error.log;
|
||||||
|
|
||||||
|
# 1. Try to get /project/ to => shared/pages/${group}/public/ or index.html
|
||||||
|
# 2. Try to get / to => shared/pages/${group}/${host}/public/ or index.html
|
||||||
|
location ~ ^/([^/]*)(/.*)?$ {
|
||||||
|
try_files "/$1/public$2"
|
||||||
|
"/$1/public$2/index.html"
|
||||||
|
"/${host}/public/${uri}"
|
||||||
|
"/${host}/public/${uri}/index.html"
|
||||||
|
=404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define custom error pages
|
||||||
|
error_page 403 /403.html;
|
||||||
|
error_page 404 /404.html;
|
||||||
|
}
|
|
@ -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
|
||||||
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
|
||||||
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)
|
||||||
|
|
0
shared/pages/.gitkeep
Normal file
0
shared/pages/.gitkeep
Normal file
BIN
spec/fixtures/pages.tar.gz
vendored
Normal file
BIN
spec/fixtures/pages.tar.gz
vendored
Normal file
Binary file not shown.
BIN
spec/fixtures/pages_empty.tar.gz
vendored
Normal file
BIN
spec/fixtures/pages_empty.tar.gz
vendored
Normal file
Binary file not shown.
43
spec/services/update_pages_service_spec.rb
Normal file
43
spec/services/update_pages_service_spec.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe UpdatePagesService, services: true do
|
||||||
|
let(:build) { create(:ci_build) }
|
||||||
|
let(:data) { Gitlab::BuildDataBuilder.build(build) }
|
||||||
|
let(:service) { UpdatePagesService.new(data) }
|
||||||
|
|
||||||
|
context 'execute asynchronously for pages job' do
|
||||||
|
before { build.name = 'pages' }
|
||||||
|
|
||||||
|
context 'on success' do
|
||||||
|
before { build.success }
|
||||||
|
|
||||||
|
it 'should execute 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 'should not execute worker' do
|
||||||
|
expect(PagesWorker).to_not receive(:perform_async)
|
||||||
|
service.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for other jobs' do
|
||||||
|
before do
|
||||||
|
build.name = 'other job'
|
||||||
|
build.success
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not execute worker' do
|
||||||
|
expect(PagesWorker).to_not receive(:perform_async)
|
||||||
|
service.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
58
spec/workers/pages_worker_spec.rb
Normal file
58
spec/workers/pages_worker_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
describe PagesWorker do
|
||||||
|
let(:project) { create :project }
|
||||||
|
let(:commit) { create :ci_commit, project: project, sha: project.commit('HEAD').sha }
|
||||||
|
let(:build) { create :ci_build, commit: commit, ref: 'HEAD' }
|
||||||
|
let(:worker) { PagesWorker.new }
|
||||||
|
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages.tar.gz', 'application/octet-stream') }
|
||||||
|
let(:empty_file) { fixture_file_upload(Rails.root + 'spec/fixtures/pages_empty.tar.gz', 'application/octet-stream') }
|
||||||
|
let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'application/octet-stream') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.remove_pages
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for valid file' do
|
||||||
|
before { build.update_attributes(artifacts_file: file) }
|
||||||
|
|
||||||
|
it 'succeeds' do
|
||||||
|
expect(project.pages_url).to be_nil
|
||||||
|
expect(worker.perform(build.id)).to be_truthy
|
||||||
|
expect(project.pages_url).to_not be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'limits pages size' do
|
||||||
|
stub_application_setting(max_pages_size: 1)
|
||||||
|
expect(worker.perform(build.id)).to_not be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes pages after destroy' do
|
||||||
|
expect(project.pages_url).to be_nil
|
||||||
|
expect(worker.perform(build.id)).to be_truthy
|
||||||
|
expect(project.pages_url).to_not be_nil
|
||||||
|
project.destroy
|
||||||
|
expect(Dir.exist?(project.public_pages_path)).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails if no artifacts' do
|
||||||
|
expect(worker.perform(build.id)).to_not be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails for empty file fails' do
|
||||||
|
build.update_attributes(artifacts_file: empty_file)
|
||||||
|
expect(worker.perform(build.id)).to_not be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails for invalid archive' do
|
||||||
|
build.update_attributes(artifacts_file: invalid_file)
|
||||||
|
expect(worker.perform(build.id)).to_not be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails if sha on branch is not latest' do
|
||||||
|
commit.update_attributes(sha: 'old_sha')
|
||||||
|
build.update_attributes(artifacts_file: file)
|
||||||
|
expect(worker.perform(build.id)).to_not be_truthy
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue