From d15b7db1216f220b9f5af7e777cf04712483cbdf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 17 Jan 2017 14:50:49 -0500 Subject: Fix References header parser for Microsoft Exchange Microsoft Exchange would append a comma and another message id into the References header, therefore we'll need to fallback and parse the header by ourselves. Closes #26567 --- lib/gitlab/email/receiver.rb | 17 ++++++++- lib/gitlab/incoming_email.rb | 9 +++-- ...sing_and_key_inside_references_with_a_comma.eml | 42 ++++++++++++++++++++++ .../email/handler/create_note_handler_spec.rb | 6 ++++ spec/lib/gitlab/incoming_email_spec.rb | 15 ++++++++ 5 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references_with_a_comma.eml diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index a40c44eb1bc..df9d1cae8da 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -35,6 +35,8 @@ module Gitlab handler.execute end + private + def build_mail Mail::Message.new(@raw) rescue Encoding::UndefinedConversionError, @@ -54,7 +56,20 @@ module Gitlab end def key_from_additional_headers(mail) - Array(mail.references).find do |mail_id| + find_key_from_references(ensure_references_array(mail.references)) + end + + def ensure_references_array(references) + case references + when Array + references + when String # Handle emails from Microsoft exchange which uses commas + Gitlab::IncomingEmail.scan_fallback_references(references) + end + end + + def find_key_from_references(references) + references.find do |mail_id| key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id) break key if key end diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 801dfde9a36..9ae3a2c1214 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -3,8 +3,6 @@ module Gitlab WILDCARD_PLACEHOLDER = '%{key}'.freeze class << self - FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze - def enabled? config.enabled && config.address end @@ -32,10 +30,11 @@ module Gitlab end def key_from_fallback_message_id(mail_id) - match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX) - return unless match + mail_id[/\Areply\-(.+)@#{Gitlab.config.gitlab.host}\z/, 1] + end - match[1] + def scan_fallback_references(references) + references.scan(/(?!<)[^<>]+(?=>)/.freeze) end def config diff --git a/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references_with_a_comma.eml b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references_with_a_comma.eml new file mode 100644 index 00000000000..6823db0cfc8 --- /dev/null +++ b/spec/fixtures/emails/reply_without_subaddressing_and_key_inside_references_with_a_comma.eml @@ -0,0 +1,42 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply@appmail.adventuretime.ooo +Message-ID: +In-Reply-To: +References: , +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 48660d1dd1b..0f2bd009148 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -174,6 +174,12 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do it_behaves_like 'an email that contains a mail key', 'References' end + + context 'mail key is in the References header with a comma' do + let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references_with_a_comma.eml') } + + it_behaves_like 'an email that contains a mail key', 'References' + end end end end diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 1dcf2c0668b..01d0cb6cbd6 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -48,4 +48,19 @@ describe Gitlab::IncomingEmail, lib: true do expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key') end end + + context 'self.scan_fallback_references' do + let(:references) do + '' + + ' ' + + ',' + end + + it 'returns reply key' do + expect(described_class.scan_fallback_references(references)) + .to eq(%w[issue_1@localhost + reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost + exchange@microsoft.com]) + end + end end -- cgit v1.2.1 From 7fcbe37df37cb9f04eae4e690305a26ea88410d2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 Jan 2017 20:20:40 +0800 Subject: Specify that iOS app would also do this --- lib/gitlab/email/receiver.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index df9d1cae8da..fa08b5c668f 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -63,7 +63,9 @@ module Gitlab case references when Array references - when String # Handle emails from Microsoft exchange which uses commas + when String + # Handle emails from clients which append with commas, + # example clients are Microsoft exchange and iOS app Gitlab::IncomingEmail.scan_fallback_references(references) end end -- cgit v1.2.1 From 15f8642994bc74bea1a39d079c70b1f4e4730bf1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 Jan 2017 22:36:12 +0800 Subject: Add changelog entry --- changelogs/unreleased/fix-references-header-parsing.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-references-header-parsing.yml diff --git a/changelogs/unreleased/fix-references-header-parsing.yml b/changelogs/unreleased/fix-references-header-parsing.yml new file mode 100644 index 00000000000..b927279cdf4 --- /dev/null +++ b/changelogs/unreleased/fix-references-header-parsing.yml @@ -0,0 +1,5 @@ +--- +title: Fix reply by email without sub-addressing for some clients from + Microsoft and Apple +merge_request: 8620 +author: -- cgit v1.2.1 From f258f4f742ec9e172ecf4f9482e05fd2deee2b04 Mon Sep 17 00:00:00 2001 From: dimitrieh Date: Fri, 27 Jan 2017 16:45:43 +0100 Subject: Remove hover animation from row elements --- app/assets/stylesheets/framework/animations.scss | 12 ++++++++---- .../26863-Remove-hover-animation-from-row-elements.yml | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 8d38fc78a19..b1003ecf420 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -116,11 +116,15 @@ a { @include transition(background-color, color, border); } -.tree-table td, -.well-list > li { - @include transition(background-color, border-color); -} +// .tree-table td, +// .well-list > li { +// @include transition(background-color, border-color); +// } .stage-nav-item { @include transition(background-color, box-shadow); } + +.nav-sidebar a { + transition: none; +} diff --git a/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml b/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml new file mode 100644 index 00000000000..8dfabf87c2a --- /dev/null +++ b/changelogs/unreleased/26863-Remove-hover-animation-from-row-elements.yml @@ -0,0 +1,4 @@ +--- +title: Remove hover animation from row elements +merge_request: +author: -- cgit v1.2.1 From f8efb3ba7d913562c6917b32c5523e9877a2f4b5 Mon Sep 17 00:00:00 2001 From: dimitrieh Date: Fri, 27 Jan 2017 16:46:28 +0100 Subject: removed commented out scss --- app/assets/stylesheets/framework/animations.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index b1003ecf420..a5f15f4836d 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -116,11 +116,6 @@ a { @include transition(background-color, color, border); } -// .tree-table td, -// .well-list > li { -// @include transition(background-color, border-color); -// } - .stage-nav-item { @include transition(background-color, box-shadow); } -- cgit v1.2.1 From cc24682b58a66a217e6ffa1d56f8d45900c10d03 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 27 Jan 2017 15:56:40 -0200 Subject: Unify projects search by removing /projects/:search endpoint --- .../unreleased/22007-unify-projects-search.yml | 4 ++ lib/api/projects.rb | 16 -------- spec/requests/api/projects_spec.rb | 46 ---------------------- 3 files changed, 4 insertions(+), 62 deletions(-) create mode 100644 changelogs/unreleased/22007-unify-projects-search.yml diff --git a/changelogs/unreleased/22007-unify-projects-search.yml b/changelogs/unreleased/22007-unify-projects-search.yml new file mode 100644 index 00000000000..f43c1925ad0 --- /dev/null +++ b/changelogs/unreleased/22007-unify-projects-search.yml @@ -0,0 +1,4 @@ +--- +title: Unify projects search by removing /projects/:search endpoint +merge_request: 8877 +author: diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 941f47114a4..92a70faf1c2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -151,22 +151,6 @@ module API present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics] end - desc 'Search for projects the current user has access to' do - success Entities::Project - end - params do - requires :query, type: String, desc: 'The project name to be searched' - use :sort_params - use :pagination - end - get "/search/:query", requirements: { query: /[^\/]+/ } do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(params[:order_by] => params[:sort]) - - present paginate(projects), with: Entities::Project - end - desc 'Create new project' do success Entities::Project end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a1db81ce18c..8b04748b3b0 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1085,52 +1085,6 @@ describe API::Projects, api: true do end end - describe 'GET /projects/search/:query' do - let!(:query) { 'query'} - let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } - let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } - let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:pre_post) { create(:empty_project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } - let!(:unfound) { create(:empty_project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } - let!(:internal) { create(:empty_project, :internal, name: "internal #{query}") } - let!(:unfound_internal) { create(:empty_project, :internal, name: 'unfound internal') } - let!(:public) { create(:empty_project, :public, name: "public #{query}") } - let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } - let!(:one_dot_two) { create(:empty_project, :public, name: "one.dot.two") } - - shared_examples_for 'project search response' do |args = {}| - it 'returns project search responses' do - get api("/projects/search/#{args[:query]}", current_user) - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(args[:results]) - json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*#{args[:query]}.*/) } - end - end - - context 'when unauthenticated' do - it_behaves_like 'project search response', query: 'query', results: 1 do - let(:current_user) { nil } - end - end - - context 'when authenticated' do - it_behaves_like 'project search response', query: 'query', results: 6 do - let(:current_user) { user } - end - it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do - let(:current_user) { user } - end - end - - context 'when authenticated as a different user' do - it_behaves_like 'project search response', query: 'query', results: 2, match_regex: /(internal|public) query/ do - let(:current_user) { user2 } - end - end - end - describe 'PUT /projects/:id' do before { project } before { user } -- cgit v1.2.1 From e8f4f30627c07cbca26e7ce292cad7942ca76d0d Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Wed, 25 Jan 2017 16:36:40 +0600 Subject: layout rearranged --- app/views/admin/projects/index.html.haml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 2e6f03fcde0..5936312801b 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -27,7 +27,7 @@ = icon("search", class: "search-icon") .dropdown - - toggle_text = 'Search for Namespace' + - toggle_text = 'Namespace' - if params[:namespace_id].present? - namespace = Namespace.find(params[:namespace_id]) - toggle_text = "#{namespace.kind}: #{namespace.path}" @@ -37,8 +37,9 @@ = dropdown_filter("Search for Namespace") = dropdown_content = dropdown_loading - - = button_tag "Search", class: "btn btn-primary btn-search" + = render 'shared/projects/dropdown' + = link_to new_project_path, class: 'btn btn-new' do + New Project %ul.nav-links - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } @@ -56,11 +57,6 @@ = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do Public - .nav-controls - = render 'shared/projects/dropdown' - = link_to new_project_path, class: 'btn btn-new' do - New Project - .projects-list-holder - if @projects.any? %ul.projects-list.content-list -- cgit v1.2.1 From cadef802758ce4cf0b59849ca4613545967c2da6 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 30 Jan 2017 15:41:56 -0200 Subject: Remain V3 endpoint unchanged --- lib/api/api.rb | 7 +- lib/api/v3/projects.rb | 458 +++++++++++ spec/requests/api/v3/projects_spec.rb | 1424 +++++++++++++++++++++++++++++++++ spec/support/api_helpers.rb | 9 +- 4 files changed, 1895 insertions(+), 3 deletions(-) create mode 100644 lib/api/v3/projects.rb create mode 100644 spec/requests/api/v3/projects_spec.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index 6cf6b501021..090109d5e6f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -1,7 +1,12 @@ module API class API < Grape::API include APIGuard - version 'v3', using: :path + + version %w(v3 v4), using: :path + + version 'v3', using: :path do + mount ::API::V3::Projects + end before { allow_access_with_scope :api } diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb new file mode 100644 index 00000000000..bac7d485a22 --- /dev/null +++ b/lib/api/v3/projects.rb @@ -0,0 +1,458 @@ +module API + module V3 + class Projects < Grape::API + include PaginationParams + + before { authenticate_non_get! } + + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the project' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' + optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility_level, type: Integer, values: [ + Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :public_builds, type: Boolean, desc: 'Perform public builds' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + if !publik.nil? && !attrs[:visibility_level].present? + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE + end + attrs + end + end + + resource :projects do + helpers do + params :collection_params do + use :sort_params + use :filter_params + use :pagination + + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + end + + params :sort_params do + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + end + + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + end + + params :statistics_params do + optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' + end + + def present_projects(projects, options = {}) + options = options.reverse_merge( + with: Entities::Project, + current_user: current_user, + simple: params[:simple], + ) + + projects = filter_projects(projects) + projects = projects.with_statistics if options[:statistics] + options[:with] = Entities::BasicProjectDetails if options[:simple] + + present paginate(projects), options + end + end + + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + end + get '/visible' do + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present_projects ProjectsFinder.new.execute(current_user), with: entity + end + + desc 'Get a projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + end + get do + authenticate! + + present_projects current_user.authorized_projects, + with: Entities::ProjectWithAccess + end + + desc 'Get an owned projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + use :statistics_params + end + get '/owned' do + authenticate! + + present_projects current_user.owned_projects, + with: Entities::ProjectWithAccess, + statistics: params[:statistics] + end + + desc 'Gets starred project for the authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + end + get '/starred' do + authenticate! + + present_projects current_user.viewable_starred_projects + end + + desc 'Get all projects for admin user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + use :statistics_params + end + get '/all' do + authenticated_as_admin! + + present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics] + end + + desc 'Search for projects the current user has access to' do + success Entities::Project + end + params do + requires :query, type: String, desc: 'The project name to be searched' + use :sort_params + use :pagination + end + get "/search/:query", requirements: { query: /[^\/]+/ } do + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(params[:order_by] => params[:sort]) + + present paginate(projects), with: Entities::Project + end + + desc 'Create new project' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + use :create_params + end + post do + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) + else + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) + end + render_validation_error!(project) + end + end + + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end + post "user/:user_id" do + authenticated_as_admin! + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) + else + render_validation_error!(project) + end + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: /[^\/]+/ } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + get ":id" do + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present user_project, with: entity, current_user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project) + end + + desc 'Get events for a single project' do + success Entities::Event + end + params do + use :pagination + end + get ":id/events" do + present paginate(user_project.events.recent), with: Entities::Event + end + + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end + post 'fork/:id' do + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] + + if namespace_id.present? + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end + + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) + not_found!('Target Namespace') + end + end + + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute + + if forked_project.errors.any? + conflict!(forked_project.errors.messages) + else + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) + end + end + + desc 'Update an existing project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, + :wiki_enabled, :builds_enabled, :snippets_enabled, + :shared_runners_enabled, :container_registry_enabled, + :lfs_enabled, :public, :visibility_level, :public_builds, + :request_access_enabled, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, :path, + :default_branch + end + put ':id' do + authorize_admin_project + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + authorize! :rename_project, user_project if attrs[:name].present? + authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? + + result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute + + if result[:status] == :success + present user_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, user_project) + else + render_validation_error!(user_project) + end + end + + desc 'Archive a project' do + success Entities::Project + end + post ':id/archive' do + authorize!(:archive_project, user_project) + + user_project.archive! + + present user_project, with: Entities::Project + end + + desc 'Unarchive a project' do + success Entities::Project + end + post ':id/unarchive' do + authorize!(:archive_project, user_project) + + user_project.unarchive! + + present user_project, with: Entities::Project + end + + desc 'Star a project' do + success Entities::Project + end + post ':id/star' do + if current_user.starred?(user_project) + not_modified! + else + current_user.toggle_star(user_project) + user_project.reload + + present user_project, with: Entities::Project + end + end + + desc 'Unstar a project' do + success Entities::Project + end + delete ':id/star' do + if current_user.starred?(user_project) + current_user.toggle_star(user_project) + user_project.reload + + present user_project, with: Entities::Project + else + not_modified! + end + end + + desc 'Remove a project' + delete ":id" do + authorize! :remove_project, user_project + ::Projects::DestroyService.new(user_project, current_user, {}).async_execute + end + + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end + post ":id/fork/:forked_from_id" do + authenticated_as_admin! + + forked_from_project = find_project!(params[:forked_from_id]) + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) + else + render_api_error!("Project already forked", 409) + end + end + + desc 'Remove a forked_from relationship' + delete ":id/fork" do + authorize! :remove_fork_project, user_project + + if user_project.forked? + user_project.forked_project_link.destroy + else + not_modified! + end + end + + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end + post ":id/share" do + authorize! :admin_project, user_project + group = Group.find_by_id(params[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end + + unless user_project.allowed_to_share_with_group? + return render_api_error!("The project sharing with group is disabled", 400) + end + + link = user_project.project_group_links.new(declared_params(include_missing: false)) + + if link.save + present link, with: Entities::ProjectGroupLink + else + render_api_error!(link.errors.full_messages.first, 409) + end + end + + params do + requires :group_id, type: Integer, desc: 'The ID of the group' + end + delete ":id/share/:group_id" do + authorize! :admin_project, user_project + + link = user_project.project_group_links.find_by(group_id: params[:group_id]) + not_found!('Group Link') unless link + + link.destroy + no_content! + end + + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end + post ":id/uploads" do + ::Projects::UploadService.new(user_project, params[:file]).execute + end + + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination + end + get ':id/users' do + users = user_project.team.users + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic + end + end + end + end +end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb new file mode 100644 index 00000000000..c3f53d0da37 --- /dev/null +++ b/spec/requests/api/v3/projects_spec.rb @@ -0,0 +1,1424 @@ +require 'spec_helper' + +describe API::V3::Projects, v3_api: true do + include ApiHelpers + include Gitlab::CurrentSettings + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + let(:admin) { create(:admin) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) } + let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } + let(:project_member) { create(:project_member, :master, user: user, project: project) } + let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } + let(:user4) { create(:user) } + let(:project3) do + create(:project, + :private, + :repository, + name: 'second_project', + path: 'second_project', + creator_id: user.id, + namespace: user.namespace, + merge_requests_enabled: false, + issues_enabled: false, wiki_enabled: false, + snippets_enabled: false) + end + let(:project_member3) do + create(:project_member, + user: user4, + project: project3, + access_level: ProjectMember::MASTER) + end + let(:project4) do + create(:empty_project, + name: 'third_project', + path: 'third_project', + creator_id: user4.id, + namespace: user4.namespace) + end + + describe 'GET /projects' do + before { project } + + context 'when unauthenticated' do + it 'returns authentication error' do + get v3_api('/projects') + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as regular user' do + it 'returns an array of projects' do + get v3_api('/projects', user) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.name) + expect(json_response.first['owner']['username']).to eq(user.username) + end + + it 'includes the project labels as the tag_list' do + get v3_api('/projects', user) + expect(response.status).to eq 200 + expect(json_response).to be_an Array + expect(json_response.first.keys).to include('tag_list') + end + + it 'includes open_issues_count' do + get v3_api('/projects', user) + expect(response.status).to eq 200 + expect(json_response).to be_an Array + expect(json_response.first.keys).to include('open_issues_count') + end + + it 'does not include open_issues_count' do + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + + get v3_api('/projects', user) + expect(response.status).to eq 200 + expect(json_response).to be_an Array + expect(json_response.first.keys).not_to include('open_issues_count') + end + + context 'GET /projects?simple=true' do + it 'returns a simplified version of all the projects' do + expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"] + + get v3_api('/projects?simple=true', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first.keys).to match_array expected_keys + end + end + + context 'and using search' do + it 'returns searched project' do + get v3_api('/projects', user), { search: project.name } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + + context 'and using the visibility filter' do + it 'filters based on private visibility param' do + get v3_api('/projects', user), { visibility: 'private' } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count) + end + + it 'filters based on internal visibility param' do + get v3_api('/projects', user), { visibility: 'internal' } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count) + end + + it 'filters based on public visibility param' do + get v3_api('/projects', user), { visibility: 'public' } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count) + end + end + + context 'and using sorting' do + before do + project2 + project3 + end + + it 'returns the correct order when sorted by id' do + get v3_api('/projects', user), { order_by: 'id', sort: 'desc' } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(project3.id) + end + end + end + end + + describe 'GET /projects/all' do + before { project } + + context 'when unauthenticated' do + it 'returns authentication error' do + get v3_api('/projects/all') + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as regular user' do + it 'returns authentication error' do + get v3_api('/projects/all', user) + expect(response).to have_http_status(403) + end + end + + context 'when authenticated as admin' do + it 'returns an array of all projects' do + get v3_api('/projects/all', admin) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + + expect(json_response).to satisfy do |response| + response.one? do |entry| + entry.has_key?('permissions') && + entry['name'] == project.name && + entry['owner']['username'] == user.username + end + end + end + + it "does not include statistics by default" do + get v3_api('/projects/all', admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + get v3_api('/projects/all', admin), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).to include 'statistics' + end + end + end + + describe 'GET /projects/owned' do + before do + project3 + project4 + end + + context 'when unauthenticated' do + it 'returns authentication error' do + get v3_api('/projects/owned') + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as project owner' do + it 'returns an array of projects the user owns' do + get v3_api('/projects/owned', user4) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project4.name) + expect(json_response.first['owner']['username']).to eq(user4.username) + end + + it "does not include statistics by default" do + get v3_api('/projects/owned', user4) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + attributes = { + commit_count: 23, + storage_size: 702, + repository_size: 123, + lfs_objects_size: 234, + build_artifacts_size: 345, + } + + project4.statistics.update!(attributes) + + get v3_api('/projects/owned', user4), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['statistics']).to eq attributes.stringify_keys + end + end + end + + describe 'GET /projects/visible' do + shared_examples_for 'visible projects response' do + it 'returns the visible projects' do + get v3_api('/projects/visible', current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) + end + end + + let!(:public_project) { create(:empty_project, :public) } + before do + project + project2 + project3 + project4 + end + + context 'when unauthenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { nil } + let(:projects) { [public_project] } + end + end + + context 'when authenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { user } + let(:projects) { [public_project, project, project2, project3] } + end + end + + context 'when authenticated as a different user' do + it_behaves_like 'visible projects response' do + let(:current_user) { user2 } + let(:projects) { [public_project] } + end + end + end + + describe 'GET /projects/starred' do + let(:public_project) { create(:empty_project, :public) } + + before do + project_member2 + user3.update_attributes(starred_projects: [project, project2, project3, public_project]) + end + + it 'returns the starred projects viewable by the user' do + get v3_api('/projects/starred', user3) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) + end + end + + describe 'POST /projects' do + context 'maximum number of projects reached' do + it 'does not create new project and respond with 403' do + allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) + expect { post v3_api('/projects', user2), name: 'foo' }. + to change {Project.count}.by(0) + expect(response).to have_http_status(403) + end + end + + it 'creates new project without path and return 201' do + expect { post v3_api('/projects', user), name: 'foo' }. + to change { Project.count }.by(1) + expect(response).to have_http_status(201) + end + + it 'creates last project before reaching project limit' do + allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) + post v3_api('/projects', user2), name: 'foo' + expect(response).to have_http_status(201) + end + + it 'does not create new project without name and return 400' do + expect { post v3_api('/projects', user) }.not_to change { Project.count } + expect(response).to have_http_status(400) + end + + it "assigns attributes to project" do + project = attributes_for(:project, { + path: 'camelCasePath', + description: FFaker::Lorem.sentence, + issues_enabled: false, + merge_requests_enabled: false, + wiki_enabled: false, + only_allow_merge_if_build_succeeds: false, + request_access_enabled: true, + only_allow_merge_if_all_discussions_are_resolved: false + }) + + post v3_api('/projects', user), project + + project.each_pair do |k, v| + next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) + expect(json_response[k.to_s]).to eq(v) + end + + # Check feature permissions attributes + project = Project.find_by_path(project[:path]) + expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) + end + + it 'sets a project as public' do + project = attributes_for(:project, :public) + post v3_api('/projects', user), project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + + it 'sets a project as public using :public' do + project = attributes_for(:project, { public: true }) + post v3_api('/projects', user), project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + + it 'sets a project as internal' do + project = attributes_for(:project, :internal) + post v3_api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + it 'sets a project as internal overriding :public' do + project = attributes_for(:project, :internal, { public: true }) + post v3_api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + it 'sets a project as private' do + project = attributes_for(:project, :private) + post v3_api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'sets a project as private using :public' do + project = attributes_for(:project, { public: false }) + post v3_api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post v3_api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post v3_api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end + + it 'sets a project as allowing merge even if discussions are unresolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) + + post v3_api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + + it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do + project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil) + + post v3_api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + + it 'sets a project as allowing merge only if all discussions are resolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) + + post v3_api('/projects', user), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy + end + + context 'when a visibility level is restricted' do + before do + @project = attributes_for(:project, { public: true }) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + end + + it 'does not allow a non-admin to use a restricted visibility level' do + post v3_api('/projects', user), @project + + expect(response).to have_http_status(400) + expect(json_response['message']['visibility_level'].first).to( + match('restricted by your GitLab administrator') + ) + end + + it 'allows an admin to override restricted visibility settings' do + post v3_api('/projects', admin), @project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to( + eq(Gitlab::VisibilityLevel::PUBLIC) + ) + end + end + end + + describe 'POST /projects/user/:id' do + before { project } + before { admin } + + it 'should create new project without path and return 201' do + expect { post v3_api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) + expect(response).to have_http_status(201) + end + + it 'responds with 400 on failure and not project' do + expect { post v3_api("/projects/user/#{user.id}", admin) }. + not_to change { Project.count } + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('name is missing') + end + + it 'assigns attributes to project' do + project = attributes_for(:project, { + description: FFaker::Lorem.sentence, + issues_enabled: false, + merge_requests_enabled: false, + wiki_enabled: false, + request_access_enabled: true + }) + + post v3_api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) + project.each_pair do |k, v| + next if %i[has_external_issue_tracker path].include?(k) + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'sets a project as public' do + project = attributes_for(:project, :public) + post v3_api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + + it 'sets a project as public using :public' do + project = attributes_for(:project, { public: true }) + post v3_api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + + it 'sets a project as internal' do + project = attributes_for(:project, :internal) + post v3_api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + it 'sets a project as internal overriding :public' do + project = attributes_for(:project, :internal, { public: true }) + post v3_api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + it 'sets a project as private' do + project = attributes_for(:project, :private) + post v3_api("/projects/user/#{user.id}", admin), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'sets a project as private using :public' do + project = attributes_for(:project, { public: false }) + post v3_api("/projects/user/#{user.id}", admin), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post v3_api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post v3_api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end + + it 'sets a project as allowing merge even if discussions are unresolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false }) + + post v3_api("/projects/user/#{user.id}", admin), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey + end + + it 'sets a project as allowing merge only if all discussions are resolved' do + project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true }) + + post v3_api("/projects/user/#{user.id}", admin), project + + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy + end + end + + describe "POST /projects/:id/uploads" do + before { project } + + it "uploads the file and returns its info" do + post v3_api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") + + expect(response).to have_http_status(201) + expect(json_response['alt']).to eq("dk") + expect(json_response['url']).to start_with("/uploads/") + expect(json_response['url']).to end_with("/dk.png") + end + end + + describe 'GET /projects/:id' do + context 'when unauthenticated' do + it 'returns the public projects' do + public_project = create(:empty_project, :public) + + get v3_api("/projects/#{public_project.id}") + + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(public_project.id) + expect(json_response['description']).to eq(public_project.description) + expect(json_response.keys).not_to include('permissions') + end + end + + context 'when authenticated' do + before do + project + project_member + end + + it 'returns a project by id' do + group = create(:group) + link = create(:project_group_link, project: project, group: group) + + get v3_api("/projects/#{project.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['public']).to be_falsey + expect(json_response['archived']).to be_falsey + expect(json_response['visibility_level']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['owner']).to be_a Hash + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['builds_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_builds']).to be_present + expect(json_response['shared_with_groups']).to be_an Array + expect(json_response['shared_with_groups'].length).to eq(1) + expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) + end + + it 'returns a project by path name' do + get v3_api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) + end + + it 'returns a 404 error if not found' do + get v3_api('/projects/42', user) + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + get v3_api("/projects/#{project.id}", other_user) + expect(response).to have_http_status(404) + end + + it 'handles users with dots' do + dot_user = create(:user, username: 'dot.user') + project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) + + get v3_api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) + end + + it 'exposes namespace fields' do + get v3_api("/projects/#{project.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['namespace']).to eq({ + 'id' => user.namespace.id, + 'name' => user.namespace.name, + 'path' => user.namespace.path, + 'kind' => user.namespace.kind, + }) + end + + describe 'permissions' do + context 'all projects' do + before { project.team << [user, :master] } + + it 'contains permission information' do + get v3_api("/projects", user) + + expect(response).to have_http_status(200) + expect(json_response.first['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response.first['permissions']['group_access']).to be_nil + end + end + + context 'personal project' do + it 'sets project access and returns 200' do + project.team << [user, :master] + get v3_api("/projects/#{project.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response['permissions']['group_access']).to be_nil + end + end + + context 'group project' do + let(:project2) { create(:empty_project, group: create(:group)) } + + before { project2.group.add_owner(user) } + + it 'sets the owner and return 200' do + get v3_api("/projects/#{project2.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']).to be_nil + expect(json_response['permissions']['group_access']['access_level']). + to eq(Gitlab::Access::OWNER) + end + end + end + end + end + + describe 'GET /projects/:id/events' do + shared_examples_for 'project events response' do + it 'returns the project events' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) + note = create(:note_on_issue, note: 'What an awesome day!', project: project) + EventCreateService.new.leave_note(note, note.author) + + get v3_api("/projects/#{project.id}/events", current_user) + + expect(response).to have_http_status(200) + + first_event = json_response.first + + expect(first_event['action_name']).to eq('commented on') + expect(first_event['note']['body']).to eq('What an awesome day!') + + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(member.username) + expect(last_event['author']['name']).to eq(member.name) + end + end + + context 'when unauthenticated' do + it_behaves_like 'project events response' do + let(:project) { create(:empty_project, :public) } + let(:current_user) { nil } + end + end + + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project events response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get v3_api('/projects/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get v3_api("/projects/#{project.id}/events", other_user) + + expect(response).to have_http_status(404) + end + end + end + + describe 'GET /projects/:id/users' do + shared_examples_for 'project users response' do + it 'returns the project users' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) + + get v3_api("/projects/#{project.id}/users", current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + + first_user = json_response.first + + expect(first_user['username']).to eq(member.username) + expect(first_user['name']).to eq(member.name) + expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url]) + end + end + + context 'when unauthenticated' do + it_behaves_like 'project users response' do + let(:project) { create(:empty_project, :public) } + let(:current_user) { nil } + end + end + + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project users response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get v3_api('/projects/42/users', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get v3_api("/projects/#{project.id}/users", other_user) + + expect(response).to have_http_status(404) + end + end + end + + describe 'GET /projects/:id/snippets' do + before { snippet } + + it 'returns an array of project snippets' do + get v3_api("/projects/#{project.id}/snippets", user) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(snippet.title) + end + end + + describe 'GET /projects/:id/snippets/:snippet_id' do + it 'returns a project snippet' do + get v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) + expect(response).to have_http_status(200) + expect(json_response['title']).to eq(snippet.title) + end + + it 'returns a 404 error if snippet id not found' do + get v3_api("/projects/#{project.id}/snippets/1234", user) + expect(response).to have_http_status(404) + end + end + + describe 'POST /projects/:id/snippets' do + it 'creates a new project snippet' do + post v3_api("/projects/#{project.id}/snippets", user), + title: 'v3_api test', file_name: 'sample.rb', code: 'test', + visibility_level: '0' + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('v3_api test') + end + + it 'returns a 400 error if invalid snippet is given' do + post v3_api("/projects/#{project.id}/snippets", user) + expect(status).to eq(400) + end + end + + describe 'PUT /projects/:id/snippets/:snippet_id' do + it 'updates an existing project snippet' do + put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), + code: 'updated code' + expect(response).to have_http_status(200) + expect(json_response['title']).to eq('example') + expect(snippet.reload.content).to eq('updated code') + end + + it 'updates an existing project snippet with new title' do + put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), + title: 'other v3_api test' + expect(response).to have_http_status(200) + expect(json_response['title']).to eq('other v3_api test') + end + end + + describe 'DELETE /projects/:id/snippets/:snippet_id' do + before { snippet } + + it 'deletes existing project snippet' do + expect do + delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) + end.to change { Snippet.count }.by(-1) + expect(response).to have_http_status(200) + end + + it 'returns 404 when deleting unknown snippet id' do + delete v3_api("/projects/#{project.id}/snippets/1234", user) + expect(response).to have_http_status(404) + end + end + + describe 'GET /projects/:id/snippets/:snippet_id/raw' do + it 'gets a raw project snippet' do + get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) + expect(response).to have_http_status(200) + end + + it 'returns a 404 error if raw project snippet not found' do + get v3_api("/projects/#{project.id}/snippets/5555/raw", user) + expect(response).to have_http_status(404) + end + end + + describe :fork_admin do + let(:project_fork_target) { create(:empty_project) } + let(:project_fork_source) { create(:empty_project, :public) } + + describe 'POST /projects/:id/fork/:forked_from_id' do + let(:new_project_fork_source) { create(:empty_project, :public) } + + it "is not available for non admin users" do + post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) + expect(response).to have_http_status(403) + end + + it 'allows project to be forked from an existing project' do + expect(project_fork_target.forked?).not_to be_truthy + post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) + expect(response).to have_http_status(201) + project_fork_target.reload + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + expect(project_fork_target.forked_project_link).not_to be_nil + expect(project_fork_target.forked?).to be_truthy + end + + it 'fails if forked_from project which does not exist' do + post v3_api("/projects/#{project_fork_target.id}/fork/9999", admin) + expect(response).to have_http_status(404) + end + + it 'fails with 409 if already forked' do + post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) + project_fork_target.reload + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + post v3_api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) + expect(response).to have_http_status(409) + project_fork_target.reload + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + expect(project_fork_target.forked?).to be_truthy + end + end + + describe 'DELETE /projects/:id/fork' do + it "is not visible to users outside group" do + delete v3_api("/projects/#{project_fork_target.id}/fork", user) + expect(response).to have_http_status(404) + end + + context 'when users belong to project group' do + let(:project_fork_target) { create(:empty_project, group: create(:group)) } + + before do + project_fork_target.group.add_owner user + project_fork_target.group.add_developer user2 + end + + it 'is forbidden to non-owner users' do + delete v3_api("/projects/#{project_fork_target.id}/fork", user2) + expect(response).to have_http_status(403) + end + + it 'makes forked project unforked' do + post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) + project_fork_target.reload + expect(project_fork_target.forked_from_project).not_to be_nil + expect(project_fork_target.forked?).to be_truthy + delete v3_api("/projects/#{project_fork_target.id}/fork", admin) + expect(response).to have_http_status(200) + project_fork_target.reload + expect(project_fork_target.forked_from_project).to be_nil + expect(project_fork_target.forked?).not_to be_truthy + end + + it 'is idempotent if not forked' do + expect(project_fork_target.forked_from_project).to be_nil + delete v3_api("/projects/#{project_fork_target.id}/fork", admin) + expect(response).to have_http_status(304) + expect(project_fork_target.reload.forked_from_project).to be_nil + end + end + end + end + + describe "POST /projects/:id/share" do + let(:group) { create(:group) } + + it "shares project with group" do + expires_at = 10.days.from_now.to_date + + expect do + post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at + end.to change { ProjectGroupLink.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response['group_id']).to eq(group.id) + expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) + expect(json_response['expires_at']).to eq(expires_at.to_s) + end + + it "returns a 400 error when group id is not given" do + post v3_api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER + expect(response).to have_http_status(400) + end + + it "returns a 400 error when access level is not given" do + post v3_api("/projects/#{project.id}/share", user), group_id: group.id + expect(response).to have_http_status(400) + end + + it "returns a 400 error when sharing is disabled" do + project.namespace.update(share_with_group_lock: true) + post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + expect(response).to have_http_status(400) + end + + it 'returns a 404 error when user cannot read group' do + private_group = create(:group, :private) + + post v3_api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + + expect(response).to have_http_status(404) + end + + it 'returns a 404 error when group does not exist' do + post v3_api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + + expect(response).to have_http_status(404) + end + + it "returns a 400 error when wrong params passed" do + post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'group_access does not have a valid value' + end + end + + describe 'DELETE /projects/:id/share/:group_id' do + it 'returns 204 when deleting a group share' do + group = create(:group, :public) + create(:project_group_link, group: group, project: project) + + delete v3_api("/projects/#{project.id}/share/#{group.id}", user) + + expect(response).to have_http_status(204) + expect(project.project_group_links).to be_empty + end + + it 'returns a 400 when group id is not an integer' do + delete v3_api("/projects/#{project.id}/share/foo", user) + + expect(response).to have_http_status(400) + end + + it 'returns a 404 error when group link does not exist' do + delete v3_api("/projects/#{project.id}/share/1234", user) + + expect(response).to have_http_status(404) + end + + it 'returns a 404 error when project does not exist' do + delete v3_api("/projects/123/share/1234", user) + + expect(response).to have_http_status(404) + end + end + + describe 'GET /projects/search/:query' do + let!(:query) { 'query'} + let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } + let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } + let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:pre_post) { create(:empty_project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } + let!(:unfound) { create(:empty_project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } + let!(:internal) { create(:empty_project, :internal, name: "internal #{query}") } + let!(:unfound_internal) { create(:empty_project, :internal, name: 'unfound internal') } + let!(:public) { create(:empty_project, :public, name: "public #{query}") } + let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } + let!(:one_dot_two) { create(:empty_project, :public, name: "one.dot.two") } + + shared_examples_for 'project search response' do |args = {}| + it 'returns project search responses' do + get v3_api("/projects/search/#{args[:query]}", current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(args[:results]) + json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*#{args[:query]}.*/) } + end + end + + context 'when unauthenticated' do + it_behaves_like 'project search response', query: 'query', results: 1 do + let(:current_user) { nil } + end + end + + context 'when authenticated' do + it_behaves_like 'project search response', query: 'query', results: 6 do + let(:current_user) { user } + end + it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do + let(:current_user) { user } + end + end + + context 'when authenticated as a different user' do + it_behaves_like 'project search response', query: 'query', results: 2, match_regex: /(internal|public) query/ do + let(:current_user) { user2 } + end + end + end + + describe 'PUT /projects/:id' do + before { project } + before { user } + before { user3 } + before { user4 } + before { project3 } + before { project4 } + before { project_member3 } + before { project_member2 } + + context 'when unauthenticated' do + it 'returns authentication error' do + project_param = { name: 'bar' } + put v3_api("/projects/#{project.id}"), project_param + expect(response).to have_http_status(401) + end + end + + context 'when authenticated as project owner' do + it 'updates name' do + project_param = { name: 'bar' } + put v3_api("/projects/#{project.id}", user), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'updates visibility_level' do + project_param = { visibility_level: 20 } + put v3_api("/projects/#{project3.id}", user), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'updates visibility_level from public to private' do + project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + project_param = { public: false } + put v3_api("/projects/#{project3.id}", user), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'does not update name to existing name' do + project_param = { name: project3.name } + put v3_api("/projects/#{project.id}", user), project_param + expect(response).to have_http_status(400) + expect(json_response['message']['name']).to eq(['has already been taken']) + end + + it 'updates request_access_enabled' do + project_param = { request_access_enabled: false } + + put v3_api("/projects/#{project.id}", user), project_param + + expect(response).to have_http_status(200) + expect(json_response['request_access_enabled']).to eq(false) + end + + it 'updates path & name to existing path & name in different namespace' do + project_param = { path: project4.path, name: project4.name } + put v3_api("/projects/#{project3.id}", user), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + end + + context 'when authenticated as project master' do + it 'updates path' do + project_param = { path: 'bar' } + put v3_api("/projects/#{project3.id}", user4), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'updates other attributes' do + project_param = { issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description' } + + put v3_api("/projects/#{project3.id}", user4), project_param + expect(response).to have_http_status(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'does not update path to existing path' do + project_param = { path: project.path } + put v3_api("/projects/#{project3.id}", user4), project_param + expect(response).to have_http_status(400) + expect(json_response['message']['path']).to eq(['has already been taken']) + end + + it 'does not update name' do + project_param = { name: 'bar' } + put v3_api("/projects/#{project3.id}", user4), project_param + expect(response).to have_http_status(403) + end + + it 'does not update visibility_level' do + project_param = { visibility_level: 20 } + put v3_api("/projects/#{project3.id}", user4), project_param + expect(response).to have_http_status(403) + end + end + + context 'when authenticated as project developer' do + it 'does not update other attributes' do + project_param = { path: 'bar', + issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description', + request_access_enabled: true } + put v3_api("/projects/#{project.id}", user3), project_param + expect(response).to have_http_status(403) + end + end + end + + describe 'POST /projects/:id/archive' do + context 'on an unarchived project' do + it 'archives the project' do + post v3_api("/projects/#{project.id}/archive", user) + + expect(response).to have_http_status(201) + expect(json_response['archived']).to be_truthy + end + end + + context 'on an archived project' do + before do + project.archive! + end + + it 'remains archived' do + post v3_api("/projects/#{project.id}/archive", user) + + expect(response).to have_http_status(201) + expect(json_response['archived']).to be_truthy + end + end + + context 'user without archiving rights to the project' do + before do + project.team << [user3, :developer] + end + + it 'rejects the action' do + post v3_api("/projects/#{project.id}/archive", user3) + + expect(response).to have_http_status(403) + end + end + end + + describe 'POST /projects/:id/unarchive' do + context 'on an unarchived project' do + it 'remains unarchived' do + post v3_api("/projects/#{project.id}/unarchive", user) + + expect(response).to have_http_status(201) + expect(json_response['archived']).to be_falsey + end + end + + context 'on an archived project' do + before do + project.archive! + end + + it 'unarchives the project' do + post v3_api("/projects/#{project.id}/unarchive", user) + + expect(response).to have_http_status(201) + expect(json_response['archived']).to be_falsey + end + end + + context 'user without archiving rights to the project' do + before do + project.team << [user3, :developer] + end + + it 'rejects the action' do + post v3_api("/projects/#{project.id}/unarchive", user3) + + expect(response).to have_http_status(403) + end + end + end + + describe 'POST /projects/:id/star' do + context 'on an unstarred project' do + it 'stars the project' do + expect { post v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response['star_count']).to eq(1) + end + end + + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'does not modify the star count' do + expect { post v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } + + expect(response).to have_http_status(304) + end + end + end + + describe 'DELETE /projects/:id/star' do + context 'on a starred project' do + before do + user.toggle_star(project) + project.reload + end + + it 'unstars the project' do + expect { delete v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) + + expect(response).to have_http_status(200) + expect(json_response['star_count']).to eq(0) + end + end + + context 'on an unstarred project' do + it 'does not modify the star count' do + expect { delete v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } + + expect(response).to have_http_status(304) + end + end + end + + describe 'DELETE /projects/:id' do + context 'when authenticated as user' do + it 'removes project' do + delete v3_api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + end + + it 'does not remove a project if not an owner' do + user3 = create(:user) + project.team << [user3, :developer] + delete v3_api("/projects/#{project.id}", user3) + expect(response).to have_http_status(403) + end + + it 'does not remove a non existing project' do + delete v3_api('/projects/1328', user) + expect(response).to have_http_status(404) + end + + it 'does not remove a project not attached to user' do + delete v3_api("/projects/#{project.id}", user2) + expect(response).to have_http_status(404) + end + end + + context 'when authenticated as admin' do + it 'removes any existing project' do + delete v3_api("/projects/#{project.id}", admin) + expect(response).to have_http_status(200) + end + + it 'does not remove a non existing project' do + delete v3_api('/projects/1328', admin) + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb index 68b196d9033..ae6e708cf87 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/api_helpers.rb @@ -17,8 +17,8 @@ module ApiHelpers # => "/api/v2/issues?foo=bar&private_token=..." # # Returns the relative path to the requested API resource - def api(path, user = nil) - "/api/#{API::API.version}#{path}" + + def api(path, user = nil, version: API::API.version) + "/api/#{version}#{path}" + # Normalize query string (path.index('?') ? '' : '?') + @@ -31,6 +31,11 @@ module ApiHelpers end end + # Temporary helper method for simplifying V3 exclusive API specs + def v3_api(path, user = nil) + api(path, user, version: 'v3') + end + def ci_api(path, user = nil) "/ci/api/v1/#{path}" + -- cgit v1.2.1 From 90114fea5f4ec410fd3a01892706ad6a066efa43 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 31 Jan 2017 17:00:53 +0600 Subject: hides search button --- app/views/admin/projects/index.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 5936312801b..cf8d438670b 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -40,6 +40,7 @@ = render 'shared/projects/dropdown' = link_to new_project_path, class: 'btn btn-new' do New Project + = button_tag "Search", class: "btn btn-primary btn-search hide" %ul.nav-links - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } -- cgit v1.2.1 From dcac161472d163d1c64555f62c15722c2ee01ec9 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 31 Jan 2017 17:04:23 +0600 Subject: adds changelog --- changelogs/unreleased/redesign-searchbar-admin-project-26794.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/redesign-searchbar-admin-project-26794.yml diff --git a/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml b/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml new file mode 100644 index 00000000000..547a7c6755c --- /dev/null +++ b/changelogs/unreleased/redesign-searchbar-admin-project-26794.yml @@ -0,0 +1,4 @@ +--- +title: Redesign searchbar in admin project list +merge_request: 8776 +author: -- cgit v1.2.1 From a54a734f70273b99b2f34236ebf4abd83dad43dc Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 31 Jan 2017 17:40:37 +0600 Subject: fixes mobile view --- app/views/shared/projects/_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index b7f8551153b..ac028f18e50 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -2,7 +2,7 @@ - personal = params[:personal] - archived = params[:archived] - namespace_id = params[:namespace_id] -.dropdown.inline +.dropdown - toggle_text = projects_sort_options_hash[@sort] = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable -- cgit v1.2.1 From 120f9abaa15ce0feec1dc457ad3dc3787e4fbfc6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 3 Nov 2015 21:28:07 +0100 Subject: 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 --- .../admin/application_settings_controller.rb | 1 + app/controllers/projects_controller.rb | 10 ++ app/models/ci/build.rb | 3 +- app/models/project.rb | 25 +++++ app/policies/project_policy.rb | 1 + app/services/update_pages_service.rb | 15 +++ .../admin/application_settings/_form.html.haml | 8 ++ app/views/projects/edit.html.haml | 35 ++++++ app/workers/pages_worker.rb | 123 +++++++++++++++++++++ config/gitlab.yml.example | 11 ++ config/initializers/1_settings.rb | 6 + config/routes/project.rb | 1 + ...32013_add_pages_size_to_application_settings.rb | 5 + db/schema.rb | 1 + doc/README.md | 1 + doc/install/installation.md | 13 +++ doc/pages/README.md | 12 ++ lib/backup/pages.rb | 13 +++ lib/support/nginx/gitlab-pages | 27 +++++ lib/tasks/gitlab/backup.rake | 21 ++++ shared/pages/.gitkeep | 0 spec/fixtures/pages.tar.gz | Bin 0 -> 1795 bytes spec/fixtures/pages_empty.tar.gz | Bin 0 -> 128 bytes spec/services/update_pages_service_spec.rb | 43 +++++++ spec/tasks/gitlab/backup_rake_spec.rb | 14 ++- spec/workers/pages_worker_spec.rb | 58 ++++++++++ 26 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 app/services/update_pages_service.rb create mode 100644 app/workers/pages_worker.rb create mode 100644 db/migrate/20151215132013_add_pages_size_to_application_settings.rb create mode 100644 doc/pages/README.md create mode 100644 lib/backup/pages.rb create mode 100644 lib/support/nginx/gitlab-pages create mode 100644 shared/pages/.gitkeep create mode 100644 spec/fixtures/pages.tar.gz create mode 100644 spec/fixtures/pages_empty.tar.gz create mode 100644 spec/services/update_pages_service_spec.rb create mode 100644 spec/workers/pages_worker_spec.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 543d5eac504..df8682e246e 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :plantuml_url, :max_artifacts_size, :max_attachment_size, + :max_pages_size, :metrics_enabled, :metrics_host, :metrics_method_call_threshold, diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 444ff837bb3..123dc179e73 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,6 +151,16 @@ class ProjectsController < Projects::ApplicationController 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 ::Projects::HousekeepingService.new(@project).execute diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5fe8ddf69d7..095a346f337 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -256,7 +256,7 @@ module Ci end def project_id - pipeline.project_id + gl_project_id end def project_name @@ -457,6 +457,7 @@ module Ci build_data = Gitlab::DataBuilder::Build.build(self) project.execute_hooks(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) end diff --git a/app/models/project.rb b/app/models/project.rb index 37f4705adbd..48ff5ec7fc7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -53,6 +53,8 @@ class Project < ActiveRecord::Base update_column(:last_activity_at, self.created_at) end + after_destroy :remove_pages + # update visibility_level of forks after_update :update_forks_visibility_level def update_forks_visibility_level @@ -1160,6 +1162,29 @@ class Project < ActiveRecord::Base ensure_runners_token! 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 @wiki ||= ProjectWiki.new(self, self.owner) end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 71ef8901932..63bc639688d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -136,6 +136,7 @@ class ProjectPolicy < BasePolicy can! :remove_fork_project can! :destroy_merge_request can! :destroy_issue + can! :remove_pages end def team_member_owner_access! diff --git a/app/services/update_pages_service.rb b/app/services/update_pages_service.rb new file mode 100644 index 00000000000..818bb94a293 --- /dev/null +++ b/app/services/update_pages_service.rb @@ -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 diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 558bbe07b16..125a805a897 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -186,6 +186,14 @@ = f.text_area :help_page_text, class: 'form-control', rows: 4 .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 %legend Continuous Integration .form-group diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index ec944d4ffb7..89e2d4046b8 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -133,6 +133,41 @@ %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" = 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 %hr .row.prepend-top-default diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb new file mode 100644 index 00000000000..9aa3030264b --- /dev/null +++ b/app/workers/pages_worker.rb @@ -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 diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 42e5f105d46..d41280624ae 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -153,6 +153,17 @@ production: &base # The location where LFS objects are stored (default: 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 ## For enabling Add to Mattermost button mattermost: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4f33aad8693..0c06b73ba36 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -254,6 +254,12 @@ Settings.registry['issuer'] ||= nil 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) +# 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 # diff --git a/config/routes/project.rb b/config/routes/project.rb index f36febc6e04..cd56f6281f5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -329,6 +329,7 @@ constraints(ProjectUrlConstrainer.new) do post :archive post :unarchive post :housekeeping + post :remove_pages post :toggle_star post :preview_markdown post :export diff --git a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb new file mode 100644 index 00000000000..e7fb73190e8 --- /dev/null +++ b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddPagesSizeToApplicationSettings < ActiveRecord::Migration + def up + add_column :application_settings, :max_pages_size, :integer, default: 100, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 5efb4f6595c..15f378b28ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" + t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false diff --git a/doc/README.md b/doc/README.md index 909740211a6..f036febb7b9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,6 +12,7 @@ - [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. - [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 and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. diff --git a/doc/install/installation.md b/doc/install/installation.md index 425c5d93efb..c78e469055d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -313,6 +313,9 @@ sudo usermod -aG redis git # Change the permissions of the directory where CI artifacts are stored 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 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 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. ### Test Configuration diff --git a/doc/pages/README.md b/doc/pages/README.md new file mode 100644 index 00000000000..d08c34c3ebc --- /dev/null +++ b/doc/pages/README.md @@ -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 diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb new file mode 100644 index 00000000000..215ded93bfe --- /dev/null +++ b/lib/backup/pages.rb @@ -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 diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages new file mode 100644 index 00000000000..0eeb0cd1917 --- /dev/null +++ b/lib/support/nginx/gitlab-pages @@ -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 ~^(?.*)\.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; +} diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index a9f1255e8cf..ffab6f492fb 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -13,6 +13,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:uploads:create"].invoke Rake::Task["gitlab:backup:builds: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: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:builds:restore'].invoke unless backup.skipped?('builds') 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:registry:restore'].invoke unless backup.skipped?('registry') Rake::Task['gitlab:shell:setup'].invoke @@ -159,6 +161,25 @@ namespace :gitlab do 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 task create: :environment do $progress.puts "Dumping lfs objects ... ".color(:blue) diff --git a/shared/pages/.gitkeep b/shared/pages/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/fixtures/pages.tar.gz b/spec/fixtures/pages.tar.gz new file mode 100644 index 00000000000..d0e89378b3e Binary files /dev/null and b/spec/fixtures/pages.tar.gz differ diff --git a/spec/fixtures/pages_empty.tar.gz b/spec/fixtures/pages_empty.tar.gz new file mode 100644 index 00000000000..5c2afa1a8f6 Binary files /dev/null and b/spec/fixtures/pages_empty.tar.gz differ diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/update_pages_service_spec.rb new file mode 100644 index 00000000000..ed392cd94ee --- /dev/null +++ b/spec/services/update_pages_service_spec.rb @@ -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 diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index bc751d20ce1..df8a47893f9 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -28,7 +28,7 @@ describe 'gitlab:app namespace rake task' do end 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 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:uploads: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:registry:restore']).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 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(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('builds.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('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 it 'deletes temp directories' do 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 @@ -304,7 +306,7 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do 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/') @@ -312,6 +314,7 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.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).not_to match('repositories/') 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:builds: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:registry:restore']).to receive :invoke expect(Rake::Task['gitlab:shell:setup']).to receive :invoke diff --git a/spec/workers/pages_worker_spec.rb b/spec/workers/pages_worker_spec.rb new file mode 100644 index 00000000000..158a4b3ba8d --- /dev/null +++ b/spec/workers/pages_worker_spec.rb @@ -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 -- cgit v1.2.1 From 732a821d4f00f9812d014b6c58eae2f9a18f7ddd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 15 Dec 2015 22:48:23 +0100 Subject: Fix specs --- app/workers/pages_worker.rb | 3 ++- config/sidekiq_queues.yml | 1 + lib/backup/manager.rb | 2 +- spec/services/update_pages_service_spec.rb | 4 ++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 9aa3030264b..c51ec81c9da 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -34,7 +34,8 @@ class PagesWorker # 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/)) + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') return unless results.compact.all?(&:success?) # Check if we did extract public directory diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 022b0e80917..56bf4e6b1de 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -50,3 +50,4 @@ - [reactive_caching, 1] - [cronjob, 1] - [default, 1] + - [pages, 1] diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index cefbfdce3bb..f099c0651ac 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,6 +1,6 @@ module Backup 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] FILE_NAME_SUFFIX = '_gitlab_backup.tar' diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/update_pages_service_spec.rb index ed392cd94ee..cf1ca15da44 100644 --- a/spec/services/update_pages_service_spec.rb +++ b/spec/services/update_pages_service_spec.rb @@ -5,6 +5,10 @@ describe UpdatePagesService, services: true do let(:data) { Gitlab::BuildDataBuilder.build(build) } let(:service) { UpdatePagesService.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' } -- cgit v1.2.1 From ac09f857cd9edd4a18280f617b48fe436109ceaa Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:53:35 +0100 Subject: Remove locking and add force to FileUtils methods --- app/workers/pages_worker.rb | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index c51ec81c9da..59f4b4f16f4 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -5,7 +5,7 @@ class PagesWorker BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte - sidekiq_options queue: :pages + sidekiq_options queue: :pages, retry: false def perform(build_id) @build_id = build_id @@ -44,25 +44,17 @@ class PagesWorker 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 + # Ignore deployment if the HEAD changed when we were extracting the archive + 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 + FileUtils.move(public_path, previous_public_path, force: true) + FileUtils.move(temp_public_path, public_path) + FileUtils.rm_r(previous_public_path, force: true) @status.success end -- cgit v1.2.1 From fa68e403e0d5539b19ceb5396394d634babdc2b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:53:54 +0100 Subject: Support https and custom port for pages --- app/models/project.rb | 7 +++++-- config/gitlab.yml.example | 2 ++ config/initializers/1_settings.rb | 28 +++++++++++++++++++--------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 48ff5ec7fc7..aa16b055e81 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1165,11 +1165,14 @@ class Project < ActiveRecord::Base def pages_url if Dir.exist?(public_pages_path) host = "#{namespace.path}.#{Settings.pages.domain}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end # If the project path is the same as host, leave the short version - return "http://#{host}" if host == path + return url if host == path - "http://#{host}/#{path}" + "#{url}/#{path}" end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index d41280624ae..fa764844cb9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -163,6 +163,8 @@ production: &base # http://group.example.com/project # or project path can be a group page: group.example.com domain: 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 ## Mattermost ## For enabling Add to Mattermost button diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0c06b73ba36..3932745e20c 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -5,8 +5,8 @@ class Settings < Settingslogic namespace Rails.env class << self - def gitlab_on_standard_port? - gitlab.port.to_i == (gitlab.https ? 443 : 80) + def on_standard_port?(config) + config.port.to_i == (config.https ? 443 : 80) end def host_without_www(url) @@ -14,7 +14,7 @@ class Settings < Settingslogic end def build_gitlab_ci_url - if gitlab_on_standard_port? + if on_standard_port?(gitlab) custom_port = nil else custom_port = ":#{gitlab.port}" @@ -27,6 +27,10 @@ class Settings < Settingslogic ].join('') end + def build_pages_url + base_url(pages).join('') + end + def build_gitlab_shell_ssh_path_prefix user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" @@ -42,11 +46,11 @@ class Settings < Settingslogic end def build_base_gitlab_url - base_gitlab_url.join('') + base_url(gitlab).join('') end def build_gitlab_url - (base_gitlab_url + [gitlab.relative_url_root]).join('') + (base_url(gitlab) + [gitlab.relative_url_root]).join('') end # check that values in `current` (string or integer) is a contant in `modul`. @@ -74,11 +78,11 @@ class Settings < Settingslogic private - def base_gitlab_url - custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" - [ gitlab.protocol, + def base_url(config) + custom_port = on_standard_port?(config) ? nil : ":#{config.port}" + [ config.protocol, "://", - gitlab.host, + config.host, custom_port ] end @@ -254,11 +258,17 @@ Settings.registry['issuer'] ||= nil 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) +# # 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" +Settings.pages['https'] = false if Settings.pages['https'].nil? +Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 +Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" +Settings.pages['url'] ||= Settings.send(:build_pages_url) # # Git LFS -- cgit v1.2.1 From b27371d89848b35a1be06e253d0958f723fc242f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 12:59:01 +0100 Subject: Change pages domain to host --- app/models/project.rb | 2 +- config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index aa16b055e81..a1888c089ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1164,7 +1164,7 @@ class Project < ActiveRecord::Base def pages_url if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.domain}" + host = "#{namespace.path}.#{Settings.pages.host}" url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| "#{prefix}#{namespace.path}." end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index fa764844cb9..c6f06d43d07 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -162,7 +162,7 @@ production: &base # 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 + 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 diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 3932745e20c..b938a9cdb2a 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -264,7 +264,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' 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" +Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http" -- cgit v1.2.1 From adc1a9abb5adbf746b492938cb11576753edcc7e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 13:04:05 +0100 Subject: Re-add missing gitlab_on_standard_port --- config/initializers/1_settings.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b938a9cdb2a..842ea94c5a4 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -5,8 +5,8 @@ class Settings < Settingslogic namespace Rails.env class << self - def on_standard_port?(config) - config.port.to_i == (config.https ? 443 : 80) + def gitlab_on_standard_port? + on_standard_port?(gitlab) end def host_without_www(url) @@ -87,6 +87,10 @@ class Settings < Settingslogic ] end + def on_standard_port?(config) + config.port.to_i == (config.https ? 443 : 80) + end + # Extract the host part of the given +url+. def host(url) url = url.downcase -- cgit v1.2.1 From d28f1a7f4aa4bdf664e04a43022667e4e7637e73 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 16:29:53 +0100 Subject: Split PagesWorker --- app/workers/pages_worker.rb | 101 ++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 59f4b4f16f4..c34259c15f1 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -12,62 +12,83 @@ class PagesWorker 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 = create_status @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 + raise 'pages are outdated' unless latest? # 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/), - err: '/dev/null') - return unless results.compact.all?(&:success?) + Dir.mktmpdir(nil, tmp_path) do |archive_path| + results = extract_archive(archive_path) + raise 'pages failed to extract' unless results.all?(&:success?) # Check if we did extract public directory - temp_public_path = File.join(temp_path, 'public') - return unless Dir.exists?(temp_public_path) + archive_public_path = File.join(archive_path, 'public') + raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) + raise 'pages are outdated' unless latest? + deploy_page!(archive_public_path) - FileUtils.mkdir_p(pages_path) + @status.success + end + rescue => e + fail(e.message, !latest?) + end - # Ignore deployment if the HEAD changed when we were extracting the archive - return unless valid? + private - # 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.move(public_path, previous_public_path, force: true) - FileUtils.move(temp_public_path, public_path) - FileUtils.rm_r(previous_public_path, force: true) + def create_status + GenericCommitStatus.new( + project: project, + commit: build.commit, + user: build.user, + ref: build.ref, + stage: 'deploy', + name: 'pages:deploy' + ) + end - @status.success - end + def extract_archive(temp_path) + results = Open3.pipeline(%W(gunzip -c #{artifacts}), + %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') + results.compact + 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) + FileUtils.move(public_path, previous_public_path, force: true) + FileUtils.move(archive_public_path, public_path) ensure - @status.drop if @status && @status.active? + FileUtils.rm_r(previous_public_path, force: true) end - private + def fail(message, allow_failure = true) + @status.allow_failure = allow_failure + @status.description = message + @status.drop + end def valid? + build && build.artifacts_file? + end + + def latest? # 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 + sha == latest_sha + end + + def blocks + # 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 + blocks end def build -- cgit v1.2.1 From 35dd2e12837ea507a02aca239fc7f78f949e9e99 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 16 Dec 2015 17:09:11 +0100 Subject: Fix tests --- app/workers/pages_worker.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index c34259c15f1..6c6bb7ed13f 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -14,23 +14,26 @@ class PagesWorker # Create status notifying the deployment of pages @status = create_status @status.run! + 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| - results = extract_archive(archive_path) - raise 'pages failed to extract' unless results.all?(&:success?) + 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.exists?(archive_public_path) raise 'pages are outdated' unless latest? + deploy_page!(archive_public_path) @status.success end rescue => e fail(e.message, !latest?) + return false end private @@ -46,12 +49,12 @@ class PagesWorker ) end - def extract_archive(temp_path) + def extract_archive!(temp_path) results = Open3.pipeline(%W(gunzip -c #{artifacts}), %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), %W(tar -x -C #{temp_path} public/), err: '/dev/null') - results.compact + raise 'pages failed to extract' unless results.compact.all?(&:success?) end def deploy_page!(archive_public_path) @@ -61,7 +64,10 @@ class PagesWorker # 2. We move temporary public to be deployed public # 3. We remove previous public path FileUtils.mkdir_p(pages_path) - FileUtils.move(public_path, previous_public_path, force: true) + 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) -- cgit v1.2.1 From 0f2274cc14f1a4071d5b4ab258209e8c1b14d698 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:03:58 +0200 Subject: First draft of pages documentation --- doc/pages/README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index d08c34c3ebc..548a24c4cc4 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,12 +1,59 @@ # GitLab Pages -To start using GitLab Pages add to your project .gitlab-ci.yml with special pages job. +_**Note:** This feature was introduced in GitLab EE 8.3_ - pages: - image: jekyll - script: jekyll build - artifacts: - paths: - - public +To start using GitLab Pages add to your project `.gitlab-ci.yml` the special +`pages` job. The example below is using [jekyll][] and assumes the created +HTML files are generated under the `public/` directory which resides under the +root directory of your Git repository. -TODO +```yaml +pages: + image: jekyll + script: jekyll build + artifacts: + paths: + - public +``` + +- 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 + +## Examples + +- Add example with stages. `test` using a linter tool, `deploy` in `pages` +- Add examples of more static tool generators + +```yaml +image: jekyll + +stages: + - test + - deploy + +lint: + script: jekyll build + stage: test + +pages: + script: jekyll build + stage: deploy + artifacts: + paths: + - public +``` + +## Current limitations + +- We currently support only http and port 80. It will be extended in the future. -- cgit v1.2.1 From c57881395bd7ba92b8b985545b239b00f3100284 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:07:35 +0200 Subject: Add missing jekyll website link --- doc/pages/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 548a24c4cc4..3353561b9a9 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -57,3 +57,5 @@ pages: ## Current limitations - We currently support only http and port 80. It will be extended in the future. + +[jekyll]: http://jekyllrb.com/ -- cgit v1.2.1 From f934460e81d954afb10049d4d596744e1e7b0cab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 11:09:52 +0200 Subject: Fix wrong assumption that the public dir must be present in your git repo --- doc/pages/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 3353561b9a9..ceda2a38915 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,8 +4,7 @@ _**Note:** This feature was introduced in GitLab EE 8.3_ To start using GitLab Pages add to your project `.gitlab-ci.yml` the special `pages` job. The example below is using [jekyll][] and assumes the created -HTML files are generated under the `public/` directory which resides under the -root directory of your Git repository. +HTML files are generated under the `public/` directory. ```yaml pages: -- cgit v1.2.1 From f8b0d06b3d341b822c6a2b7f54c979f7c0e86b94 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 17 Dec 2015 15:33:43 +0100 Subject: Fix pages storage path --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 842ea94c5a4..e52171f0d64 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -267,7 +267,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' # 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['path'] ||= File.expand_path('shared/pages/', Rails.root) Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 -- cgit v1.2.1 From ab220022f40b526ce5b937caed2a13b4b1ca239b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 18:03:38 +0200 Subject: Add GitLab Pages administration guide --- doc/README.md | 1 + doc/pages/administration.md | 146 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 doc/pages/administration.md diff --git a/doc/README.md b/doc/README.md index f036febb7b9..951a302f8ba 100644 --- a/doc/README.md +++ b/doc/README.md @@ -54,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. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. +- [GitLab Pages configuration](pages/administration.md) - [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. - [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. diff --git a/doc/pages/administration.md b/doc/pages/administration.md new file mode 100644 index 00000000000..e754d4cbd96 --- /dev/null +++ b/doc/pages/administration.md @@ -0,0 +1,146 @@ +# GitLab Pages Administration + +_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ + +If you are looking for ways to upload your static content in GitLab Pages, you +probably want to read the [user documentation](README.md). + +## Configuration + +There are a couple of things to consider before enabling GitLab pages in your +GitLab EE instance. + +1. You need to properly configure your DNS to point to the domain that pages + will be served +1. Pages use a separate nginx configuration file which needs to be explicitly + added in the server under which GitLab EE runs + +Both of these settings are described in detail in the sections below. + +### DNS configuration + +GitLab Pages expect to run on their own virtual host. In your DNS 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: + +``` +*.gitlabpages.com. 60 IN A 1.2.3.4 +``` + +where `gitlabpages.com` is the domain under which GitLab Pages will be served +and `1.2.3.4` is the IP address of your GitLab instance. + +It is strongly advised to **not** use the GitLab domain to serve user pages. +See [security](#security). + +### Omnibus package installations + +See the relevant documentation at . + +### Installations from source + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` in + order to enable the pages feature: + + ```bash + ## GitLab Pages + pages: + enabled: true + # 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 + ``` + +1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` + + Don't forget to add your domain name in the Nginx config. For example if your + GitLab pages domain is `gitlabpages.com`, replace + + ```bash + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + ``` + + with + + ``` + server_name ~^(?.*)\.gitlabpages\.com$; + ``` + + You must be extra careful to not remove the backslashes. + +1. Restart Nginx and GitLab: + + ```bash + sudo service nginx restart + sudo service gitlab restart + ``` + +### Running GitLab pages with HTTPS + +If you want the pages to be served under HTTPS, a wildcard SSL certificate is +required. + +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # 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: gitlabpages.com + port: 443 # Set to 443 if you serve the pages with HTTPS + https: true # Set to true if you serve the pages with HTTPS + ``` +1. Use the `gitlab-pages-ssl` Nginx configuration file + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` + + Make sure to edit the config and add your domain as well as correctly point + to the right location where the SSL certificates reside. + +## Set maximum pages size + +The maximum size of the unpacked archive can be configured in the Admin area +under the Application settings in the **Maximum size of pages (MB)**. +The default is 100MB. + +## Security + +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS. + +## How it works + +- 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 are part of backups +- Pages notify the deployment status using Commit Status API +- Pages use a new sidekiq queue: pages + +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record -- cgit v1.2.1 From a60f5f6cb429ff232647665ab288b3df251669e5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 19:38:21 +0200 Subject: GitLab Pages admin guide clean up [ci skip] - Fix markdown - Remove how it works section, maybe add it at a later point --- doc/pages/administration.md | 129 +++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index e754d4cbd96..02e575c851a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -41,56 +41,55 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; - ``` + ```bash + server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + ``` - with + with - ``` - server_name ~^(?.*)\.gitlabpages\.com$; - ``` + ``` + server_name ~^(?.*)\.gitlabpages\.com$; + ``` - You must be extra careful to not remove the backslashes. + You must be extra careful to not remove the backslashes. 1. Restart Nginx and GitLab: - ```bash - sudo service nginx restart - sudo service gitlab restart - ``` + ```bash + sudo service nginx restart + sudo service gitlab restart + ``` ### Running GitLab pages with HTTPS @@ -99,29 +98,29 @@ required. 1. In `gitlab.yml`, set the port to `443` and https to `true`: - ```bash - ## GitLab Pages - pages: - enabled: true - # 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: gitlabpages.com - port: 443 # Set to 443 if you serve the pages with HTTPS - https: true # Set to true if you serve the pages with HTTPS + ```bash + ## GitLab Pages + pages: + enabled: true + # 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: gitlabpages.com + port: 443 # Set to 443 if you serve the pages with HTTPS + https: true # Set to true if you serve the pages with HTTPS ``` -1. Use the `gitlab-pages-ssl` Nginx configuration file +1. Copy the `gitlab-pages-ssl` Nginx configuration file: - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + ``` - Make sure to edit the config and add your domain as well as correctly point - to the right location where the SSL certificates reside. + Make sure to edit the config to add your domain as well as correctly point + to the right location where the SSL certificates reside. ## Set maximum pages size @@ -129,18 +128,14 @@ The maximum size of the unpacked archive can be configured in the Admin area under the Application settings in the **Maximum size of pages (MB)**. The default is 100MB. -## Security +## Backup -You should strongly consider running GitLab pages under a different hostname -than GitLab to prevent XSS. +Pages are part of the regular backup so there is nothing to configure. -## How it works +## Security -- 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 are part of backups -- Pages notify the deployment status using Commit Status API -- Pages use a new sidekiq queue: pages +You should strongly consider running GitLab pages under a different hostname +than GitLab to prevent XSS attacks. [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record -- cgit v1.2.1 From 38028d92539afb70ab6c1d5ec0d03299a9d2c1db Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 19:56:07 +0200 Subject: Add example when using a subdomain [ci skip] --- doc/pages/administration.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 02e575c851a..6e242efae9f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -45,7 +45,8 @@ See the relevant documentation at .*)\.gitlabpages\.com$; ``` - You must be extra careful to not remove the backslashes. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: @@ -108,7 +115,7 @@ required. # 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: gitlabpages.com + host: example.com port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` @@ -120,7 +127,8 @@ required. ``` Make sure to edit the config to add your domain as well as correctly point - to the right location where the SSL certificates reside. + to the right location where the SSL certificates reside. After all changes + restart Nginx. ## Set maximum pages size -- cgit v1.2.1 From 94fdf58a87f8f2cc54c0482a2fce9e3fa425d4b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 17 Dec 2015 19:25:28 +0100 Subject: Store pages in shared/pages/fqdn/fqdn/public or shared/pages/fqdn/subpath/public - makes it simpler to implement CNAMEs in future --- app/models/project.rb | 14 ++++--- app/workers/pages_worker.rb | 4 -- doc/pages/administration.md | 12 ++---- lib/support/nginx/gitlab-pages | 11 ++++-- lib/support/nginx/gitlab-pages-ssl | 80 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/app/models/project.rb b/app/models/project.rb index a1888c089ce..9cdd01e433d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1137,6 +1137,7 @@ class Project < ActiveRecord::Base issues.opened.count end +<<<<<<< HEAD def visibility_level_allowed_as_fork?(level = self.visibility_level) return true unless forked? @@ -1162,22 +1163,23 @@ class Project < ActiveRecord::Base ensure_runners_token! end + def pages_host + "#{namespace.path}.#{Settings.pages.host}" + end + def pages_url if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" - url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| - "#{prefix}#{namespace.path}." - end + url = Gitlab.config.pages.url.sub(Settings.pages.host, pages_host) # If the project path is the same as host, leave the short version - return url if host == path + return url if pages_host == path "#{url}/#{path}" end end def pages_path - File.join(Settings.pages.path, path_with_namespace) + File.join(Settings.pages.path, pages_host, path) end def public_pages_path diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 6c6bb7ed13f..836e8d8ad9d 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,10 +121,6 @@ class PagesWorker @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 diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 6e242efae9f..28e975e5621 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -74,22 +74,16 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name *.YOUR_GITLAB_PAGES.DOMAIN; ``` with ``` - server_name ~^(?.*)\.gitlabpages\.com$; + server_name *.gitlabpages.com; ``` - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: - - ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` + You must be add `*` in front of your domain, this is required to catch all subdomains of gitlabpages.com. 1. Restart Nginx and GitLab: diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 0eeb0cd1917..6300c268521 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,18 +1,21 @@ +## 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$; - root /home/git/gitlab/shared/pages/${group}; + server_name *.YOUR_GITLAB_PAGES.DOMAIN; + root /home/git/gitlab/shared/pages/${host}; ## 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 + # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ + # 2. Try to get / from => shared/pages/${host}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl new file mode 100644 index 00000000000..d3e8379ed29 --- /dev/null +++ b/lib/support/nginx/gitlab-pages-ssl @@ -0,0 +1,80 @@ +## 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; + + 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; + + ## 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 + root /home/git/gitlab/shared/pages/${host}; + + ## 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; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; + + ## [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; + + # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ + # 2. Try to get / from => shared/pages/${host}/${host}/public/ + 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; +} -- cgit v1.2.1 From 6fd06943f9135fcaa406f8df0c016981067638a5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 21:03:14 +0200 Subject: Point to GP administration guide, no need to duplicate things [ci skip] --- doc/install/installation.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index c78e469055d..4496243da25 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -313,7 +313,7 @@ sudo usermod -aG redis git # Change the permissions of the directory where CI artifacts are stored sudo chmod -R u+rwX shared/artifacts/ - # Change the permissions of the directory where CI artifacts are stored + # Change the permissions of the directory where GitLab Pages are stored sudo chmod -R ug+rwX shared/pages/ # Copy the example Unicorn config @@ -487,16 +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 sudo editor /etc/nginx/sites-available/gitlab -Copy the GitLab pages site config: +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](../pages/administration.md). - 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. ### Test Configuration -- cgit v1.2.1 From 4e03436befb2c912549440236456c063aa628e51 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 17 Dec 2015 21:29:24 +0200 Subject: Small lines improvement [ci skip] --- doc/pages/administration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 28e975e5621..7feeddd496b 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -83,7 +83,8 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 00:44:11 +0200 Subject: Finish GitLab Pages user documentation --- doc/pages/README.md | 141 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 41 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ceda2a38915..1d6ea1991f8 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,60 +1,119 @@ # GitLab Pages -_**Note:** This feature was introduced in GitLab EE 8.3_ +_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ -To start using GitLab Pages add to your project `.gitlab-ci.yml` the special -`pages` job. The example below is using [jekyll][] and assumes the created -HTML files are generated under the `public/` directory. +GitLab Pages allow you to host static content + +## Enable the pages feature in your GitLab EE instance + +The administrator guide is located at [administration](administration.md). + +## Understanding how GitLab Pages work + +GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. +The steps that are performed from the initialization of a project to the +creation of the static content, can be summed up to: + +1. Create project (its name could be specific according to the case) +1. Enable the GitLab Pages feature under the project's settings +1. Provide a specific job in `.gitlab-ci.yml` +1. GitLab Runner builds the project +1. GitLab CI uploads the artifacts +1. Nginx serves the content + +As a user, you should normally be concerned only with the first three items. + +In general there are four kinds of pages one might create. This is better +explained with an example so let's make some assumptions. + +The domain under which the pages are hosted is named `gitlab.io`. There is a +user with the username `walter` and they are the owner of an organization named +`therug`. The personal project of `walter` is named `area51` and don't forget +that the organization has also a project under its namespace, called +`welovecats`. + +The following table depicts what the project's name should be and under which +URL it will be accessible. + +| Pages type | Repository name | URL schema | +| ---------- | --------------- | ---------- | +| User page | `walter/walter.gitlab.io` | `https://walter.gitlab.io` | +| Group page | `therug/therug.gitlab.io` | `https://therug.gitlab.io` | +| Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | +| Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | + +## Enable the pages feature in your project + +The GitLab Pages feature needs to be explicitly enabled for each project +under its **Settings**. + +## Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +in a project's **Settings**. + +## Explore the contents of .gitlab-ci.yml + +Before reading this section, make sure you familiarize yourself with GitLab CI +and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +following our [quick start guide](../ci/quick_start/README.md). + +--- + +To make use of GitLab Pages your `.gitlab-ci.yml` must follow the rules below: + +1. A special `pages` job must be defined +1. Any static content must be placed under a `public/` directory +1. `artifacts` with a path to the `public/` directory must be defined + +The pages are created after the build completes successfully and the artifacts +for the `pages` job are uploaded to GitLab. + +The example below is using [Jekyll][] and assumes that the created HTML files +are generated under the `public/` directory. ```yaml +image: ruby:2.1 + pages: - image: jekyll - script: jekyll build + script: + - gem install jekyll + - jekyll build -d public/ artifacts: paths: - public ``` -- 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 - -## Examples - -- Add example with stages. `test` using a linter tool, `deploy` in `pages` -- Add examples of more static tool generators +## Example projects -```yaml -image: jekyll +Below is a list of example projects that make use of static generators. +Contributions are very welcome. -stages: - - test - - deploy +* [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -lint: - script: jekyll build - stage: test +## Custom error codes pages -pages: - script: jekyll build - stage: deploy - artifacts: - paths: - - public -``` +You can provide your own 403 and 404 error pages by creating the `403.html` and +`404.html` files respectively in the `public/` directory that will be included +in the artifacts. + +## Frequently Asked Questions + +**Q:** Where are my generated pages stored? + +**A:** All content is located by default under `shared/pages/` in the root +directory of the GitLab installation. To be exact, all specific projects under +a namespace are stored ind `shared/pages/${namespace}/${project}/public/` and +all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. + +--- + +**Q:** Can I download my generated pages? -## Current limitations +**A:** Sure. All you need is to download the artifacts archive from the build + page. -- We currently support only http and port 80. It will be extended in the future. +--- [jekyll]: http://jekyllrb.com/ +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -- cgit v1.2.1 From 139ebce691fd9575ebde46111accb86d03c12b21 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:30:16 +0200 Subject: Clean up the text in pages view --- app/views/projects/edit.html.haml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 89e2d4046b8..f9c6809b903 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -142,25 +142,32 @@ .panel-body - if @project.pages_url %strong - Congratulations. Your pages are served at: + 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. + 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/pages/README.html", target: :blank}. %p - Use existing tools: + In the example below we define a special job named + %code pages + which is using Jekyll to build a static site. The generated + HTML will be stored in the + %code public/ + directory which will then be archived and uploaded to GitLab. + The name of the directory should not be different than + %code public/ + in order for the pages to work. %ul %li %pre :plain pages: - image: jekyll - script: jekyll build + image: jekyll/jekyll + script: jekyll build -d public/ artifacts: paths: - - public + - public/ - if @project.pages_url && can?(current_user, :remove_pages, @project) .form-actions -- cgit v1.2.1 From cab3ea0044c4b072fa0fd41eb21ea664830cac1f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:57:09 +0200 Subject: Add missing intro [ci skip] --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 1d6ea1991f8..1afe2a97036 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -2,7 +2,9 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ -GitLab Pages allow you to host static content +With GitLab Pages you can host for free 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. ## Enable the pages feature in your GitLab EE instance -- cgit v1.2.1 From 31454eb2f0f6bf960be5a51f0d8073672ea621a9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 01:57:32 +0200 Subject: Capitalize Pages [ci skip] --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 7feeddd496b..02d737aedbb 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -93,7 +93,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 02:20:29 +0200 Subject: Add section on storage path [ci skip] --- doc/pages/administration.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 02d737aedbb..8df861f70ec 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -48,7 +48,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 12:18:00 +0100 Subject: Revert "Small lines improvement [ci skip]" This reverts commit 0d73bd93bab349d84910cf14773633b8a66df466. --- doc/pages/administration.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 8df861f70ec..29762829819 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -83,8 +83,7 @@ See the relevant documentation at Date: Fri, 18 Dec 2015 12:18:11 +0100 Subject: Revert "Store pages in shared/pages/fqdn/fqdn/public or shared/pages/fqdn/subpath/public - makes it simpler to implement CNAMEs in future" This reverts commit 86a2a78f0d13a678899460638add6b862059433e. --- app/models/project.rb | 14 +++---- app/workers/pages_worker.rb | 4 ++ doc/pages/administration.md | 12 ++++-- lib/support/nginx/gitlab-pages | 11 ++---- lib/support/nginx/gitlab-pages-ssl | 80 -------------------------------------- 5 files changed, 23 insertions(+), 98 deletions(-) delete mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/app/models/project.rb b/app/models/project.rb index 9cdd01e433d..a1888c089ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1137,7 +1137,6 @@ class Project < ActiveRecord::Base issues.opened.count end -<<<<<<< HEAD def visibility_level_allowed_as_fork?(level = self.visibility_level) return true unless forked? @@ -1163,23 +1162,22 @@ class Project < ActiveRecord::Base ensure_runners_token! end - def pages_host - "#{namespace.path}.#{Settings.pages.host}" - end - def pages_url if Dir.exist?(public_pages_path) - url = Gitlab.config.pages.url.sub(Settings.pages.host, pages_host) + host = "#{namespace.path}.#{Settings.pages.host}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end # If the project path is the same as host, leave the short version - return url if pages_host == path + return url if host == path "#{url}/#{path}" end end def pages_path - File.join(Settings.pages.path, pages_host, path) + File.join(Settings.pages.path, path_with_namespace) end def public_pages_path diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 836e8d8ad9d..6c6bb7ed13f 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,6 +121,10 @@ class PagesWorker @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 diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 29762829819..98a26ec7be9 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -74,16 +74,22 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; ``` with ``` - server_name *.gitlabpages.com; + server_name ~^(?.*)\.gitlabpages\.com$; ``` - You must be add `*` in front of your domain, this is required to catch all subdomains of gitlabpages.com. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 6300c268521..0eeb0cd1917 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,21 +1,18 @@ -## 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; - root /home/git/gitlab/shared/pages/${host}; + server_name ~^(?.*)\.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/ from => shared/pages/${host}/${project}/public/ - # 2. Try to get / from => shared/pages/${host}/${host}/public/ + # 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" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl deleted file mode 100644 index d3e8379ed29..00000000000 --- a/lib/support/nginx/gitlab-pages-ssl +++ /dev/null @@ -1,80 +0,0 @@ -## 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; - - 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; - - ## 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 - root /home/git/gitlab/shared/pages/${host}; - - ## 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; - # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 5s; - - ## [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; - - # 1. Try to get /project/ from => shared/pages/${host}/${project}/public/ - # 2. Try to get / from => shared/pages/${host}/${host}/public/ - 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; -} -- cgit v1.2.1 From 2c2447771f098f7c8d692e7318d8f822df468b48 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 12:40:34 +0100 Subject: Rename pages namespace or project path when changed - Move UploadsTransfer to ProjectTransfer and inherit from this to UploadsTransfer and PagesTransfer --- app/models/namespace.rb | 1 + app/models/project.rb | 1 + app/services/projects/transfer_service.rb | 3 ++ app/workers/pages_worker.rb | 4 -- doc/pages/administration.md | 1 + lib/gitlab/pages_transfer.rb | 7 ++++ lib/gitlab/project_transfer.rb | 35 +++++++++++++++++ lib/gitlab/uploads_transfer.rb | 30 +-------------- spec/lib/gitlab/project_transfer_spec.rb | 51 +++++++++++++++++++++++++ spec/lib/gitlab/uploads_transfer_spec.rb | 50 ------------------------ spec/services/projects/transfer_service_spec.rb | 2 + 11 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 lib/gitlab/pages_transfer.rb create mode 100644 lib/gitlab/project_transfer.rb create mode 100644 spec/lib/gitlab/project_transfer_spec.rb delete mode 100644 spec/lib/gitlab/uploads_transfer_spec.rb diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 67d8c1c2e4c..2fb2eb44aaa 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -130,6 +130,7 @@ class Namespace < ActiveRecord::Base end Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + Gitlab::PagesTransfer.new.rename_namespace(path_was, path) remove_exports! diff --git a/app/models/project.rb b/app/models/project.rb index a1888c089ce..e9c7108e805 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -961,6 +961,7 @@ class Project < ActiveRecord::Base 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::PagesTransfer.new.rename_project(path_was, path, namespace.path) end # Expires various caches before a project is renamed. diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 34ec575e808..20b049b5973 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -64,6 +64,9 @@ module Projects # Move uploads 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 SystemHooksService.new.execute_hooks_for(project, :transfer) diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 6c6bb7ed13f..836e8d8ad9d 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -121,10 +121,6 @@ class PagesWorker @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 diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 98a26ec7be9..2356a123fa3 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -119,6 +119,7 @@ required. port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` + 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash diff --git a/lib/gitlab/pages_transfer.rb b/lib/gitlab/pages_transfer.rb new file mode 100644 index 00000000000..fb215f27cbd --- /dev/null +++ b/lib/gitlab/pages_transfer.rb @@ -0,0 +1,7 @@ +module Gitlab + class PagesTransfer < ProjectTransfer + def root_dir + Gitlab.config.pages.path + end + end +end diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb new file mode 100644 index 00000000000..1bba0b78e2f --- /dev/null +++ b/lib/gitlab/project_transfer.rb @@ -0,0 +1,35 @@ +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 diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb index be8fcc7b2d2..81701831a6a 100644 --- a/lib/gitlab/uploads_transfer.rb +++ b/lib/gitlab/uploads_transfer.rb @@ -1,33 +1,5 @@ module Gitlab - class UploadsTransfer - 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 - + class UploadsTransfer < ProjectTransfer def root_dir File.join(Rails.root, "public", "uploads") end diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb new file mode 100644 index 00000000000..e2d6b1b9ab7 --- /dev/null +++ b/spec/lib/gitlab/project_transfer_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Gitlab::ProjectTransfer, lib: true do + before do + @root_dir = File.join(Rails.root, "public", "uploads") + @project_transfer = Gitlab::ProjectTransfer.new + allow(@project_transfer).to receive(:root_dir).and_return(@root_dir) + + @project_path_was = "test_project_was" + @project_path = "test_project" + @namespace_path_was = "test_namespace_was" + @namespace_path = "test_namespace" + end + + after do + FileUtils.rm_rf([ + File.join(@root_dir, @namespace_path), + File.join(@root_dir, @namespace_path_was) + ]) + end + + describe '#move_project' do + it "moves project upload to another namespace" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) + @project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end + + describe '#rename_project' do + it "renames project" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) + @project_transfer.rename_project(@project_path_was, @project_path, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end + + describe '#rename_namespace' do + it "renames namespace" do + FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) + @project_transfer.rename_namespace(@namespace_path_was, @namespace_path) + + expected_path = File.join(@root_dir, @namespace_path, @project_path) + expect(Dir.exist?(expected_path)).to be_truthy + end + end +end diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb deleted file mode 100644 index 4092f7fb638..00000000000 --- a/spec/lib/gitlab/uploads_transfer_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' - -describe Gitlab::UploadsTransfer, lib: true do - before do - @root_dir = File.join(Rails.root, "public", "uploads") - @upload_transfer = Gitlab::UploadsTransfer.new - - @project_path_was = "test_project_was" - @project_path = "test_project" - @namespace_path_was = "test_namespace_was" - @namespace_path = "test_namespace" - end - - after do - FileUtils.rm_rf([ - File.join(@root_dir, @namespace_path), - File.join(@root_dir, @namespace_path_was) - ]) - end - - describe '#move_project' do - it "moves project upload to another namespace" do - FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) - @upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path) - - expected_path = File.join(@root_dir, @namespace_path, @project_path) - expect(Dir.exist?(expected_path)).to be_truthy - end - end - - describe '#rename_project' do - it "renames project" do - FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was)) - @upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path) - - expected_path = File.join(@root_dir, @namespace_path, @project_path) - expect(Dir.exist?(expected_path)).to be_truthy - end - end - - describe '#rename_namespace' do - it "renames namespace" do - FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path)) - @upload_transfer.rename_namespace(@namespace_path_was, @namespace_path) - - expected_path = File.join(@root_dir, @namespace_path, @project_path) - expect(Dir.exist?(expected_path)).to be_truthy - end - end -end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 1540b90163a..5d5812c2c15 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -9,6 +9,8 @@ describe Projects::TransferService, services: true do before do allow_any_instance_of(Gitlab::UploadsTransfer). to receive(:move_project).and_return(true) + allow_any_instance_of(Gitlab::PagesTransfer). + to receive(:move_project).and_return(true) group.add_owner(user) @result = transfer_project(project, user, group) end -- cgit v1.2.1 From d26eadf305ecf40d320c3bca0baf9a58288de7a6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:40:13 +0200 Subject: Fix small typos in GP user guide --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 1afe2a97036..552b3436d77 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -62,7 +62,7 @@ following our [quick start guide](../ci/quick_start/README.md). --- -To make use of GitLab Pages your `.gitlab-ci.yml` must follow the rules below: +To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. A special `pages` job must be defined 1. Any static content must be placed under a `public/` directory @@ -105,7 +105,7 @@ in the artifacts. **A:** All content is located by default under `shared/pages/` in the root directory of the GitLab installation. To be exact, all specific projects under -a namespace are stored ind `shared/pages/${namespace}/${project}/public/` and +a namespace are stored in `shared/pages/${namespace}/${project}/public/` and all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. --- -- cgit v1.2.1 From a691e9f494c7a661f9fa8ba199dbe6180d8d0329 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:40:55 +0200 Subject: Minor cleanup, use gitlab.io as an example domain for GP --- doc/pages/administration.md | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2356a123fa3..b1ef2cebb14 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -12,26 +12,26 @@ GitLab EE instance. 1. You need to properly configure your DNS to point to the domain that pages will be served -1. Pages use a separate nginx configuration file which needs to be explicitly +1. Pages use a separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs Both of these settings are described in detail in the sections below. ### DNS configuration -GitLab Pages expect to run on their own virtual host. In your DNS 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: +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: ``` -*.gitlabpages.com. 60 IN A 1.2.3.4 +*.gitlab.io. 60 IN A 1.2.3.4 ``` -where `gitlabpages.com` is the domain under which GitLab Pages will be served +where `gitlab.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. It is strongly advised to **not** use the GitLab domain to serve user pages. -See [security](#security). +For more information see the [security section](#security). ### Omnibus package installations @@ -58,7 +58,7 @@ See the relevant documentation at .*)\.YOUR_GITLAB_PAGES\.DOMAIN$; @@ -80,16 +80,11 @@ See the relevant documentation at .*)\.gitlabpages\.com$; + server_name *.gitlab.io; ``` - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: - - ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` + You must be add `*` in front of your domain, this is required to catch all + subdomains of `gitlab.io`. 1. Restart Nginx and GitLab: @@ -115,7 +110,7 @@ required. # 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 + host: gitlab.io port: 443 # Set to 443 if you serve the pages with HTTPS https: true # Set to true if you serve the pages with HTTPS ``` @@ -128,13 +123,13 @@ required. ``` Make sure to edit the config to add your domain as well as correctly point - to the right location where the SSL certificates reside. After all changes - restart Nginx. + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. ## Set maximum pages size -The maximum size of the unpacked archive can be configured in the Admin area -under the Application settings in the **Maximum size of pages (MB)**. +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. ## Change storage path -- cgit v1.2.1 From 9ff381c492695bf9b76b27047bd0b38a70a4daac Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 13:41:33 +0200 Subject: Add pages to excluded directories in backup task [ci skip] --- doc/raketasks/backup_restore.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index f6b4db71b44..0fb69d63dbe 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -84,6 +84,29 @@ Deleting tmp directories...[DONE] 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 Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. -- cgit v1.2.1 From e9e8a2f60811c460d0bb850da2bb35ea43e35698 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:07:53 +0100 Subject: Asynchronously remove pages --- app/models/project.rb | 6 +++++- app/services/update_pages_service.rb | 2 +- app/workers/pages_worker.rb | 11 ++++++++++- spec/workers/pages_worker_spec.rb | 21 ++++++++++++++------- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index e9c7108e805..7a5bf77c5a9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1186,7 +1186,11 @@ class Project < ActiveRecord::Base end def remove_pages - FileUtils.rm_r(pages_path, force: true) + temp_path = "#{path}.#{SecureRandom.hex}" + + 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 diff --git a/app/services/update_pages_service.rb b/app/services/update_pages_service.rb index 818bb94a293..39f08b2a03d 100644 --- a/app/services/update_pages_service.rb +++ b/app/services/update_pages_service.rb @@ -10,6 +10,6 @@ class UpdatePagesService return unless data[:build_name] == 'pages' return unless data[:build_status] == 'success' - PagesWorker.perform_async(data[:build_id]) + PagesWorker.perform_async(:deploy, data[:build_id]) end end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 836e8d8ad9d..ff765a6c13c 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -7,7 +7,11 @@ class PagesWorker sidekiq_options queue: :pages, retry: false - def perform(build_id) + def perform(action, *arg) + send(action, *arg) + end + + def deploy(build_id) @build_id = build_id return unless valid? @@ -36,6 +40,11 @@ class PagesWorker return false 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 + private def create_status diff --git a/spec/workers/pages_worker_spec.rb b/spec/workers/pages_worker_spec.rb index 158a4b3ba8d..85592154598 100644 --- a/spec/workers/pages_worker_spec.rb +++ b/spec/workers/pages_worker_spec.rb @@ -18,41 +18,48 @@ describe PagesWorker do it 'succeeds' do expect(project.pages_url).to be_nil - expect(worker.perform(build.id)).to be_truthy + expect(worker.deploy(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 + expect(worker.deploy(build.id)).to_not be_truthy end it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) expect(project.pages_url).to be_nil - expect(worker.perform(build.id)).to be_truthy + expect(worker.deploy(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 to remove project pages when no pages is deployed' do + expect(PagesWorker).to_not receive(:perform_in) + expect(project.pages_url).to be_nil + project.destroy + end + it 'fails if no artifacts' do - expect(worker.perform(build.id)).to_not be_truthy + expect(worker.deploy(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 + expect(worker.deploy(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 + expect(worker.deploy(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 + expect(worker.deploy(build.id)).to_not be_truthy end end -- cgit v1.2.1 From 2c6a852529cc42507e45d1266489df491ef1b893 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:09:14 +0100 Subject: Refer to server_name with regex --- doc/pages/administration.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index b1ef2cebb14..9c24fee4f1a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -80,11 +80,16 @@ See the relevant documentation at .*)\.gitlabpages\.com$; ``` - You must be add `*` in front of your domain, this is required to catch all - subdomains of `gitlab.io`. + You must be extra careful to not remove the backslashes. If you are using + a subdomain, make sure to escape all dots (`.`) with a backslash (\). + For example `pages.gitlab.io` would be: + + ``` + server_name ~^(?.*)\.pages\.gitlab\.io$; + ``` 1. Restart Nginx and GitLab: -- cgit v1.2.1 From 3fbe9b3b49d871071e16dad215e9af0e4dd68e42 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 13:15:12 +0100 Subject: Add comment about the sequence when removing the pages --- app/models/project.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 7a5bf77c5a9..8a8aca44945 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1186,6 +1186,9 @@ class Project < ActiveRecord::Base 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}" if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) -- cgit v1.2.1 From 6c9ba469d9538c58434db492c0a955c20aba2ba1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 16:18:57 +0100 Subject: Bring back GitLab Pages SSL config --- lib/support/nginx/gitlab-pages | 3 ++ lib/support/nginx/gitlab-pages-ssl | 81 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 lib/support/nginx/gitlab-pages-ssl diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 0eeb0cd1917..33f573d0b5b 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -1,3 +1,6 @@ +## GitLab +## + ## Pages serving host server { listen 0.0.0.0:80; diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl new file mode 100644 index 00000000000..006610262f9 --- /dev/null +++ b/lib/support/nginx/gitlab-pages-ssl @@ -0,0 +1,81 @@ +## 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; + + ## 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 + root /home/git/gitlab/shared/pages/${group}; + + ## 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; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; + + ## [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; + + # 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; +} -- cgit v1.2.1 From 324fe12a125b1a766978556d7d8f2b8bb6c22e43 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 16:40:59 +0100 Subject: Fix pages path settings option. --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index e52171f0d64..860cafad325 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -267,7 +267,7 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' # 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['path'] = File.expand_path(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"), Rails.root) Settings.pages['host'] ||= "example.com" Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['port'] ||= Settings.pages.https ? 443 : 80 -- cgit v1.2.1 From 9c78a206ce2039cdba095c2631538f9e50c28f95 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 Dec 2015 18:08:41 +0200 Subject: Typo fixes, remove unnecessary information about pages path [ci skip] --- doc/pages/README.md | 15 +++------------ doc/pages/administration.md | 4 ++-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 552b3436d77..c83fdb63e53 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,7 +4,7 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ With GitLab Pages you can host for free 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. +deploy static pages for your individual projects, your user or your group. ## Enable the pages feature in your GitLab EE instance @@ -101,19 +101,10 @@ in the artifacts. ## Frequently Asked Questions -**Q:** Where are my generated pages stored? - -**A:** All content is located by default under `shared/pages/` in the root -directory of the GitLab installation. To be exact, all specific projects under -a namespace are stored in `shared/pages/${namespace}/${project}/public/` and -all user/group pages in `shared/pages/${namespace}/${namespace}/public/`. - ---- - **Q:** Can I download my generated pages? -**A:** Sure. All you need is to download the artifacts archive from the build - page. +**A:** Sure. All you need to do is download the artifacts archive from the + build page. --- diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 9c24fee4f1a..6c5436842fe 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -30,8 +30,8 @@ host that GitLab runs. For example, an entry would look like this: where `gitlab.io` is the domain under which GitLab Pages will be served and `1.2.3.4` is the IP address of your GitLab instance. -It is strongly advised to **not** use the GitLab domain to serve user pages. -For more information see the [security section](#security). +You should not use the GitLab domain to serve user pages. For more information +see the [security section](#security). ### Omnibus package installations -- cgit v1.2.1 From c66b15803a5674a5b97968ae9479b2bd293ca34f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 18 Dec 2015 17:08:02 +0100 Subject: Fix confusing implementation detail in nginx config about how gitlab-pages work [ci skip] --- lib/support/nginx/gitlab-pages | 4 ++-- lib/support/nginx/gitlab-pages-ssl | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 33f573d0b5b..ed4f7e4316a 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -14,8 +14,8 @@ server { 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 + # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ + # 2. Try to get / from shared/pages/${group}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 006610262f9..dcbbee4042a 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -53,8 +53,6 @@ server { # ssl_stapling on; # ssl_stapling_verify on; # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; - # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired - # resolver_timeout 5s; ## [Optional] Generate a stronger DHE parameter: ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 @@ -65,8 +63,8 @@ server { 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 + # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ + # 2. Try to get / from shared/pages/${group}/${host}/public/ location ~ ^/([^/]*)(/.*)?$ { try_files "/$1/public$2" "/$1/public$2/index.html" -- cgit v1.2.1 From 0bb480dcc738889e56397a7c05950ce8d73caedf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 14:24:54 +0200 Subject: Add note about shared runners [ci skip] --- doc/pages/administration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 6c5436842fe..529a1450fd3 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -14,6 +14,9 @@ GitLab EE instance. will be served 1. Pages use a separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs +1. Optionally but recommended, you can add some + [shared runners](../ci/runners/README.md) so that your users don't have to + bring their own. Both of these settings are described in detail in the sections below. -- cgit v1.2.1 From 8aba28e14dd7c66b17e0bc4a4230d0f5586155d0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 14:26:06 +0200 Subject: Clarify some things in Pages [ci skip] * Pages are enabled by default on each project * Add note about using the `only` parameter --- doc/pages/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index c83fdb63e53..ea975ec8149 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -17,13 +17,14 @@ The steps that are performed from the initialization of a project to the creation of the static content, can be summed up to: 1. Create project (its name could be specific according to the case) -1. Enable the GitLab Pages feature under the project's settings 1. Provide a specific job in `.gitlab-ci.yml` 1. GitLab Runner builds the project 1. GitLab CI uploads the artifacts 1. Nginx serves the content As a user, you should normally be concerned only with the first three items. +If [shared runners](../ci/runners/README.md) are enabled by your GitLab +administrator, you should be able to use them instead of bringing your own. In general there are four kinds of pages one might create. This is better explained with an example so let's make some assumptions. @@ -68,6 +69,13 @@ To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. Any static content must be placed under a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined +Be aware that Pages are by default branch/tag agnostic and their deployment +relies solely on what you specify in `gitlab-ci.yml`. If you don't limit the +`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to whatever branch or tag, the Pages will be +overwritten. In the examples below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch, which is advisable to do so. + The pages are created after the build completes successfully and the artifacts for the `pages` job are uploaded to GitLab. @@ -84,6 +92,8 @@ pages: artifacts: paths: - public + only: + - master ``` ## Example projects @@ -101,10 +111,9 @@ in the artifacts. ## Frequently Asked Questions -**Q:** Can I download my generated pages? +**Q: Can I download my generated pages?** -**A:** Sure. All you need to do is download the artifacts archive from the - build page. +Sure. All you need to do is download the artifacts archive from the build page. --- -- cgit v1.2.1 From a7fa7f269a3a6b7562880d2a780013cbab0a5110 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 22 Dec 2015 16:57:22 +0200 Subject: Add missing dot in .gitlab-ci.yml [ci skip] --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ea975ec8149..f6eb8ccb7a7 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -70,7 +70,7 @@ To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: 1. `artifacts` with a path to the `public/` directory must be defined Be aware that Pages are by default branch/tag agnostic and their deployment -relies solely on what you specify in `gitlab-ci.yml`. If you don't limit the +relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), whenever a new commit is pushed to whatever branch or tag, the Pages will be overwritten. In the examples below, we limit the Pages to be deployed whenever -- cgit v1.2.1 From 1d159ffbf807e3853f45faa1e7075b9a6546953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 22 Dec 2015 18:07:14 +0000 Subject: Fix URL to GitLab pages documentation --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f9c6809b903..f4c1db1b93d 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -147,7 +147,7 @@ - else %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/pages/README.html", target: :blank}. + GitLab by following the #{link_to "documentation on GitLab Pages", "http://doc.gitlab.com/ee/pages/README.html", target: :blank}. %p In the example below we define a special job named %code pages -- cgit v1.2.1 From 6e70870a2e80ea092f8528f727753184eb3265fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 15 Jan 2016 12:21:52 +0100 Subject: Move most of PagesWorker logic UpdatePagesService --- app/models/ci/build.rb | 2 +- app/services/pages_service.rb | 15 +++ app/services/projects/update_pages_service.rb | 132 ++++++++++++++++++++ app/services/update_pages_service.rb | 15 --- app/workers/pages_worker.rb | 133 +-------------------- spec/services/pages_service_spec.rb | 47 ++++++++ spec/services/projects/update_pages_worker_spec.rb | 70 +++++++++++ spec/services/update_pages_service_spec.rb | 47 -------- spec/workers/pages_worker_spec.rb | 65 ---------- 9 files changed, 267 insertions(+), 259 deletions(-) create mode 100644 app/services/pages_service.rb create mode 100644 app/services/projects/update_pages_service.rb delete mode 100644 app/services/update_pages_service.rb create mode 100644 spec/services/pages_service_spec.rb create mode 100644 spec/services/projects/update_pages_worker_spec.rb delete mode 100644 spec/services/update_pages_service_spec.rb delete mode 100644 spec/workers/pages_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 095a346f337..da8e66e5f6e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -457,7 +457,7 @@ module Ci build_data = Gitlab::DataBuilder::Build.build(self) project.execute_hooks(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks) - UpdatePagesService.new(build_data).execute + PagesService.new(build_data).execute project.running_or_pending_build_count(force: true) end diff --git a/app/services/pages_service.rb b/app/services/pages_service.rb new file mode 100644 index 00000000000..446eeb34d3b --- /dev/null +++ b/app/services/pages_service.rb @@ -0,0 +1,15 @@ +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 diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb new file mode 100644 index 00000000000..e1bb4c92e40 --- /dev/null +++ b/app/services/projects/update_pages_service.rb @@ -0,0 +1,132 @@ +module Projects + class UpdatePagesService < BaseService + BLOCK_SIZE = 32.kilobytes + MAX_SIZE = 1.terabyte + + 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.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.exists?(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, + commit: build.commit, + user: build.user, + ref: build.ref, + stage: 'deploy', + name: 'pages:deploy' + ) + end + + def extract_archive!(temp_path) + results = Open3.pipeline(%W(gunzip -c #{artifacts}), + %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), + %W(tar -x -C #{temp_path} public/), + err: '/dev/null') + raise 'pages failed to extract' unless results.compact.all?(&:success?) + 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 + max_size = current_application_settings.max_pages_size.megabytes + max_size ||= MAX_SIZE + blocks = 1 + max_size / BLOCK_SIZE + blocks + 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 diff --git a/app/services/update_pages_service.rb b/app/services/update_pages_service.rb deleted file mode 100644 index 39f08b2a03d..00000000000 --- a/app/services/update_pages_service.rb +++ /dev/null @@ -1,15 +0,0 @@ -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(:deploy, data[:build_id]) - end -end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index ff765a6c13c..8c99e8dbe76 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -1,9 +1,5 @@ class PagesWorker include Sidekiq::Worker - include Gitlab::CurrentSettings - - BLOCK_SIZE = 32.kilobytes - MAX_SIZE = 1.terabyte sidekiq_options queue: :pages, retry: false @@ -12,137 +8,12 @@ class PagesWorker end def deploy(build_id) - @build_id = build_id - return unless valid? - - # Create status notifying the deployment of pages - @status = create_status - @status.run! - - 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.exists?(archive_public_path) - raise 'pages are outdated' unless latest? - - deploy_page!(archive_public_path) - - @status.success - end - rescue => e - fail(e.message, !latest?) - return false + build = Ci::Build.find_by(id: build_id) + Projects::UpdatePagesService.new(build.project, build).execute 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 - - private - - def create_status - GenericCommitStatus.new( - project: project, - commit: build.commit, - user: build.user, - ref: build.ref, - stage: 'deploy', - name: 'pages:deploy' - ) - end - - def extract_archive!(temp_path) - results = Open3.pipeline(%W(gunzip -c #{artifacts}), - %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} public/), - err: '/dev/null') - raise 'pages failed to extract' unless results.compact.all?(&:success?) - 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 fail(message, allow_failure = true) - @status.allow_failure = allow_failure - @status.description = message - @status.drop - end - - def valid? - build && build.artifacts_file? - 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 - max_size = current_application_settings.max_pages_size.megabytes - max_size ||= MAX_SIZE - blocks = 1 + max_size / BLOCK_SIZE - blocks - 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 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 diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb new file mode 100644 index 00000000000..e6ad93358a0 --- /dev/null +++ b/spec/services/pages_service_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe PagesService, services: true do + let(:build) { create(:ci_build) } + let(:data) { Gitlab::BuildDataBuilder.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 '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 diff --git a/spec/services/projects/update_pages_worker_spec.rb b/spec/services/projects/update_pages_worker_spec.rb new file mode 100644 index 00000000000..0607c025b9e --- /dev/null +++ b/spec/services/projects/update_pages_worker_spec.rb @@ -0,0 +1,70 @@ +require "spec_helper" + +describe Projects::UpdatePagesService 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(: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') } + + subject { described_class.new(project, build) } + + 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(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + end + + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(execute).to_not eq(:success) + end + + it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) + expect(project.pages_url).to be_nil + expect(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + project.destroy + expect(Dir.exist?(project.public_pages_path)).to be_falsey + end + end + + it 'fails to remove project pages when no pages is deployed' do + expect(PagesWorker).to_not receive(:perform_in) + expect(project.pages_url).to be_nil + project.destroy + end + + it 'fails if no artifacts' do + expect(execute).to_not eq(:success) + end + + it 'fails for empty file fails' do + build.update_attributes(artifacts_file: empty_file) + expect(execute).to_not eq(:success) + end + + it 'fails for invalid archive' do + build.update_attributes(artifacts_file: invalid_file) + expect(execute).to_not eq(:success) + end + + it 'fails if sha on branch is not latest' do + commit.update_attributes(sha: 'old_sha') + build.update_attributes(artifacts_file: file) + expect(execute).to_not eq(:success) + end + + def execute + subject.execute[:status] + end +end diff --git a/spec/services/update_pages_service_spec.rb b/spec/services/update_pages_service_spec.rb deleted file mode 100644 index cf1ca15da44..00000000000 --- a/spec/services/update_pages_service_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -describe UpdatePagesService, services: true do - let(:build) { create(:ci_build) } - let(:data) { Gitlab::BuildDataBuilder.build(build) } - let(:service) { UpdatePagesService.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 '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 diff --git a/spec/workers/pages_worker_spec.rb b/spec/workers/pages_worker_spec.rb deleted file mode 100644 index 85592154598..00000000000 --- a/spec/workers/pages_worker_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -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.deploy(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.deploy(build.id)).to_not be_truthy - end - - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_url).to be_nil - expect(worker.deploy(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 to remove project pages when no pages is deployed' do - expect(PagesWorker).to_not receive(:perform_in) - expect(project.pages_url).to be_nil - project.destroy - end - - it 'fails if no artifacts' do - expect(worker.deploy(build.id)).to_not be_truthy - end - - it 'fails for empty file fails' do - build.update_attributes(artifacts_file: empty_file) - expect(worker.deploy(build.id)).to_not be_truthy - end - - it 'fails for invalid archive' do - build.update_attributes(artifacts_file: invalid_file) - expect(worker.deploy(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.deploy(build.id)).to_not be_truthy - end -end -- cgit v1.2.1 From c4c8ca04052aaf7d37c2335066381b536df68427 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 20 Jan 2016 21:49:26 +0100 Subject: Added support for zip archives in pages The ZIP archive size is calculated from artifacts metadata that should get uploaded for new artifacts --- app/services/projects/update_pages_service.rb | 41 ++++++++++-- spec/fixtures/pages.zip | Bin 0 -> 1851 bytes spec/fixtures/pages.zip.meta | Bin 0 -> 225 bytes spec/fixtures/pages_empty.zip | Bin 0 -> 160 bytes spec/fixtures/pages_empty.zip.meta | Bin 0 -> 116 bytes spec/services/projects/update_pages_worker_spec.rb | 74 ++++++++++++--------- 6 files changed, 78 insertions(+), 37 deletions(-) create mode 100644 spec/fixtures/pages.zip create mode 100644 spec/fixtures/pages.zip.meta create mode 100644 spec/fixtures/pages_empty.zip create mode 100644 spec/fixtures/pages_empty.zip.meta diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index e1bb4c92e40..ceabd29fd52 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -2,6 +2,7 @@ module Projects class UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte + SITE_PATH = 'public/' attr_reader :build @@ -60,13 +61,42 @@ module Projects 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} public/), + %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: #{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 @@ -91,10 +121,11 @@ module Projects def blocks # 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 - blocks + 1 + max_size / BLOCK_SIZE + end + + def max_size + current_application_settings.max_pages_size.megabytes || MAX_SIZE end def tmp_path diff --git a/spec/fixtures/pages.zip b/spec/fixtures/pages.zip new file mode 100644 index 00000000000..9558fcd4b94 Binary files /dev/null and b/spec/fixtures/pages.zip differ diff --git a/spec/fixtures/pages.zip.meta b/spec/fixtures/pages.zip.meta new file mode 100644 index 00000000000..1e6198a15f0 Binary files /dev/null and b/spec/fixtures/pages.zip.meta differ diff --git a/spec/fixtures/pages_empty.zip b/spec/fixtures/pages_empty.zip new file mode 100644 index 00000000000..db3f0334c12 Binary files /dev/null and b/spec/fixtures/pages_empty.zip differ diff --git a/spec/fixtures/pages_empty.zip.meta b/spec/fixtures/pages_empty.zip.meta new file mode 100644 index 00000000000..d0b93b3b9c0 Binary files /dev/null and b/spec/fixtures/pages_empty.zip.meta differ diff --git a/spec/services/projects/update_pages_worker_spec.rb b/spec/services/projects/update_pages_worker_spec.rb index 0607c025b9e..68e66866340 100644 --- a/spec/services/projects/update_pages_worker_spec.rb +++ b/spec/services/projects/update_pages_worker_spec.rb @@ -4,9 +4,7 @@ describe Projects::UpdatePagesService 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(: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') } + let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') } subject { described_class.new(project, build) } @@ -14,27 +12,50 @@ describe Projects::UpdatePagesService do project.remove_pages end - context 'for valid file' do - before { build.update_attributes(artifacts_file: file) } + %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.exists?(filename) + end - it 'succeeds' do - expect(project.pages_url).to be_nil - expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil - end + before do + build.update_attributes(artifacts_file: file) + build.update_attributes(artifacts_metadata: metadata) + end - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).to_not eq(:success) - end + it 'succeeds' do + expect(project.pages_url).to be_nil + expect(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + end + + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(execute).to_not eq(:success) + end - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_url).to be_nil - expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil - project.destroy - expect(Dir.exist?(project.public_pages_path)).to be_falsey + it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) + expect(project.pages_url).to be_nil + expect(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + project.destroy + expect(Dir.exist?(project.public_pages_path)).to be_falsey + end + + it 'fails if sha on branch is not latest' do + commit.update_attributes(sha: 'old_sha') + build.update_attributes(artifacts_file: file) + expect(execute).to_not eq(:success) + end + + it 'fails for empty file fails' do + build.update_attributes(artifacts_file: empty_file) + expect(execute).to_not eq(:success) + end end end @@ -48,21 +69,10 @@ describe Projects::UpdatePagesService do expect(execute).to_not eq(:success) end - it 'fails for empty file fails' do - build.update_attributes(artifacts_file: empty_file) - expect(execute).to_not eq(:success) - end - it 'fails for invalid archive' do build.update_attributes(artifacts_file: invalid_file) expect(execute).to_not eq(:success) end - - it 'fails if sha on branch is not latest' do - commit.update_attributes(sha: 'old_sha') - build.update_attributes(artifacts_file: file) - expect(execute).to_not eq(:success) - end def execute subject.execute[:status] -- cgit v1.2.1 From 5f7257c27dace1dcb9d3eb4732caf68f061a8d68 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 9 Feb 2016 18:06:55 +0100 Subject: Initial work on GitLab Pages update --- Gemfile | 3 + Gemfile.lock | 4 + app/controllers/projects/pages_controller.rb | 94 ++++++++++++++++++++++ app/controllers/projects_controller.rb | 10 --- app/helpers/projects_helper.rb | 8 ++ app/models/project.rb | 51 ++++++++++-- app/policies/project_policy.rb | 1 + .../projects/update_pages_configuration_service.rb | 53 ++++++++++++ app/validators/certificate_key_validator.rb | 24 ++++++ app/validators/certificate_validator.rb | 30 +++++++ app/views/layouts/nav/_project_settings.html.haml | 4 + app/views/projects/pages/_access.html.haml | 34 ++++++++ app/views/projects/pages/_destroy.haml | 10 +++ app/views/projects/pages/_disabled.html.haml | 4 + app/views/projects/pages/_form.html.haml | 35 ++++++++ .../projects/pages/_remove_certificate.html.haml | 16 ++++ .../projects/pages/_upload_certificate.html.haml | 32 ++++++++ app/views/projects/pages/_use.html.haml | 18 +++++ app/views/projects/pages/show.html.haml | 18 +++++ app/workers/pages_worker.rb | 6 +- config/initializers/1_settings.rb | 1 + config/routes/project.rb | 5 +- ...09125808_add_pages_custom_domain_to_projects.rb | 10 +++ 23 files changed, 451 insertions(+), 20 deletions(-) create mode 100644 app/controllers/projects/pages_controller.rb create mode 100644 app/services/projects/update_pages_configuration_service.rb create mode 100644 app/validators/certificate_key_validator.rb create mode 100644 app/validators/certificate_validator.rb create mode 100644 app/views/projects/pages/_access.html.haml create mode 100644 app/views/projects/pages/_destroy.haml create mode 100644 app/views/projects/pages/_disabled.html.haml create mode 100644 app/views/projects/pages/_form.html.haml create mode 100644 app/views/projects/pages/_remove_certificate.html.haml create mode 100644 app/views/projects/pages/_upload_certificate.html.haml create mode 100644 app/views/projects/pages/_use.html.haml create mode 100644 app/views/projects/pages/show.html.haml create mode 100644 db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb diff --git a/Gemfile b/Gemfile index dd7c93c5a75..bc1b13c7331 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,9 @@ gem 'rqrcode-rails3', '~> 0.1.7' gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' +# GitLab Pages +gem 'validates_hostname', '~> 1.0.0' + # Browser detection gem 'browser', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 3b207d19d1f..6263b02b041 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -799,6 +799,9 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) + validates_hostname (1.0.5) + activerecord (>= 3.0) + activesupport (>= 3.0) version_sorter (2.1.0) virtus (1.0.5) axiom-types (~> 0.1) @@ -1014,6 +1017,7 @@ DEPENDENCIES unf (~> 0.1.4) unicorn (~> 5.1.0) unicorn-worker-killer (~> 0.4.4) + validates_hostname (~> 1.0.0) version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb new file mode 100644 index 00000000000..ef0ed505142 --- /dev/null +++ b/app/controllers/projects/pages_controller.rb @@ -0,0 +1,94 @@ +class Projects::PagesController < Projects::ApplicationController + layout 'project_settings' + + before_action :authorize_update_pages!, except: [:show] + before_action :authorize_remove_pages!, only: :destroy + + helper_method :valid_certificate?, :valid_certificate_key? + helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? + helper_method :certificate, :certificate_key + + def show + end + + def update + if @project.update_attributes(pages_params) + redirect_to namespace_project_pages_path(@project.namespace, @project) + else + render 'show' + end + end + + def certificate + @project.remove_pages_certificate + end + + def destroy + @project.remove_pages + + respond_to do |format| + format.html { redirect_to project_path(@project) } + end + end + + private + + def pages_params + params.require(:project).permit( + :pages_custom_certificate, + :pages_custom_certificate_key, + :pages_custom_domain, + :pages_redirect_http, + ) + end + + def valid_certificate? + certificate.present? + end + + def valid_certificate_key? + certificate_key.present? + end + + def valid_key_for_certificiate? + return false unless certificate + return false unless certificate_key + + certificate.verify(certificate_key) + rescue OpenSSL::X509::CertificateError + false + end + + def valid_certificate_intermediates? + return false unless certificate + + store = OpenSSL::X509::Store.new + store.set_default_paths + + # This forces to load all intermediate certificates stored in `pages_custom_certificate` + Tempfile.open('project_certificate') do |f| + f.write(@project.pages_custom_certificate) + f.flush + store.add_file(f.path) + end + + store.verify(certificate) + rescue OpenSSL::X509::StoreError + false + end + + def certificate + return unless @project.pages_custom_certificate + + @certificate ||= OpenSSL::X509::Certificate.new(@project.pages_custom_certificate) + rescue OpenSSL::X509::CertificateError + nil + end + + def certificate_key + return unless @project.pages_custom_certificate_key + @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) + rescue OpenSSL::PKey::PKeyError + nil + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 123dc179e73..444ff837bb3 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,16 +151,6 @@ class ProjectsController < Projects::ApplicationController 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 ::Projects::HousekeepingService.new(@project).execute diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index eb98204285d..63aa182502d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -81,6 +81,14 @@ module ProjectsHelper "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?" end + def remove_pages_message(project) + "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" + end + + def remove_pages_certificate_message(project) + "You are going to remove a certificates for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" + end + def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/models/project.rb b/app/models/project.rb index 8a8aca44945..34618817fb6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -76,6 +76,8 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + alias_attribute :title, :name # Relations @@ -205,6 +207,11 @@ class Project < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true + validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true + validates :pages_custom_certificate, certificate: { intermediate: true } + validates :pages_custom_certificate_key, certificate_key: true + add_authentication_token_field :runners_token before_save :ensure_runners_token @@ -1164,16 +1171,27 @@ class Project < ActiveRecord::Base end def pages_url - if Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" - url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| - "#{prefix}#{namespace.path}." - end + return unless Dir.exist?(public_pages_path) + + host = "#{namespace.path}.#{Settings.pages.host}" + url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + "#{prefix}#{namespace.path}." + end + + # If the project path is the same as host, leave the short version + return url if host == path + + "#{url}/#{path}" + end - # If the project path is the same as host, leave the short version - return url if host == path + def pages_custom_url + return unless pages_custom_domain + return unless Dir.exist?(public_pages_path) - "#{url}/#{path}" + if Gitlab.config.pages.https + return "https://#{pages_custom_domain}" + else + return "http://#{pages_custom_domain}" end end @@ -1185,6 +1203,15 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end + def remove_pages_certificate + update( + pages_custom_certificate: nil, + pages_custom_certificate_key: nil + ) + + UpdatePagesConfigurationService.new(self).execute + end + def remove_pages # 1. We rename pages to temporary directory # 2. We wait 5 minutes, due to NFS caching @@ -1194,6 +1221,14 @@ class Project < ActiveRecord::Base if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path) PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path) end + + update( + pages_custom_certificate: nil, + pages_custom_certificate_key: nil, + pages_custom_domain: nil + ) + + UpdatePagesConfigurationService.new(self).execute end def wiki diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 63bc639688d..ca5b39a001f 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy can! :admin_pipeline can! :admin_environment can! :admin_deployment + can! :update_pages end def public_access! diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb new file mode 100644 index 00000000000..be4c2fbef8c --- /dev/null +++ b/app/services/projects/update_pages_configuration_service.rb @@ -0,0 +1,53 @@ +module Projects + class UpdatePagesConfigurationService < BaseService + attr_reader :project + + def initialize(project) + @project = project + end + + def execute + update_file(pages_cname_file, project.pages_custom_domain) + update_file(pages_certificate_file, project.pages_custom_certificate) + update_file(pages_certificate_file_key, project.pages_custom_certificate_key) + reload_daemon + success + rescue => e + error(e.message) + end + + private + + def reload_daemon + # GitLab Pages daemon constantly watches for modification time of `pages.path` + # It reloads configuration when `pages.path` is modified + File.touch(Settings.pages.path) + end + + def pages_path + @pages_path ||= project.pages_path + end + + def pages_cname_file + File.join(pages_path, 'CNAME') + end + + def pages_certificate_file + File.join(pages_path, 'domain.crt') + end + + def pages_certificate_key_file + File.join(pages_path, 'domain.key') + end + + def update_file(file, data) + if data + File.open(file, 'w') do |file| + file.write(data) + end + else + File.rm_r(file) + end + end + end +end diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb new file mode 100644 index 00000000000..3b5bd30db1a --- /dev/null +++ b/app/validators/certificate_key_validator.rb @@ -0,0 +1,24 @@ +# 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) + pkey = OpenSSL::PKey::RSA.new(value) + pkey.private? + rescue OpenSSL::PKey::PKeyError + false + end +end diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb new file mode 100644 index 00000000000..2cba5a435b7 --- /dev/null +++ b/app/validators/certificate_validator.rb @@ -0,0 +1,30 @@ +# UrlValidator +# +# Custom validator for private keys. +# +# class Project < ActiveRecord::Base +# validates :certificate_key, certificate_key: true +# end +# +class CertificateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + certificate = parse_certificate(value) + unless certificate + record.errors.add(attribute, "must be a valid PEM certificate") + end + + if options[:intermediates] + unless certificate + record.errors.add(attribute, "certificate verification failed: missing intermediate certificates") + end + end + end + + private + + def parse_certificate(value) + OpenSSL::X509::Certificate.new(value) + rescue OpenSSL::X509::CertificateError + nil + end +end diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index c6df66d2c3c..d6c158b6de3 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -34,3 +34,7 @@ = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do %span 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 diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml new file mode 100644 index 00000000000..d64f99fd22b --- /dev/null +++ b/app/views/projects/pages/_access.html.haml @@ -0,0 +1,34 @@ +- if @project.pages_url + .panel.panel-default + .panel-heading + Access pages + .panel-body + %p + %strong + Congratulations! Your pages are served at: + %p= link_to @project.pages_url, @project.pages_url + + - if Settings.pages.custom_domain && @project.pages_custom_url + %p= link_to @project.pages_custom_url, @project.pages_custom_url + + - if @project.pages_custom_certificate + - unless valid_certificate? + #error_explanation + .alert.alert-warning + Your certificate is invalid. + + - unless valid_certificate_key? + #error_explanation + .alert.alert-warning + Your private key is invalid. + + - unless valid_key_for_certificiate? + #error_explanation + .alert.alert-warning + Your private key can't be used with your certificate. + + - unless valid_certificate_intermediates? + #error_explanation + .alert.alert-warning + Your certificate doesn't have intermediates. + Your page may not work properly. diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml new file mode 100644 index 00000000000..61b995a5934 --- /dev/null +++ b/app/views/projects/pages/_destroy.haml @@ -0,0 +1,10 @@ +- if can?(current_user, :remove_pages, @project) && @project.pages_url + .panel.panel-default.panel.panel-danger + .panel-heading Remove pages + .errors-holder + .panel-body + = form_tag(namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + %p + Removing the pages will prevent from exposing them to outside world. + .form-actions + = button_to 'Remove pages', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_message(@project) } diff --git a/app/views/projects/pages/_disabled.html.haml b/app/views/projects/pages/_disabled.html.haml new file mode 100644 index 00000000000..cf9ef5b4d6f --- /dev/null +++ b/app/views/projects/pages/_disabled.html.haml @@ -0,0 +1,4 @@ +.panel.panel-default + .nothing-here-block + GitLab Pages is disabled. + Ask your system's administrator to enable it. diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml new file mode 100644 index 00000000000..a7b03d552db --- /dev/null +++ b/app/views/projects/pages/_form.html.haml @@ -0,0 +1,35 @@ +- if can?(current_user, :update_pages, @project) + .panel.panel-default + .panel-heading + Settings + .panel-body + = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| + - if @project.errors.any? + #error_explanation + .alert.alert-danger + - @project.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :pages_domain, class: 'control-label' do + Custom domain + .col-sm-10 + - if Settings.pages.custom_domain + = f.text_field :pages_custom_domain, required: false, autocomplete: 'off', class: 'form-control' + %span.help-inline Allows you to serve the pages under your domain + - else + .nothing-here-block + Support for custom domains and certificates is disabled. + Ask your system's administrator to enable it. + + - if Settings.pages.https + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :pages_redirect_http do + = f.check_box :pages_redirect_http + %span.descr Force HTTPS + .help-block Redirect the HTTP to HTTPS forcing to always use the secure connection + + .form-actions + = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/pages/_remove_certificate.html.haml b/app/views/projects/pages/_remove_certificate.html.haml new file mode 100644 index 00000000000..e8c0d03adfa --- /dev/null +++ b/app/views/projects/pages/_remove_certificate.html.haml @@ -0,0 +1,16 @@ +- if can?(current_user, :update_pages, @project) && @project.pages_custom_certificate + .panel.panel-default.panel.panel-danger + .panel-heading + Remove certificate + .errors-holder + .panel-body + = form_tag(certificates_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + %p + Removing the certificate will stop serving the page under HTTPS. + - if certificate + %p + %pre + = certificate.to_text + + .form-actions + = button_to 'Remove certificate', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_certificate_message(@project) } diff --git a/app/views/projects/pages/_upload_certificate.html.haml b/app/views/projects/pages/_upload_certificate.html.haml new file mode 100644 index 00000000000..30873fcf395 --- /dev/null +++ b/app/views/projects/pages/_upload_certificate.html.haml @@ -0,0 +1,32 @@ +- if can?(current_user, :update_pages, @project) && Settings.pages.https && Settings.pages.custom_domain + .panel.panel-default + .panel-heading + Certificate + .panel-body + %p + Allows you to upload your certificate which will be used to serve pages under your domain. + %br + + = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| + - if @project.errors.any? + #error_explanation + .alert.alert-danger + - @project.errors.full_messages.each do |msg| + %p= msg + + .form-group + = f.label :pages_custom_certificate, class: 'control-label' do + Certificate (PEM) + .col-sm-10 + = f.text_area :pages_custom_certificate, required: true, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + + .form-group + = f.label :pages_custom_certificate_key, class: 'control-label' do + Key (PEM) + .col-sm-10 + = f.text_area :pages_custom_certificate_key, required: true, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + + .form-actions + = f.submit 'Update certificate', class: "btn btn-save" diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml new file mode 100644 index 00000000000..5542bbe670b --- /dev/null +++ b/app/views/projects/pages/_use.html.haml @@ -0,0 +1,18 @@ +- unless @project.pages_url + .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}. + %p + In the example below we define a special job named + %code pages + which is using Jekyll to build a static site. The generated + HTML will be stored in the + %code public/ + directory which will then be archived and uploaded to GitLab. + The name of the directory should not be different than + %code public/ + in order for the pages to work. diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml new file mode 100644 index 00000000000..5f689800da8 --- /dev/null +++ b/app/views/projects/pages/show.html.haml @@ -0,0 +1,18 @@ +- page_title "Pages" +%h3.page_title Pages +%p.light + With GitLab Pages you can host for free 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 + +- if Settings.pages.enabled + = render 'access' + = render 'use' + - if @project.pages_url + = render 'form' + = render 'upload_certificate' + = render 'remove_certificate' + = render 'destroy' +- else + = render 'disabled' diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index 8c99e8dbe76..4eeb9666bb0 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -9,7 +9,11 @@ class PagesWorker def deploy(build_id) build = Ci::Build.find_by(id: build_id) - Projects::UpdatePagesService.new(build.project, build).execute + 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) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 860cafad325..239aa662d9f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -273,6 +273,7 @@ Settings.pages['https'] = false if Settings.pages['https'].nil? 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['custom_domain'] ||= false if Settings.pages['custom_domain'].nil? # # Git LFS diff --git a/config/routes/project.rb b/config/routes/project.rb index cd56f6281f5..956a2d3186f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,6 +39,10 @@ constraints(ProjectUrlConstrainer.new) do end end + resource :pages, only: [:show, :update, :destroy] do + delete :certificates + end + resources :compare, only: [:index, :create] do collection do get :diff_for_path @@ -329,7 +333,6 @@ constraints(ProjectUrlConstrainer.new) do post :archive post :unarchive post :housekeeping - post :remove_pages post :toggle_star post :preview_markdown post :export diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb new file mode 100644 index 00000000000..6472199fc4a --- /dev/null +++ b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb @@ -0,0 +1,10 @@ +class AddPagesCustomDomainToProjects < ActiveRecord::Migration + def change + add_column :projects, :pages_custom_certificate, :text + add_column :projects, :pages_custom_certificate_key, :text + add_column :projects, :pages_custom_certificate_key_iv, :string + add_column :projects, :pages_custom_certificate_key_salt, :string + add_column :projects, :pages_custom_domain, :string, unique: true + add_column :projects, :pages_redirect_http, :boolean, default: false, null: false + end +end -- cgit v1.2.1 From 930a7030b5a0080128b2fe3e2b9506717c54a6a5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 9 Feb 2016 19:04:39 +0100 Subject: Implement proper verification of certificate's public_key against the private_key --- app/controllers/projects/pages_controller.rb | 5 +-- app/models/project.rb | 8 ++--- app/validators/certificate_key_validator.rb | 1 + app/validators/certificate_validator.rb | 14 +++----- app/views/projects/edit.html.haml | 41 ---------------------- app/views/projects/pages/_use.html.haml | 10 ------ ...09125808_add_pages_custom_domain_to_projects.rb | 6 ++-- 7 files changed, 15 insertions(+), 70 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index ef0ed505142..359544472e9 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -54,8 +54,9 @@ class Projects::PagesController < Projects::ApplicationController return false unless certificate return false unless certificate_key - certificate.verify(certificate_key) - rescue OpenSSL::X509::CertificateError + # We compare the public key stored in certificate with public key from certificate key + certificate.public_key.to_pem == certificate_key.public_key.to_pem + rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError false end diff --git a/app/models/project.rb b/app/models/project.rb index 34618817fb6..f447c2bf293 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -76,8 +76,6 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch attr_accessor :old_path_with_namespace - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base - alias_attribute :title, :name # Relations @@ -209,14 +207,16 @@ class Project < ActiveRecord::Base validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true - validates :pages_custom_certificate, certificate: { intermediate: true } - validates :pages_custom_certificate_key, certificate_key: true + validates :pages_custom_certificate, certificate: true, allow_nil: true, allow_blank: true + validates :pages_custom_certificate_key, certificate_key: true, allow_nil: true, allow_blank: true add_authentication_token_field :runners_token before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + # Scopes default_scope { where(pending_delete: false) } diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb index 3b5bd30db1a..7039bd5a621 100644 --- a/app/validators/certificate_key_validator.rb +++ b/app/validators/certificate_key_validator.rb @@ -16,6 +16,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator private def valid_private_key_pem?(value) + return unless value pkey = OpenSSL::PKey::RSA.new(value) pkey.private? rescue OpenSSL::PKey::PKeyError diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb index 2cba5a435b7..2a04c76d4b9 100644 --- a/app/validators/certificate_validator.rb +++ b/app/validators/certificate_validator.rb @@ -3,26 +3,20 @@ # Custom validator for private keys. # # class Project < ActiveRecord::Base -# validates :certificate_key, certificate_key: true +# validates :certificate_key, certificate: true # end # class CertificateValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - certificate = parse_certificate(value) - unless certificate + unless valid_certificate_pem?(value) record.errors.add(attribute, "must be a valid PEM certificate") end - - if options[:intermediates] - unless certificate - record.errors.add(attribute, "certificate verification failed: missing intermediate certificates") - end - end end private - def parse_certificate(value) + def valid_certificate_pem?(value) + return unless value OpenSSL::X509::Certificate.new(value) rescue OpenSSL::X509::CertificateError nil diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f4c1db1b93d..dab40d37ead 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -134,47 +134,6 @@ = 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" - - 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 - 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}. - %p - In the example below we define a special job named - %code pages - which is using Jekyll to build a static site. The generated - HTML will be stored in the - %code public/ - directory which will then be archived and uploaded to GitLab. - The name of the directory should not be different than - %code public/ - in order for the pages to work. - %ul - %li - %pre - :plain - pages: - image: jekyll/jekyll - script: jekyll build -d public/ - 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 %hr .row.prepend-top-default diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index 5542bbe670b..ee38f45d44d 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -6,13 +6,3 @@ %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}. - %p - In the example below we define a special job named - %code pages - which is using Jekyll to build a static site. The generated - HTML will be stored in the - %code public/ - directory which will then be archived and uploaded to GitLab. - The name of the directory should not be different than - %code public/ - in order for the pages to work. diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb index 6472199fc4a..13b42d18a7a 100644 --- a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb +++ b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb @@ -1,9 +1,9 @@ class AddPagesCustomDomainToProjects < ActiveRecord::Migration def change add_column :projects, :pages_custom_certificate, :text - add_column :projects, :pages_custom_certificate_key, :text - add_column :projects, :pages_custom_certificate_key_iv, :string - add_column :projects, :pages_custom_certificate_key_salt, :string + add_column :projects, :encrypted_pages_custom_certificate_key, :text + add_column :projects, :encrypted_pages_custom_certificate_key_iv, :string + add_column :projects, :encrypted_pages_custom_certificate_key_salt, :string add_column :projects, :pages_custom_domain, :string, unique: true add_column :projects, :pages_redirect_http, :boolean, default: false, null: false end -- cgit v1.2.1 From f034f6b3ec5dc8b72f43c954ddb34bae037be254 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 11:37:27 +0100 Subject: WIP --- app/controllers/projects/pages_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 359544472e9..055f182ae00 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -89,7 +89,7 @@ class Projects::PagesController < Projects::ApplicationController def certificate_key return unless @project.pages_custom_certificate_key @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) - rescue OpenSSL::PKey::PKeyError + rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError nil end end -- cgit v1.2.1 From 6e99226cca41f36d92c4ccb2cd398d2256091adc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 12:07:46 +0100 Subject: Added PagesDomain --- app/models/pages_domain.rb | 29 +++++++++++++++++ app/models/project.rb | 38 ++-------------------- ...09125808_add_pages_custom_domain_to_projects.rb | 10 ------ db/migrate/20160210105555_create_pages_domain.rb | 14 ++++++++ 4 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 app/models/pages_domain.rb delete mode 100644 db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb create mode 100644 db/migrate/20160210105555_create_pages_domain.rb diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb new file mode 100644 index 00000000000..eebdf7501de --- /dev/null +++ b/app/models/pages_domain.rb @@ -0,0 +1,29 @@ +class PagesDomain < ActiveRecord::Base + belongs_to :project + + validates :domain, hostname: true + validates_uniqueness_of :domain, allow_nil: true, allow_blank: true + validates :certificate, certificate: true, allow_nil: true, allow_blank: true + validates :key, certificate_key: true, allow_nil: true, allow_blank: true + + attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + + after_create :update + after_save :update + after_destroy :update + + def url + return unless domain + return unless Dir.exist?(project.public_pages_path) + + if certificate + return "https://#{domain}" + else + return "http://#{domain}" + end + end + + def update + UpdatePagesConfigurationService.new(project).execute + end +end diff --git a/app/models/project.rb b/app/models/project.rb index f447c2bf293..dac52a0fc5e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -150,6 +150,7 @@ class Project < ActiveRecord::Base has_many :lfs_objects, through: :lfs_objects_projects has_many :project_group_links, dependent: :destroy has_many :invited_groups, through: :project_group_links, source: :group + has_many :pages_domains, dependent: :destroy has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy, as: :source @@ -205,18 +206,11 @@ class Project < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } - validates :pages_custom_domain, hostname: true, allow_blank: true, allow_nil: true - validates_uniqueness_of :pages_custom_domain, allow_nil: true, allow_blank: true - validates :pages_custom_certificate, certificate: true, allow_nil: true, allow_blank: true - validates :pages_custom_certificate_key, certificate_key: true, allow_nil: true, allow_blank: true - add_authentication_token_field :runners_token before_save :ensure_runners_token mount_uploader :avatar, AvatarUploader - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base - # Scopes default_scope { where(pending_delete: false) } @@ -1184,17 +1178,6 @@ class Project < ActiveRecord::Base "#{url}/#{path}" end - def pages_custom_url - return unless pages_custom_domain - return unless Dir.exist?(public_pages_path) - - if Gitlab.config.pages.https - return "https://#{pages_custom_domain}" - else - return "http://#{pages_custom_domain}" - end - end - def pages_path File.join(Settings.pages.path, path_with_namespace) end @@ -1203,32 +1186,15 @@ class Project < ActiveRecord::Base File.join(pages_path, 'public') end - def remove_pages_certificate - update( - pages_custom_certificate: nil, - pages_custom_certificate_key: nil - ) - - UpdatePagesConfigurationService.new(self).execute - 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}" + 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 - - update( - pages_custom_certificate: nil, - pages_custom_certificate_key: nil, - pages_custom_domain: nil - ) - - UpdatePagesConfigurationService.new(self).execute end def wiki diff --git a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb b/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb deleted file mode 100644 index 13b42d18a7a..00000000000 --- a/db/migrate/20160209125808_add_pages_custom_domain_to_projects.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddPagesCustomDomainToProjects < ActiveRecord::Migration - def change - add_column :projects, :pages_custom_certificate, :text - add_column :projects, :encrypted_pages_custom_certificate_key, :text - add_column :projects, :encrypted_pages_custom_certificate_key_iv, :string - add_column :projects, :encrypted_pages_custom_certificate_key_salt, :string - add_column :projects, :pages_custom_domain, :string, unique: true - add_column :projects, :pages_redirect_http, :boolean, default: false, null: false - end -end diff --git a/db/migrate/20160210105555_create_pages_domain.rb b/db/migrate/20160210105555_create_pages_domain.rb new file mode 100644 index 00000000000..9af206143bd --- /dev/null +++ b/db/migrate/20160210105555_create_pages_domain.rb @@ -0,0 +1,14 @@ +class CreatePagesDomain < ActiveRecord::Migration + 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 -- cgit v1.2.1 From 13b6bad17ec46eb78878f6972da1e7e34be86bb5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 15:06:31 +0100 Subject: Implement extra domains and save pages configuration --- app/controllers/projects/pages_controller.rb | 94 ++++++++-------------- app/helpers/projects_helper.rb | 4 - app/models/pages_domain.rb | 84 ++++++++++++++++++- .../projects/update_pages_configuration_service.rb | 32 +++++--- app/views/projects/pages/_access.html.haml | 29 +------ app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_form.html.haml | 64 +++++++-------- app/views/projects/pages/_list.html.haml | 16 ++++ app/views/projects/pages/_no_domains.html.haml | 6 ++ .../projects/pages/_remove_certificate.html.haml | 16 ---- .../projects/pages/_upload_certificate.html.haml | 32 -------- app/views/projects/pages/index.html.haml | 25 ++++++ app/views/projects/pages/new.html.haml | 6 ++ app/views/projects/pages/show.html.haml | 38 +++++---- config/gitlab.yml.example | 2 + config/initializers/1_settings.rb | 3 +- config/routes/project.rb | 4 +- 17 files changed, 249 insertions(+), 208 deletions(-) create mode 100644 app/views/projects/pages/_list.html.haml create mode 100644 app/views/projects/pages/_no_domains.html.haml delete mode 100644 app/views/projects/pages/_remove_certificate.html.haml delete mode 100644 app/views/projects/pages/_upload_certificate.html.haml create mode 100644 app/views/projects/pages/index.html.haml create mode 100644 app/views/projects/pages/new.html.haml diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 055f182ae00..82814afe196 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -2,25 +2,45 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' before_action :authorize_update_pages!, except: [:show] - before_action :authorize_remove_pages!, only: :destroy + before_action :authorize_remove_pages!, only: [:remove_pages] + before_action :label, only: [:destroy] + before_action :domain, only: [:show] helper_method :valid_certificate?, :valid_certificate_key? helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? helper_method :certificate, :certificate_key + def index + @domains = @project.pages_domains.order(:domain) + end + def show end - def update - if @project.update_attributes(pages_params) + 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 'show' + render 'new' end end - def certificate - @project.remove_pages_certificate + 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 def destroy @@ -33,63 +53,15 @@ class Projects::PagesController < Projects::ApplicationController private - def pages_params - params.require(:project).permit( - :pages_custom_certificate, - :pages_custom_certificate_key, - :pages_custom_domain, - :pages_redirect_http, + def pages_domain_params + params.require(:pages_domain).permit( + :certificate, + :key, + :domain ) end - def valid_certificate? - certificate.present? - end - - def valid_certificate_key? - certificate_key.present? - end - - def valid_key_for_certificiate? - return false unless certificate - return false unless certificate_key - - # We compare the public key stored in certificate with public key from certificate key - certificate.public_key.to_pem == certificate_key.public_key.to_pem - rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError - false - end - - def valid_certificate_intermediates? - return false unless certificate - - store = OpenSSL::X509::Store.new - store.set_default_paths - - # This forces to load all intermediate certificates stored in `pages_custom_certificate` - Tempfile.open('project_certificate') do |f| - f.write(@project.pages_custom_certificate) - f.flush - store.add_file(f.path) - end - - store.verify(certificate) - rescue OpenSSL::X509::StoreError - false - end - - def certificate - return unless @project.pages_custom_certificate - - @certificate ||= OpenSSL::X509::Certificate.new(@project.pages_custom_certificate) - rescue OpenSSL::X509::CertificateError - nil - end - - def certificate_key - return unless @project.pages_custom_certificate_key - @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key) - rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError - nil + def domain + @domain ||= @project.pages_domains.find_by(domain: params[:id].to_s) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 63aa182502d..054cc849839 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -85,10 +85,6 @@ module ProjectsHelper "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" end - def remove_pages_certificate_message(project) - "You are going to remove a certificates for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" - end - def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index eebdf7501de..810af4e832a 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -2,19 +2,25 @@ class PagesDomain < ActiveRecord::Base belongs_to :project validates :domain, hostname: true - validates_uniqueness_of :domain, allow_nil: true, allow_blank: 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 - attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base + 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, key: Gitlab::Application.secrets.db_key_base after_create :update after_save :update after_destroy :update + def to_param + domain + end + def url return unless domain - return unless Dir.exist?(project.public_pages_path) if certificate return "https://#{domain}" @@ -23,7 +29,77 @@ class PagesDomain < ActiveRecord::Base end end + def has_matching_key? + return unless x509 + return 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 + + 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 + return current < x509.not_before || x509.not_after < current + end + + def subject + return unless x509 + return x509.subject.to_s + end + + def fingerprint + return unless x509 + @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s + end + + private + + 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 + def update - UpdatePagesConfigurationService.new(project).execute + ::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 end diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index be4c2fbef8c..5afb0582ca6 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -7,9 +7,7 @@ module Projects end def execute - update_file(pages_cname_file, project.pages_custom_domain) - update_file(pages_certificate_file, project.pages_custom_certificate) - update_file(pages_certificate_file_key, project.pages_custom_certificate_key) + update_file(pages_config_file, pages_config) reload_daemon success rescue => e @@ -18,6 +16,22 @@ module Projects 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 @@ -28,16 +42,8 @@ module Projects @pages_path ||= project.pages_path end - def pages_cname_file - File.join(pages_path, 'CNAME') - end - - def pages_certificate_file - File.join(pages_path, 'domain.crt') - end - - def pages_certificate_key_file - File.join(pages_path, 'domain.key') + def pages_config_file + File.join(pages_path, 'config.jso') end def update_file(file, data) diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml index d64f99fd22b..9740877b214 100644 --- a/app/views/projects/pages/_access.html.haml +++ b/app/views/projects/pages/_access.html.haml @@ -5,30 +5,9 @@ .panel-body %p %strong - Congratulations! Your pages are served at: - %p= link_to @project.pages_url, @project.pages_url - - - if Settings.pages.custom_domain && @project.pages_custom_url - %p= link_to @project.pages_custom_url, @project.pages_custom_url - - - if @project.pages_custom_certificate - - unless valid_certificate? - #error_explanation - .alert.alert-warning - Your certificate is invalid. + Congratulations! Your pages are served under: - - unless valid_certificate_key? - #error_explanation - .alert.alert-warning - Your private key is invalid. - - - unless valid_key_for_certificiate? - #error_explanation - .alert.alert-warning - Your private key can't be used with your certificate. + %p= link_to @project.pages_url, @project.pages_url - - unless valid_certificate_intermediates? - #error_explanation - .alert.alert-warning - Your certificate doesn't have intermediates. - Your page may not work properly. + - @project.pages_domains.each do |domain| + %p= link_to domain.url, domain.url diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 61b995a5934..dd493a6d312 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -3,7 +3,7 @@ .panel-heading Remove pages .errors-holder .panel-body - = form_tag(namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do + = form_tag(remove_pages_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do %p Removing the pages will prevent from exposing them to outside world. .form-actions diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml index a7b03d552db..c69b76c6697 100644 --- a/app/views/projects/pages/_form.html.haml +++ b/app/views/projects/pages/_form.html.haml @@ -1,35 +1,35 @@ -- if can?(current_user, :update_pages, @project) - .panel.panel-default - .panel-heading - Settings - .panel-body - = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| - - if @project.errors.any? - #error_explanation - .alert.alert-danger - - @project.errors.full_messages.each do |msg| - %p= msg += form_for [@domain], url: namespace_project_pages_path(@project.namespace, @project), 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 :pages_domain, class: 'control-label' do - Custom domain - .col-sm-10 - - if Settings.pages.custom_domain - = f.text_field :pages_custom_domain, required: false, autocomplete: 'off', class: 'form-control' - %span.help-inline Allows you to serve the pages under your domain - - else - .nothing-here-block - Support for custom domains and certificates is disabled. - Ask your system's administrator to enable it. + .form-group + = f.label :domain, class: 'control-label' do + Domain + .col-sm-10 + = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' + %span.help-inline * required - - if Settings.pages.https - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :pages_redirect_http do - = f.check_box :pages_redirect_http - %span.descr Force HTTPS - .help-block Redirect the HTTP to HTTPS forcing to always use the secure connection + - if Settings.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', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates - .form-actions - = f.submit 'Save changes', class: "btn btn-save" + .form-group + = f.label :key, class: 'control-label' do + Key (PEM) + .col-sm-10 + = f.text_area :key, rows: 5, class: 'form-control', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + - 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" diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml new file mode 100644 index 00000000000..7dfeb0e6e12 --- /dev/null +++ b/app/views/projects/pages/_list.html.haml @@ -0,0 +1,16 @@ +.panel.panel-default + .panel-heading + Domains (#{@domains.count}) + %ul.well-list + - @domains.each do |domain| + %li + .pull-right + = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_page_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 diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml new file mode 100644 index 00000000000..5a18740346a --- /dev/null +++ b/app/views/projects/pages/_no_domains.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading + Domains + .nothing-here-block + Support for domains and certificates is disabled. + Ask your system's administrator to enable it. diff --git a/app/views/projects/pages/_remove_certificate.html.haml b/app/views/projects/pages/_remove_certificate.html.haml deleted file mode 100644 index e8c0d03adfa..00000000000 --- a/app/views/projects/pages/_remove_certificate.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -- if can?(current_user, :update_pages, @project) && @project.pages_custom_certificate - .panel.panel-default.panel.panel-danger - .panel-heading - Remove certificate - .errors-holder - .panel-body - = form_tag(certificates_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do - %p - Removing the certificate will stop serving the page under HTTPS. - - if certificate - %p - %pre - = certificate.to_text - - .form-actions - = button_to 'Remove certificate', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_certificate_message(@project) } diff --git a/app/views/projects/pages/_upload_certificate.html.haml b/app/views/projects/pages/_upload_certificate.html.haml deleted file mode 100644 index 30873fcf395..00000000000 --- a/app/views/projects/pages/_upload_certificate.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -- if can?(current_user, :update_pages, @project) && Settings.pages.https && Settings.pages.custom_domain - .panel.panel-default - .panel-heading - Certificate - .panel-body - %p - Allows you to upload your certificate which will be used to serve pages under your domain. - %br - - = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f| - - if @project.errors.any? - #error_explanation - .alert.alert-danger - - @project.errors.full_messages.each do |msg| - %p= msg - - .form-group - = f.label :pages_custom_certificate, class: 'control-label' do - Certificate (PEM) - .col-sm-10 - = f.text_area :pages_custom_certificate, required: true, rows: 5, class: 'form-control', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates - - .form-group - = f.label :pages_custom_certificate_key, class: 'control-label' do - Key (PEM) - .col-sm-10 - = f.text_area :pages_custom_certificate_key, required: true, rows: 5, class: 'form-control', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates - - .form-actions - = f.submit 'Update certificate', class: "btn btn-save" diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml new file mode 100644 index 00000000000..fea34c113ba --- /dev/null +++ b/app/views/projects/pages/index.html.haml @@ -0,0 +1,25 @@ +- page_title "Pages" +%h3.page_title + Pages + + = link_to new_namespace_project_page_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 for free 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 Settings.pages.enabled + = render 'access' + = render 'use' + - if Settings.pages.external_http || Settings.pages.external_https + = render 'list' + - else + = render 'no_domains' + = render 'destroy' +- else + = render 'disabled' diff --git a/app/views/projects/pages/new.html.haml b/app/views/projects/pages/new.html.haml new file mode 100644 index 00000000000..2609df62aac --- /dev/null +++ b/app/views/projects/pages/new.html.haml @@ -0,0 +1,6 @@ +- page_title 'Pages' +%h3.page_title + New Pages Domain +%hr.clearfix +%div + = render 'form' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 5f689800da8..98c4e890968 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,18 +1,22 @@ -- page_title "Pages" -%h3.page_title Pages -%p.light - With GitLab Pages you can host for free 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 +- page_title "#{@domain.domain}", "Pages Domain" -- if Settings.pages.enabled - = render 'access' - = render 'use' - - if @project.pages_url - = render 'form' - = render 'upload_certificate' - = render 'remove_certificate' - = render 'destroy' -- else - = render 'disabled' +%h3.page-title + #{@domain.domain} + +.table-holder + %table.table + %tr + %td + Domain + %td + = link_to @domain.domain, @domain.url + %tr + %td + Certificate + %td + - if @domain.certificate + %pre + = @domain.certificate.to_text + - else + .light + missing diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c6f06d43d07..f2bde602795 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -165,6 +165,8 @@ production: &base 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 notifies the GitLab pages do support Custom Domains + # external_https: "1.1.1.1:443" # if defined notifies the GitLab pages do support Custom Domains with Certificates ## Mattermost ## For enabling Add to Mattermost button diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 239aa662d9f..0015ddf902d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -273,7 +273,8 @@ Settings.pages['https'] = false if Settings.pages['https'].nil? 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['custom_domain'] ||= false if Settings.pages['custom_domain'].nil? +Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil? +Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil? # # Git LFS diff --git a/config/routes/project.rb b/config/routes/project.rb index 956a2d3186f..ac1e3fce16a 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,8 +39,8 @@ constraints(ProjectUrlConstrainer.new) do end end - resource :pages, only: [:show, :update, :destroy] do - delete :certificates + resources :pages, except: [:edit, :update] do + delete :remove_pages end resources :compare, only: [:index, :create] do -- cgit v1.2.1 From e5e2e7b70315cbcee12db66ebc73dbd0ef4a14ae Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 16:21:39 +0100 Subject: Fix the remove_pages --- app/controllers/projects/pages_controller.rb | 5 +++++ app/services/projects/update_pages_configuration_service.rb | 4 ++-- app/views/projects/pages/index.html.haml | 7 ++++--- config/routes/project.rb | 4 +++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 82814afe196..0c7f2bd5784 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -51,6 +51,11 @@ class Projects::PagesController < Projects::ApplicationController end end + def remove_pages + project.remove_pages + project.pages_domains.destroy_all + end + private def pages_domain_params diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 5afb0582ca6..53e9d9e2757 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -43,7 +43,7 @@ module Projects end def pages_config_file - File.join(pages_path, 'config.jso') + File.join(pages_path, 'config.json') end def update_file(file, data) @@ -52,7 +52,7 @@ module Projects file.write(data) end else - File.rm_r(file) + File.rm(file, force: true) end end end diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml index fea34c113ba..284f362d535 100644 --- a/app/views/projects/pages/index.html.haml +++ b/app/views/projects/pages/index.html.haml @@ -2,9 +2,10 @@ %h3.page_title Pages - = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do - %i.fa.fa-plus - New Domain + - if Settings.pages.external_http || Settings.pages.external_https + = link_to new_namespace_project_page_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 for free your static websites on GitLab. diff --git a/config/routes/project.rb b/config/routes/project.rb index ac1e3fce16a..7a41cb81bfa 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -40,7 +40,9 @@ constraints(ProjectUrlConstrainer.new) do end resources :pages, except: [:edit, :update] do - delete :remove_pages + collection do + delete :remove_pages + end end resources :compare, only: [:index, :create] do -- cgit v1.2.1 From 0552c0b6f185433ad0a7caac321f0a6d445a0b63 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 10 Feb 2016 16:45:59 +0100 Subject: Fix views --- app/models/pages_domain.rb | 4 ++-- app/views/projects/pages/_list.html.haml | 33 ++++++++++++++++---------------- app/views/projects/pages/show.html.haml | 6 +++--- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 810af4e832a..985329bb856 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -71,8 +71,6 @@ class PagesDomain < ActiveRecord::Base @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s end - private - def x509 return unless certificate @x509 ||= OpenSSL::X509::Certificate.new(certificate) @@ -87,6 +85,8 @@ class PagesDomain < ActiveRecord::Base nil end + private + def update ::Projects::UpdatePagesConfigurationService.new(project).execute end diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 7dfeb0e6e12..e88a001d636 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,16 +1,17 @@ -.panel.panel-default - .panel-heading - Domains (#{@domains.count}) - %ul.well-list - - @domains.each do |domain| - %li - .pull-right - = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_page_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 @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_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_page_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 diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 98c4e890968..52493b1959b 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,7 +1,7 @@ - page_title "#{@domain.domain}", "Pages Domain" %h3.page-title - #{@domain.domain} + Pages Domain .table-holder %table.table @@ -14,9 +14,9 @@ %td Certificate %td - - if @domain.certificate + - if @domain.x509 %pre - = @domain.certificate.to_text + = @domain.x509.to_text - else .light missing -- cgit v1.2.1 From d3b828487647f106a8947864e18ac1ad7bd9d6f4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 12 Feb 2016 16:05:17 +0100 Subject: Pages domain model specs --- app/models/pages_domain.rb | 50 ++++--- .../projects/update_pages_configuration_service.rb | 22 ++- app/services/projects/update_pages_service.rb | 3 +- app/views/projects/pages/show.html.haml | 4 +- db/schema.rb | 11 ++ spec/factories/pages_domains.rb | 79 +++++++++++ spec/models/pages_domain_spec.rb | 152 +++++++++++++++++++++ spec/models/project_spec.rb | 1 + .../services/projects/update_pages_service_spec.rb | 80 +++++++++++ spec/services/projects/update_pages_worker_spec.rb | 80 ----------- 10 files changed, 373 insertions(+), 109 deletions(-) create mode 100644 spec/factories/pages_domains.rb create mode 100644 spec/models/pages_domain_spec.rb create mode 100644 spec/services/projects/update_pages_service_spec.rb delete mode 100644 spec/services/projects/update_pages_worker_spec.rb diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 985329bb856..b594957493a 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -6,7 +6,8 @@ class PagesDomain < ActiveRecord::Base validates :certificate, certificate: true, allow_nil: true, allow_blank: true validates :key, certificate_key: true, allow_nil: true, allow_blank: true - validate :validate_matching_key, if: ->(domain) { domain.certificate.present? && domain.key.present? } + 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, key: Gitlab::Application.secrets.db_key_base @@ -30,8 +31,8 @@ class PagesDomain < ActiveRecord::Base end def has_matching_key? - return unless x509 - return unless pkey + 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) @@ -40,6 +41,9 @@ class PagesDomain < ActiveRecord::Base 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 @@ -66,23 +70,8 @@ class PagesDomain < ActiveRecord::Base return x509.subject.to_s end - def fingerprint - return unless x509 - @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s - 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 + def certificate_text + @certificate_text ||= x509.try(:to_text) end private @@ -102,4 +91,25 @@ class PagesDomain < ActiveRecord::Base 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 diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 53e9d9e2757..b5324587d0e 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -35,7 +35,7 @@ module Projects def reload_daemon # GitLab Pages daemon constantly watches for modification time of `pages.path` # It reloads configuration when `pages.path` is modified - File.touch(Settings.pages.path) + update_file(pages_update_file, SecureRandom.hex(64)) end def pages_path @@ -46,14 +46,24 @@ module Projects File.join(pages_path, 'config.json') end + def pages_update_file + File.join(Settings.pages.path, '.update') + end + def update_file(file, data) - if data - File.open(file, 'w') do |file| - file.write(data) - end - else + unless data File.rm(file, force: true) + return + end + + temp_file = "#{file}.#{SecureRandom.hex(16)}" + File.open(temp_file, 'w') do |file| + file.write(data) end + File.mv(temp_file, file, force: true) + ensure + # In case if the updating fails + File.rm(temp_file, force: true) end end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index ceabd29fd52..a9979bf1e96 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,5 +1,6 @@ module Projects - class UpdatePagesService < BaseService + class + UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 52493b1959b..8b7010b75b2 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -14,9 +14,9 @@ %td Certificate %td - - if @domain.x509 + - if @domain.certificate_text %pre - = @domain.x509.to_text + = @domain.certificate_text - else .light missing diff --git a/db/schema.rb b/db/schema.rb index 15f378b28ff..dc3d8c22e8d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -855,6 +855,17 @@ ActiveRecord::Schema.define(version: 20170130204620) 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", ["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| t.integer "user_id", null: false t.string "token", null: false diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb new file mode 100644 index 00000000000..4608867087c --- /dev/null +++ b/spec/factories/pages_domains.rb @@ -0,0 +1,79 @@ +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_certificate_chain do + # This certificate is signed with different key + 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_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 diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb new file mode 100644 index 00000000000..929b2a26549 --- /dev/null +++ b/spec/models/pages_domain_spec.rb @@ -0,0 +1,152 @@ +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 'no domain' do + let(:domain) { nil } + + it { is_expected.to_not be_valid } + end + + context 'invalid domain' do + let(:domain) { '0123123' } + + it { is_expected.to_not 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.to_not 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.to_not be_valid } + end + + context 'when only key is specified' do + let(:domain) { build(:pages_domain, :with_key) } + + it { is_expected.to_not 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_certificate_chain, :with_key) } + + it { is_expected.to_not 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_certificate_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 certificate chain without the root' do + let(:domain) { build(:pages_domain, :with_certificate_chain) } + + it { is_expected.to be_falsey } + 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.to_not be_empty } + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48b085781e7..5fde9194e93 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -60,6 +60,7 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } 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(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb new file mode 100644 index 00000000000..68e66866340 --- /dev/null +++ b/spec/services/projects/update_pages_service_spec.rb @@ -0,0 +1,80 @@ +require "spec_helper" + +describe Projects::UpdatePagesService 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(: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.exists?(filename) + end + + before do + build.update_attributes(artifacts_file: file) + build.update_attributes(artifacts_metadata: metadata) + end + + it 'succeeds' do + expect(project.pages_url).to be_nil + expect(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + end + + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(execute).to_not eq(:success) + end + + it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) + expect(project.pages_url).to be_nil + expect(execute).to eq(:success) + expect(project.pages_url).to_not be_nil + project.destroy + expect(Dir.exist?(project.public_pages_path)).to be_falsey + end + + it 'fails if sha on branch is not latest' do + commit.update_attributes(sha: 'old_sha') + build.update_attributes(artifacts_file: file) + expect(execute).to_not eq(:success) + end + + it 'fails for empty file fails' do + build.update_attributes(artifacts_file: empty_file) + expect(execute).to_not eq(:success) + end + end + end + + it 'fails to remove project pages when no pages is deployed' do + expect(PagesWorker).to_not receive(:perform_in) + expect(project.pages_url).to be_nil + project.destroy + end + + it 'fails if no artifacts' do + expect(execute).to_not eq(:success) + end + + it 'fails for invalid archive' do + build.update_attributes(artifacts_file: invalid_file) + expect(execute).to_not eq(:success) + end + + def execute + subject.execute[:status] + end +end diff --git a/spec/services/projects/update_pages_worker_spec.rb b/spec/services/projects/update_pages_worker_spec.rb deleted file mode 100644 index 68e66866340..00000000000 --- a/spec/services/projects/update_pages_worker_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -require "spec_helper" - -describe Projects::UpdatePagesService 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(: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.exists?(filename) - end - - before do - build.update_attributes(artifacts_file: file) - build.update_attributes(artifacts_metadata: metadata) - end - - it 'succeeds' do - expect(project.pages_url).to be_nil - expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil - end - - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).to_not eq(:success) - end - - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_url).to be_nil - expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil - project.destroy - expect(Dir.exist?(project.public_pages_path)).to be_falsey - end - - it 'fails if sha on branch is not latest' do - commit.update_attributes(sha: 'old_sha') - build.update_attributes(artifacts_file: file) - expect(execute).to_not eq(:success) - end - - it 'fails for empty file fails' do - build.update_attributes(artifacts_file: empty_file) - expect(execute).to_not eq(:success) - end - end - end - - it 'fails to remove project pages when no pages is deployed' do - expect(PagesWorker).to_not receive(:perform_in) - expect(project.pages_url).to be_nil - project.destroy - end - - it 'fails if no artifacts' do - expect(execute).to_not eq(:success) - end - - it 'fails for invalid archive' do - build.update_attributes(artifacts_file: invalid_file) - expect(execute).to_not eq(:success) - end - - def execute - subject.execute[:status] - end -end -- cgit v1.2.1 From b7fd7daee4610674a2301c618fd60b8997f2cf8a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 19:58:45 +0100 Subject: Fix rubocop complains --- app/controllers/projects/pages_controller.rb | 21 ++++++++++----------- app/models/pages_domain.rb | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 0c7f2bd5784..2268d2d8aa2 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -43,26 +43,25 @@ class Projects::PagesController < Projects::ApplicationController end end - def destroy - @project.remove_pages - - respond_to do |format| - format.html { redirect_to project_path(@project) } - end - end - def remove_pages 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 private def pages_domain_params params.require(:pages_domain).permit( - :certificate, - :key, - :domain + :certificate, + :key, + :domain ) end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index b594957493a..83fdc1c630d 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -62,12 +62,12 @@ class PagesDomain < ActiveRecord::Base def expired? return false unless x509 current = Time.new - return current < x509.not_before || x509.not_after < current + current < x509.not_before || x509.not_after < current end def subject return unless x509 - return x509.subject.to_s + x509.subject.to_s end def certificate_text -- cgit v1.2.1 From db35f3dc573fe5d07eb03de7690d98eef98784d3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 20:28:02 +0100 Subject: Add tests for Active Tab --- app/views/projects/pages/_disabled.html.haml | 2 +- features/project/active_tab.feature | 7 +++++++ features/steps/project/active_tab.rb | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/views/projects/pages/_disabled.html.haml b/app/views/projects/pages/_disabled.html.haml index cf9ef5b4d6f..ad51fbc6cab 100644 --- a/app/views/projects/pages/_disabled.html.haml +++ b/app/views/projects/pages/_disabled.html.haml @@ -1,4 +1,4 @@ .panel.panel-default .nothing-here-block - GitLab Pages is disabled. + GitLab Pages are disabled. Ask your system's administrator to enable it. diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index d033e6b167b..5c14c5db665 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -53,6 +53,13 @@ Feature: Project Active Tab And no other sub navs should be active 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 Given I visit my project's members page Then the active sub nav should be Members diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 9f701840f1d..e842d7bec2b 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -35,6 +35,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps click_link('Deploy Keys') end + step 'I click the "Pages" tab' do + click_link('Pages') + end + step 'the active sub nav should be Members' do ensure_active_sub_nav('Members') end @@ -47,6 +51,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ensure_active_sub_nav('Deploy Keys') end + step 'the active sub nav should be Pages' do + ensure_active_sub_nav('Pages') + end + # Sub Tabs: Commits step 'I click the "Compare" tab' do -- cgit v1.2.1 From 84edc9a22f5d858cb02f32d22b66c92fb939378a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 21:06:24 +0100 Subject: Added spinach tests --- .../projects/pages_domains_controller.rb | 0 app/helpers/projects_helper.rb | 4 - app/views/projects/pages/_destroy.haml | 9 +- app/views/projects/pages/_form.html.haml | 2 +- app/views/projects/pages/index.html.haml | 6 +- features/project/pages.feature | 73 +++++++++++ features/steps/project/pages.rb | 139 +++++++++++++++++++++ 7 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 app/controllers/projects/pages_domains_controller.rb create mode 100644 features/project/pages.feature create mode 100644 features/steps/project/pages.rb diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 054cc849839..eb98204285d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -81,10 +81,6 @@ module ProjectsHelper "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?" end - def remove_pages_message(project) - "You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?" - end - def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index dd493a6d312..c560aca5725 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -3,8 +3,7 @@ .panel-heading Remove pages .errors-holder .panel-body - = form_tag(remove_pages_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do - %p - Removing the pages will prevent from exposing them to outside world. - .form-actions - = button_to 'Remove pages', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_message(@project) } + %p + Removing the pages will prevent from exposing them to outside world. + .form-actions + = link_to 'Remove', remove_pages_namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml index c69b76c6697..fd411462330 100644 --- a/app/views/projects/pages/_form.html.haml +++ b/app/views/projects/pages/_form.html.haml @@ -12,7 +12,7 @@ = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required - - if Settings.pages.external_https + - if Gitlab.config.pages.external_https .form-group = f.label :certificate, class: 'control-label' do Certificate (PEM) diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml index 284f362d535..1a5dbb79830 100644 --- a/app/views/projects/pages/index.html.haml +++ b/app/views/projects/pages/index.html.haml @@ -2,7 +2,7 @@ %h3.page_title Pages - - if Settings.pages.external_http || Settings.pages.external_https + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do %i.fa.fa-plus New Domain @@ -14,10 +14,10 @@ %hr.clearfix -- if Settings.pages.enabled +- if Gitlab.config.pages.enabled = render 'access' = render 'use' - - if Settings.pages.external_http || Settings.pages.external_https + - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https = render 'list' - else = render 'no_domains' diff --git a/features/project/pages.feature b/features/project/pages.feature new file mode 100644 index 00000000000..392f2d29c3c --- /dev/null +++ b/features/project/pages.feature @@ -0,0 +1,73 @@ +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 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 diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb new file mode 100644 index 00000000000..d484ae90bdc --- /dev/null +++ b/features/steps/project/pages.rb @@ -0,0 +1,139 @@ +class Spinach::Features::ProjectPages < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'pages are enabled' do + Gitlab.config.pages.stub(:enabled).and_return(true) + Gitlab.config.pages.stub(:host).and_return('example.com') + Gitlab.config.pages.stub(:port).and_return(80) + Gitlab.config.pages.stub(:https).and_return(false) + end + + step 'pages are disabled' do + Gitlab.config.pages.stub(: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 + commit = @project.ensure_ci_commit(@project.commit('HEAD').sha) + build = build(:ci_build, + project: @project, + commit: commit, + 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 + Gitlab.config.pages.stub(:external_http).and_return(nil) + Gitlab.config.pages.stub(:external_https).and_return(nil) + end + + step 'pages are exposed on external HTTP address' do + Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') + Gitlab.config.pages.stub(:external_https).and_return(nil) + end + + step 'pages are exposed on external HTTPS address' do + Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') + Gitlab.config.pages.stub(: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_page_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_url).to be_nil + end +end -- cgit v1.2.1 From 7f12cb0eed06ad3f83126a3a8038e7fa658f4eac Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 14 Feb 2016 21:22:44 +0100 Subject: Split PagesController into PagesController and PagesDomainsController 1. PagesController is used to show all domains and general overview of Pages 2. PagesDomainsController is used to manage pages domains --- app/controllers/projects/pages_controller.rb | 54 +--------------------- .../projects/pages_domains_controller.rb | 49 ++++++++++++++++++++ app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_form.html.haml | 35 -------------- app/views/projects/pages/_list.html.haml | 4 +- app/views/projects/pages/index.html.haml | 26 ----------- app/views/projects/pages/new.html.haml | 6 --- app/views/projects/pages/show.html.haml | 44 ++++++++++-------- app/views/projects/pages_domains/_form.html.haml | 35 ++++++++++++++ app/views/projects/pages_domains/new.html.haml | 6 +++ app/views/projects/pages_domains/show.html.haml | 22 +++++++++ config/routes/project.rb | 6 +-- features/steps/project/pages.rb | 2 +- 13 files changed, 144 insertions(+), 147 deletions(-) delete mode 100644 app/views/projects/pages/_form.html.haml delete mode 100644 app/views/projects/pages/index.html.haml delete mode 100644 app/views/projects/pages/new.html.haml create mode 100644 app/views/projects/pages_domains/_form.html.haml create mode 100644 app/views/projects/pages_domains/new.html.haml create mode 100644 app/views/projects/pages_domains/show.html.haml diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 2268d2d8aa2..b73f998392d 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -1,49 +1,13 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' - before_action :authorize_update_pages!, except: [:show] - before_action :authorize_remove_pages!, only: [:remove_pages] - before_action :label, only: [:destroy] - before_action :domain, only: [:show] - - helper_method :valid_certificate?, :valid_certificate_key? - helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates? - helper_method :certificate, :certificate_key - - def index - @domains = @project.pages_domains.order(:domain) - end + before_action :authorize_update_pages! 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 + @domains = @project.pages_domains.order(:domain) 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 - - def remove_pages project.remove_pages project.pages_domains.destroy_all @@ -54,18 +18,4 @@ class Projects::PagesController < Projects::ApplicationController end 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 diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb index e69de29bb2d..b8c253f6ae3 100644 --- a/app/controllers/projects/pages_domains_controller.rb +++ b/app/controllers/projects/pages_domains_controller.rb @@ -0,0 +1,49 @@ +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 diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index c560aca5725..0cd25f82cd4 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -6,4 +6,4 @@ %p Removing the pages will prevent from exposing them to outside world. .form-actions - = link_to 'Remove', remove_pages_namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" + = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml deleted file mode 100644 index fd411462330..00000000000 --- a/app/views/projects/pages/_form.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -= form_for [@domain], url: namespace_project_pages_path(@project.namespace, @project), 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' - %span.help-inline * required - - - 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', value: '' - %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', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates - - 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" diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index e88a001d636..c1a6948a574 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -6,8 +6,8 @@ - @domains.each do |domain| %li .pull-right - = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped" - = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" + = 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 diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml deleted file mode 100644 index 1a5dbb79830..00000000000 --- a/app/views/projects/pages/index.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -- page_title "Pages" -%h3.page_title - Pages - - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https - = link_to new_namespace_project_page_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 for free 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' diff --git a/app/views/projects/pages/new.html.haml b/app/views/projects/pages/new.html.haml deleted file mode 100644 index 2609df62aac..00000000000 --- a/app/views/projects/pages/new.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- page_title 'Pages' -%h3.page_title - New Pages Domain -%hr.clearfix -%div - = render 'form' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 8b7010b75b2..9be6f8678cf 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,22 +1,26 @@ -- page_title "#{@domain.domain}", "Pages Domain" +- page_title "Pages" +%h3.page_title + Pages -%h3.page-title - Pages Domain + - if 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 -.table-holder - %table.table - %tr - %td - Domain - %td - = link_to @domain.domain, @domain.url - %tr - %td - Certificate - %td - - if @domain.certificate_text - %pre - = @domain.certificate_text - - else - .light - missing +%p.light + With GitLab Pages you can host for free 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' diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml new file mode 100644 index 00000000000..5458f9e7734 --- /dev/null +++ b/app/views/projects/pages_domains/_form.html.haml @@ -0,0 +1,35 @@ += form_for [@project.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' + %span.help-inline * required + + - 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', value: '' + %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', value: '' + %span.help-inline Upload a certificate for your domain with all intermediates + - 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" diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml new file mode 100644 index 00000000000..2609df62aac --- /dev/null +++ b/app/views/projects/pages_domains/new.html.haml @@ -0,0 +1,6 @@ +- page_title 'Pages' +%h3.page_title + New Pages Domain +%hr.clearfix +%div + = render 'form' diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml new file mode 100644 index 00000000000..8b7010b75b2 --- /dev/null +++ b/app/views/projects/pages_domains/show.html.haml @@ -0,0 +1,22 @@ +- page_title "#{@domain.domain}", "Pages Domain" + +%h3.page-title + Pages Domain + +.table-holder + %table.table + %tr + %td + Domain + %td + = link_to @domain.domain, @domain.url + %tr + %td + Certificate + %td + - if @domain.certificate_text + %pre + = @domain.certificate_text + - else + .light + missing diff --git a/config/routes/project.rb b/config/routes/project.rb index 7a41cb81bfa..ea3bfdd45e6 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -39,10 +39,8 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :pages, except: [:edit, :update] do - collection do - delete :remove_pages - end + resource :pages, only: [:show, :destroy] do + resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains' end resources :compare, only: [:index, :create] do diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index d484ae90bdc..a5cb81b0ef3 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -67,7 +67,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'I visit add a new Pages Domain' do - visit new_namespace_project_page_path(@project.namespace, @project) + visit new_namespace_project_pages_domain_path(@project.namespace, @project) end step 'I fill the domain' do -- cgit v1.2.1 From c6723ed3e018d6249ca9409ebd08c04bd76dea97 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 14:44:54 +0100 Subject: Updated configuration saving --- app/services/projects/update_pages_configuration_service.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index b5324587d0e..188847b5ad6 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -7,7 +7,7 @@ module Projects end def execute - update_file(pages_config_file, pages_config) + update_file(pages_config_file, pages_config.to_json) reload_daemon success rescue => e @@ -52,18 +52,18 @@ module Projects def update_file(file, data) unless data - File.rm(file, force: true) + FileUtils.remove(file, force: true) return end temp_file = "#{file}.#{SecureRandom.hex(16)}" - File.open(temp_file, 'w') do |file| - file.write(data) + File.open(temp_file, 'w') do |f| + f.write(data) end - File.mv(temp_file, file, force: true) + FileUtils.move(temp_file, file, force: true) ensure # In case if the updating fails - File.rm(temp_file, force: true) + FileUtils.remove(temp_file, force: true) end end end -- cgit v1.2.1 From fd7756ec3741e503197d451927849a09526423f6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 14:45:11 +0100 Subject: Added information about the CNAME record --- app/views/projects/pages_domains/show.html.haml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index 8b7010b75b2..e4c5922d863 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -10,6 +10,14 @@ 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 -- cgit v1.2.1 From 361047a7911dbf5da3c33aefa5c77a43621e5514 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Feb 2016 15:01:42 +0100 Subject: Updated according to comments --- app/models/pages_domain.rb | 4 ++-- app/services/projects/update_pages_service.rb | 3 +-- app/views/projects/pages/show.html.haml | 6 +++--- app/views/projects/pages_domains/_form.html.haml | 7 +++---- app/views/projects/pages_domains/new.html.haml | 2 +- app/views/projects/pages_domains/show.html.haml | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 83fdc1c630d..9155e57331d 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -24,9 +24,9 @@ class PagesDomain < ActiveRecord::Base return unless domain if certificate - return "https://#{domain}" + "https://#{domain}" else - return "http://#{domain}" + "http://#{domain}" end end diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index a9979bf1e96..ceabd29fd52 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,6 +1,5 @@ module Projects - class - UpdatePagesService < BaseService + class UpdatePagesService < BaseService BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/' diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 9be6f8678cf..f4ca33f418b 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,14 +1,14 @@ -- page_title "Pages" +- page_title 'Pages' %h3.page_title Pages - if 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 + = 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 for free your static websites on GitLab. + 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. diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index 5458f9e7734..e97d19653d5 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -10,22 +10,21 @@ Domain .col-sm-10 = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control' - %span.help-inline * required - 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', value: '' + = 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', value: '' - %span.help-inline Upload a certificate for your domain with all intermediates + = 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. diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml index 2609df62aac..e1477c71d06 100644 --- a/app/views/projects/pages_domains/new.html.haml +++ b/app/views/projects/pages_domains/new.html.haml @@ -1,4 +1,4 @@ -- page_title 'Pages' +- page_title 'New Pages Domain' %h3.page_title New Pages Domain %hr.clearfix diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index e4c5922d863..52dddb052a7 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@domain.domain}", "Pages Domain" +- page_title "#{@domain.domain}", 'Pages Domains' %h3.page-title Pages Domain -- cgit v1.2.1 From 4d2337175872217eb22f035c3fcd981a38e8a374 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 10:48:51 +0100 Subject: Final fixes --- config/gitlab.yml.example | 4 ++-- features/steps/project/pages.rb | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index f2bde602795..1cf24e3f3db 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -165,8 +165,8 @@ production: &base 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 notifies the GitLab pages do support Custom Domains - # external_https: "1.1.1.1:443" # if defined notifies the GitLab pages do support Custom Domains with Certificates + # 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 ## For enabling Add to Mattermost button diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index a5cb81b0ef3..ac44aac9e38 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -4,14 +4,14 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps include SharedProject step 'pages are enabled' do - Gitlab.config.pages.stub(:enabled).and_return(true) - Gitlab.config.pages.stub(:host).and_return('example.com') - Gitlab.config.pages.stub(:port).and_return(80) - Gitlab.config.pages.stub(:https).and_return(false) + 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 - Gitlab.config.pages.stub(:enabled).and_return(false) + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) end step 'I visit the Project Pages' do @@ -48,18 +48,18 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'support for external domains is disabled' do - Gitlab.config.pages.stub(:external_http).and_return(nil) - Gitlab.config.pages.stub(:external_https).and_return(nil) + 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 - Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') - Gitlab.config.pages.stub(:external_https).and_return(nil) + 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 - Gitlab.config.pages.stub(:external_http).and_return('1.1.1.1:80') - Gitlab.config.pages.stub(:external_https).and_return('1.1.1.1:443') + 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 -- cgit v1.2.1 From 3bcb65c9f2ceaa4213c6d3eb1641ecb0f07d35ad Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 13:32:38 +0100 Subject: Added pages version [ci skip] --- GITLAB_PAGES_VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 GITLAB_PAGES_VERSION diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION new file mode 100644 index 00000000000..6e8bf73aa55 --- /dev/null +++ b/GITLAB_PAGES_VERSION @@ -0,0 +1 @@ +0.1.0 -- cgit v1.2.1 From 8f09ec28379da331fb5bd4a4da950def7b83dd94 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 14:39:58 +0100 Subject: Verify trusted certificate chain --- spec/factories/pages_domains.rb | 78 +++++++++++++++++++++++++++++++++++++++- spec/models/pages_domain_spec.rb | 14 +++++--- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 4608867087c..ff72df8dc02 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -38,8 +38,9 @@ nNp/xedE1YxutQ== -----END PRIVATE KEY-----' end - trait :with_certificate_chain do + 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 @@ -61,6 +62,81 @@ VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w= -----END CERTIFICATE-----' end + trait :with_trusted_chain do + # This is + # [Intermediate #2 (SHA-2)] 'Comodo RSA Domain Validation Secure Server CA' + # [Intermediate #1 (SHA-2)] COMODO RSA Certification Authority + # We only validate that we want to rebuild the trust chain, + # we don't need end-to-end certificate to do that + 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 diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 929b2a26549..3e083ba9001 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -63,7 +63,7 @@ describe PagesDomain, models: true do end context 'for not matching key' do - let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } it { is_expected.to_not be_valid } end @@ -95,7 +95,7 @@ describe PagesDomain, models: true do end context 'for invalid key' do - let(:domain) { build(:pages_domain, :with_certificate_chain, :with_key) } + let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } it { is_expected.to be_falsey } end @@ -110,11 +110,17 @@ describe PagesDomain, models: true do it { is_expected.to be_truthy } end - context 'for certificate chain without the root' do - let(:domain) { build(:pages_domain, :with_certificate_chain) } + 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 + let(:domain) { build(:pages_domain, :with_trusted_chain) } + + it { is_expected.to be_truthy } + end end describe :expired? do -- cgit v1.2.1 From 63eb415610b151495ac54e98804ce37ba5500be4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 14:40:54 +0100 Subject: Fix certificate validators --- app/validators/certificate_key_validator.rb | 2 +- app/validators/certificate_validator.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/validators/certificate_key_validator.rb b/app/validators/certificate_key_validator.rb index 7039bd5a621..098b16017d2 100644 --- a/app/validators/certificate_key_validator.rb +++ b/app/validators/certificate_key_validator.rb @@ -16,7 +16,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator private def valid_private_key_pem?(value) - return unless value + return false unless value pkey = OpenSSL::PKey::RSA.new(value) pkey.private? rescue OpenSSL::PKey::PKeyError diff --git a/app/validators/certificate_validator.rb b/app/validators/certificate_validator.rb index 2a04c76d4b9..e3d18097f71 100644 --- a/app/validators/certificate_validator.rb +++ b/app/validators/certificate_validator.rb @@ -16,9 +16,9 @@ class CertificateValidator < ActiveModel::EachValidator private def valid_certificate_pem?(value) - return unless value - OpenSSL::X509::Certificate.new(value) + return false unless value + OpenSSL::X509::Certificate.new(value).present? rescue OpenSSL::X509::CertificateError - nil + false end end -- cgit v1.2.1 From 92a1efe69b8d3a491e14b5d583033ec7ebd4c623 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 16 Feb 2016 19:13:28 +0100 Subject: Use GitLab Pages 0.2.0 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 6e8bf73aa55..0ea3a944b39 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 -- cgit v1.2.1 From c089f103342ae8f60c7fa9055ef79e3245d6a5fb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 17 Feb 2016 10:05:26 +0100 Subject: Update comments --- features/steps/project/pages.rb | 2 +- spec/factories/pages_domains.rb | 6 ++---- spec/models/pages_domain_spec.rb | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index ac44aac9e38..b3a6b93c5d0 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -34,7 +34,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps 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 diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index ff72df8dc02..6d2e45f41ba 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -63,11 +63,9 @@ VM+d42lQAgoq318A84Xu5vRh1KCAJuztkhNbM+w= end trait :with_trusted_chain do - # This is + # This contains # [Intermediate #2 (SHA-2)] 'Comodo RSA Domain Validation Secure Server CA' - # [Intermediate #1 (SHA-2)] COMODO RSA Certification Authority - # We only validate that we want to rebuild the trust chain, - # we don't need end-to-end certificate to do that + # [Intermediate #1 (SHA-2)] 'COMODO RSA Certification Authority' certificate '-----BEGIN CERTIFICATE----- MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 3e083ba9001..0b95bf594c5 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -117,6 +117,10 @@ describe PagesDomain, models: true do 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 } -- cgit v1.2.1 From 492627c987fd167c956df49843e741cbe29fd77a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 15:11:03 +0100 Subject: Fix the URL of group pages --- app/models/project.rb | 10 +++++++--- doc/pages/README.md | 8 ++++++++ spec/models/project_spec.rb | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index dac52a0fc5e..73a642e1580 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1167,12 +1167,16 @@ class Project < ActiveRecord::Base def pages_url return unless Dir.exist?(public_pages_path) - host = "#{namespace.path}.#{Settings.pages.host}" + # 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 + end.downcase - # If the project path is the same as host, leave the short version + # If the project path is the same as host, we serve it as group page return url if host == path "#{url}/#{path}" diff --git a/doc/pages/README.md b/doc/pages/README.md index f6eb8ccb7a7..eb4217e0d1a 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -45,6 +45,14 @@ URL it will be accessible. | Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | | Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | +## Group pages + +You can create a group page in context of your group. +The project for group page must be written in lower. + +If you have a group `TheRug` and pages are hosted under `Example.com` in order to create a group page +create a new project named `therug.example.com`. + ## Enable the pages feature in your project The GitLab Pages feature needs to be explicitly enabled for each project diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5fde9194e93..cf45ee54fa4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1845,4 +1845,37 @@ describe Project, models: true do def enable_lfs allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) 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 + FileUtils.mkdir_p(project.public_pages_path) + + allow(Settings.pages).to receive(:host).and_return(domain) + allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com') + end + + after do + FileUtils.rmdir(project.public_pages_path) + 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 -- cgit v1.2.1 From 3e6cbcdd00017acae132daafa5af35f16bf48e3c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 15:11:26 +0100 Subject: Fix pages abilities --- app/controllers/projects/pages_controller.rb | 3 ++- app/policies/project_policy.rb | 2 ++ app/views/projects/pages/_destroy.haml | 2 ++ app/views/projects/pages/_list.html.haml | 2 +- app/views/projects/pages/_no_domains.html.haml | 13 +++++++------ app/views/projects/pages/show.html.haml | 2 +- doc/user/permissions.md | 3 +++ 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index b73f998392d..fbd18b68141 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -1,7 +1,8 @@ class Projects::PagesController < Projects::ApplicationController layout 'project_settings' - before_action :authorize_update_pages! + before_action :authorize_read_pages!, only: [:show] + before_action :authorize_update_pages!, except: [:show] def show @domains = @project.pages_domains.order(:domain) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ca5b39a001f..f5fd50745aa 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,8 @@ class ProjectPolicy < BasePolicy can! :admin_pipeline can! :admin_environment can! :admin_deployment + can! :admin_pages + can! :read_pages can! :update_pages end diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 0cd25f82cd4..896a86712a1 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -7,3 +7,5 @@ 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 diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index c1a6948a574..4f2dd1a1398 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,4 +1,4 @@ -- if @domains.any? +- if can?(current_user, :update_pages, @project) && @domains.any? .panel.panel-default .panel-heading Domains (#{@domains.count}) diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml index 5a18740346a..7cea5f3e70b 100644 --- a/app/views/projects/pages/_no_domains.html.haml +++ b/app/views/projects/pages/_no_domains.html.haml @@ -1,6 +1,7 @@ -.panel.panel-default - .panel-heading - Domains - .nothing-here-block - Support for domains and certificates is disabled. - Ask your system's administrator to enable it. +- 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. diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index f4ca33f418b..b6595269b06 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -2,7 +2,7 @@ %h3.page_title Pages - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + - 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 diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 678fc3ffd1f..e87cae092a5 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -62,11 +62,14 @@ The following table depicts the various user permission levels in a project. | Manage runners | | | | ✓ | ✓ | | Manage build triggers | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ | +| Manage pages | | | | ✓ | ✓ | +| Manage pages domains and certificates | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Force push to protected branches [^3] | | | | | | | Remove protected branches [^3] | | | | | | +| Remove pages | | | | | ✓ | ## Group -- cgit v1.2.1 From 8a861c87bf8ba71d5c1a479c8118d9ed6aaf8e88 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:07:27 +0100 Subject: Describe #pages_url instead of :pages_url --- spec/models/project_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index cf45ee54fa4..bb4d82a4df1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1846,7 +1846,7 @@ describe Project, models: true do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) end - describe :pages_url do + 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' } -- cgit v1.2.1 From 06d96a9a624d31294bdf16a4662aaa7121274061 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:12:56 +0100 Subject: Introduce pages_deployed? to Project model --- app/models/project.rb | 6 ++++-- app/views/projects/pages/_access.html.haml | 2 +- app/views/projects/pages/_destroy.haml | 2 +- app/views/projects/pages/_use.html.haml | 2 +- spec/models/project_spec.rb | 23 ++++++++++++++++------ .../services/projects/update_pages_service_spec.rb | 12 +++++------ 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 73a642e1580..a1034e80b6c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1164,9 +1164,11 @@ class Project < ActiveRecord::Base ensure_runners_token! end - def pages_url - return unless Dir.exist?(public_pages_path) + 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 diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml index 9740877b214..82e20eeebb3 100644 --- a/app/views/projects/pages/_access.html.haml +++ b/app/views/projects/pages/_access.html.haml @@ -1,4 +1,4 @@ -- if @project.pages_url +- if @project.pages_deployed? .panel.panel-default .panel-heading Access pages diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 896a86712a1..6a7b6baf767 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,4 +1,4 @@ -- if can?(current_user, :remove_pages, @project) && @project.pages_url +- if can?(current_user, :remove_pages, @project) && @project.pages_deployed? .panel.panel-default.panel.panel-danger .panel-heading Remove pages .errors-holder diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index ee38f45d44d..9db46f0b1fc 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -1,4 +1,4 @@ -- unless @project.pages_url +- unless @project.pages_deployed? .panel.panel-info .panel-heading Configure pages diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bb4d82a4df1..558674b5b39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1068,6 +1068,23 @@ describe Project, models: true do end end + describe '#pages_deployed?' do + let(:project) { create :empty_project } + + subject { project.pages_deployed? } + + context 'if public folder does exist' do + before { FileUtils.mkdir_p(project.public_pages_path) } + after { FileUtils.rmdir(project.public_pages_path) } + + 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 let(:project) { create(:empty_project, description: 'kitten mittens') } @@ -1854,16 +1871,10 @@ describe Project, models: true do subject { project.pages_url } before do - FileUtils.mkdir_p(project.public_pages_path) - allow(Settings.pages).to receive(:host).and_return(domain) allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com') end - after do - FileUtils.rmdir(project.public_pages_path) - end - context 'group page' do let(:group_name) { 'Group' } let(:project_name) { 'group.example.com' } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 68e66866340..51da582c497 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -27,9 +27,9 @@ describe Projects::UpdatePagesService do end it 'succeeds' do - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil + expect(project.pages_deployed?).to be_truthy end it 'limits pages size' do @@ -39,11 +39,11 @@ describe Projects::UpdatePagesService do it 'removes pages after destroy' do expect(PagesWorker).to receive(:perform_in) - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey expect(execute).to eq(:success) - expect(project.pages_url).to_not be_nil + expect(project.pages_deployed?).to be_truthy project.destroy - expect(Dir.exist?(project.public_pages_path)).to be_falsey + expect(project.pages_deployed?).to be_falsey end it 'fails if sha on branch is not latest' do @@ -61,7 +61,7 @@ describe Projects::UpdatePagesService do it 'fails to remove project pages when no pages is deployed' do expect(PagesWorker).to_not receive(:perform_in) - expect(project.pages_url).to be_nil + expect(project.pages_deployed?).to be_falsey project.destroy end -- cgit v1.2.1 From 861129c33ae1a1c4c3122832033c838d4af5d88d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:13:58 +0100 Subject: Mock Dir::exist? in project_spec.rb --- spec/models/project_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 558674b5b39..591ea314142 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1074,8 +1074,7 @@ describe Project, models: true do subject { project.pages_deployed? } context 'if public folder does exist' do - before { FileUtils.mkdir_p(project.public_pages_path) } - after { FileUtils.rmdir(project.public_pages_path) } + before { allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) } it { is_expected.to be_truthy } end -- cgit v1.2.1 From a621b9d5dff7dfb2418a473473df6e6011dfc63a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 20:17:10 +0100 Subject: Update docs --- doc/pages/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index eb4217e0d1a..c9d0d7e2b49 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -47,11 +47,10 @@ URL it will be accessible. ## Group pages -You can create a group page in context of your group. -The project for group page must be written in lower. +To create a page for a group, add a new project to it. The project name must be lowercased. -If you have a group `TheRug` and pages are hosted under `Example.com` in order to create a group page -create a new project named `therug.example.com`. +For example, if you have a group called `TheRug` and pages are hosted under `Example.com`, +create a project named `therug.example.com`. ## Enable the pages feature in your project -- cgit v1.2.1 From c634ff42ae26ed8e33a70a4c5cb75b38f68644fc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 19 Feb 2016 22:17:15 +0100 Subject: Fix broken feature tests --- features/steps/project/pages.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index b3a6b93c5d0..34f97f1ea8b 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -134,6 +134,6 @@ nNp/xedE1YxutQ== end step 'The Pages should get removed' do - expect(@project.pages_url).to be_nil + expect(@project.pages_deployed?).to be_falsey end end -- cgit v1.2.1 From d5ccea0286b229ba64a50e4576a68674d83ef30b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Feb 2016 23:29:40 +0200 Subject: Add init scripts for GitLab Pages daemon --- lib/support/init.d/gitlab | 61 +++++++++++++++++++++++++++---- lib/support/init.d/gitlab.default.example | 24 ++++++++++++ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 31b00ff128a..38a9ab194d1 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -89,6 +89,13 @@ check_pids(){ mpid=0 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. @@ -144,7 +151,15 @@ check_status(){ mail_room_status="-1" 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 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -186,12 +201,19 @@ check_stale_pids(){ exit 1 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. exit_if_not_running(){ 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." exit fi @@ -213,6 +235,9 @@ start_gitlab() { if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo "Starting GitLab MailRoom" 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. if [ "$web_status" = "0" ]; then @@ -252,6 +277,16 @@ start_gitlab() { 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_pids # Finally check the status to tell wether or not GitLab is running @@ -278,13 +313,17 @@ stop_gitlab() { echo "Shutting down GitLab MailRoom" RAILS_ENV=$RAILS_ENV bin/mail_room stop 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. - 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 check_status 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" break fi @@ -298,6 +337,7 @@ stop_gitlab() { if [ "$mail_room_enabled" = true ]; then rm "$mail_room_pid_path" 2>/dev/null fi + rm -f "$gitlab_pages_pid_path" print_status } @@ -305,7 +345,7 @@ stop_gitlab() { ## Prints the status of GitLab and its components. print_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." return fi @@ -331,7 +371,14 @@ print_status() { printf "The GitLab MailRoom email processor 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" ]; }; 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" fi } @@ -362,7 +409,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ 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 fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index cc8617b72ca..6a4f6b090c9 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -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_log="$app_root/log/gitlab-workhorse.log" +# The GitLab Pages Daemon needs to use a separate IP address on which it will +# listen. You can also 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=false` if you want to disable the Pages feature. +gitlab_pages_enabled=true +gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8282" +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. # This is required for the Reply by email feature. # The default is "false" -- cgit v1.2.1 From 50bbc326a475f0cca8e63c7a8de96b3f5538cee0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Feb 2016 23:32:46 +0200 Subject: Change NGINX pages configs to account for the Pages daemon --- lib/support/nginx/gitlab-pages | 16 +++++++--------- lib/support/nginx/gitlab-pages-ssl | 18 ++++++++---------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index ed4f7e4316a..2e0eb2af4b1 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -8,20 +8,18 @@ server { ## Replace this with something like pages.gitlab.com server_name ~^(?.*)\.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 /path/ from shared/pages/${group}/${path}/public/ - # 2. Try to get / from shared/pages/${group}/${host}/public/ - location ~ ^/([^/]*)(/.*)?$ { - try_files "/$1/public$2" - "/$1/public$2/index.html" - "/${host}/public/${uri}" - "/${host}/public/${uri}/index.html" - =404; + 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:8282/; } # Define custom error pages diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index dcbbee4042a..1045cd42d2f 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -23,12 +23,11 @@ server { ## Pages serving host server { listen 0.0.0.0:443 ssl; - listen [::]:443 ipv6only=on 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 - root /home/git/gitlab/shared/pages/${group}; ## Strong SSL Security ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ @@ -63,14 +62,13 @@ server { access_log /var/log/nginx/gitlab_pages_access.log; error_log /var/log/nginx/gitlab_pages_error.log; - # 1. Try to get /path/ from shared/pages/${group}/${path}/public/ - # 2. Try to get / from shared/pages/${group}/${host}/public/ - location ~ ^/([^/]*)(/.*)?$ { - try_files "/$1/public$2" - "/$1/public$2/index.html" - "/${host}/public/${uri}" - "/${host}/public/${uri}/index.html" - =404; + 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:8282/; } # Define custom error pages -- cgit v1.2.1 From 4b45f284c9d060de06f4f54d9e5b1c2815b743dd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:31:46 +0200 Subject: Change the pages daemon proxy listen port to 8090 So as to be consistent with what is set in Omnibus --- lib/support/init.d/gitlab.default.example | 2 +- lib/support/nginx/gitlab-pages | 2 +- lib/support/nginx/gitlab-pages-ssl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index 6a4f6b090c9..f096298afbb 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -68,7 +68,7 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" # The -pages-domain must be specified the same as in `gitlab.yml > pages > host`. # Set `gitlab_pages_enabled=false` if you want to disable the Pages feature. gitlab_pages_enabled=true -gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8282" +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. diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 2e0eb2af4b1..169d7d11ca7 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -19,7 +19,7 @@ server { 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:8282/; + proxy_pass http://localhost:8090/; } # Define custom error pages diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 1045cd42d2f..16edd337e10 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -68,7 +68,7 @@ server { 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:8282/; + proxy_pass http://localhost:8090/; } # Define custom error pages -- cgit v1.2.1 From deb9481efde12e6198b0330bb8eb4c802d1d4b4c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:32:58 +0200 Subject: Add missing variables for gitlab-pages [ci skip] --- lib/support/init.d/gitlab | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 38a9ab194d1..9f2ce01d931 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -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_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_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" # Read configuration variable file if it is present -- cgit v1.2.1 From fd9916c8d2976f724589d581943cc6aa4b1237f7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 00:41:08 +0200 Subject: Add section about changes from 8.4 to 8.5 [ci skip] --- doc/pages/administration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 529a1450fd3..5e0e4f8efed 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -5,6 +5,15 @@ _**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). +## Changes to GitLab Pages from GitLab 8.4 to 8.5 + +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. + +[gitlab-pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages + ## Configuration There are a couple of things to consider before enabling GitLab pages in your -- cgit v1.2.1 From 055b8230cc84252e143798938d179024b083f152 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 12:17:36 +0200 Subject: Add Changelog of GitLab Pages --- doc/pages/administration.md | 51 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 5e0e4f8efed..f45b013b8f6 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,14 +1,17 @@ # GitLab Pages Administration -_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ +> **Note:** +This feature was [introduced][ee-80] in GitLab EE 8.3. + +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](README.md). -## Changes to GitLab Pages from GitLab 8.4 to 8.5 - -In GitLab 8.5 we introduced the [gitlab-pages daemon] which is now the -recommended way to set up GitLab Pages. +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 The NGINX configs have changed to reflect this change. @@ -177,5 +180,39 @@ Pages are part of the regular backup so there is nothing to configure. You should strongly consider running GitLab pages under a different hostname than GitLab to prevent XSS attacks. -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record +## 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, assume that +the documentation is the same as the previous version(s). + +--- + +**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 + +[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 +[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx + +--- + +**GitLab 8.4** + +No new changes. + +--- + +**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 + +--- -- cgit v1.2.1 From 96fed04452ff9b637c2298c05a89f1f14fa091f2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 12:27:50 +0200 Subject: Add first draft of architecture --- doc/pages/administration.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f45b013b8f6..f52703b7e43 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -13,9 +13,13 @@ probably want to read the [user documentation](README.md). [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -The NGINX configs have changed to reflect this change. +## Architecture -[gitlab-pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +GitLab uses a separate tool ([gitlab-pages]), a simple HTTP server written in +Go that serves GitLab Pages with CNAMEs and SNI using HTTP/HTTP2. You are +encouraged to read its [README][pages-readme] to fully understand how it works. + +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md ## Configuration -- cgit v1.2.1 From 0a4585bee4a5f7edde61d7358fb3a30b8f8224a1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:34:18 +0200 Subject: Add MR that custom CNAMEs were introduced --- doc/pages/administration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f52703b7e43..74c960afa4f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,7 +1,10 @@ # GitLab Pages Administration > **Note:** -This feature was [introduced][ee-80] in GitLab EE 8.3. +> This feature was first [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. + +--- 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 -- cgit v1.2.1 From 516a95ddb71c524e10d19fb3cf4ab4893772fa03 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:35:01 +0200 Subject: Add TOC --- doc/pages/administration.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 74c960afa4f..282736cdfd8 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -15,6 +15,27 @@ If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 + +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Architecture](#architecture) +- [Configuration](#configuration) + - [DNS configuration](#dns-configuration) + - [Omnibus package installations](#omnibus-package-installations) + - [Installations from source](#installations-from-source) + - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Set maximum pages size](#set-maximum-pages-size) +- [Change storage path](#change-storage-path) +- [Backup](#backup) +- [Security](#security) +- [Changelog](#changelog) + + ## Architecture -- cgit v1.2.1 From f8927dad8c1f2940953d6694a8b7f507dbe0c3e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 17:36:34 +0200 Subject: More changelog clarification --- doc/pages/administration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 282736cdfd8..fc858cd20e7 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -212,8 +212,9 @@ than GitLab to prevent XSS attacks. 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, assume that -the documentation is the same as the previous version(s). +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. --- -- cgit v1.2.1 From acf7ae5ed80ec39d26dbd37c09bc0f3eb78e1628 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:52:15 +0200 Subject: Add info about the pages daemon --- doc/pages/administration.md | 49 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index fc858cd20e7..3f3d6cac9b2 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -37,11 +37,52 @@ probably want to read the [user documentation](README.md). -## Architecture +## The GitLab Pages daemon -GitLab uses a separate tool ([gitlab-pages]), a simple HTTP server written in -Go that serves GitLab Pages with CNAMEs and SNI using HTTP/HTTP2. You are -encouraged to read its [README][pages-readme] to fully understand how it works. +Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), +a simple HTTP server written in Go that serves GitLab Pages with CNAMEs and SNI +using HTTP/HTTP2. You are encouraged to read its [README][pages-readme] to fully +understand how it works. + +What is supported when using the pages daemon: + +- Multiple domains per-project +- One TLS certificate per-domain + - Validation of certificate + - Validation of certificate chain + - Validation of private key against certificate + +--- + +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, so you basically have three choices: + +1. Run the pages daemon in the same server as GitLab, listening on a secondary IP +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. +1. Run the pages daemon in a separate server. In that case, the Pages [`path`] + must also be present in the server that the pages daemon is installed, so + you will have to share it via network. + +[`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 + +### Install the Pages daemon + +**Install the Pages daemon on a source installation** + +``` +cd /home/git +sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git +cd gitlab-pages +sudo -u git -H git checkout 0.2.0 +sudo -u git -H make +``` + +**Install the Pages daemon on Omnibus** + +The `gitlab-pages` daemon is included in the Omnibus package. [pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md -- cgit v1.2.1 From dfc3e58a5d763d0250e76625ada08b88a7cb7e63 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:52:53 +0200 Subject: Add configuration scenarios --- doc/pages/administration.md | 46 +++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 3f3d6cac9b2..db0eb83b9f4 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -88,18 +88,40 @@ The `gitlab-pages` daemon is included in the Omnibus package. ## Configuration -There are a couple of things to consider before enabling GitLab pages in your -GitLab EE instance. - -1. You need to properly configure your DNS to point to the domain that pages - will be served -1. Pages use a separate Nginx configuration file which needs to be explicitly - added in the server under which GitLab EE runs -1. Optionally but recommended, you can add some - [shared runners](../ci/runners/README.md) so that your users don't have to - bring their own. - -Both of these settings are described in detail in the sections below. +There are multiple ways to set up GitLab Pages according to what URL scheme you +are willing to support. Below you will find all possible scenarios to choose +from. + +### Configuration scenarios + +Before proceeding you have to decide what Pages scenario you want to use. +Remember that in either scenario, you need: + +1. A separate domain +1. A separate Nginx configuration file which needs to be explicitly added in + the server under which GitLab EE runs (Omnibus does that automatically) +1. (Optional) A wildcard certificate for that domain if you decide to serve + pages under HTTPS +1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that + your users don't have to bring their own. + +The possible scenarios are depicted in the table below. + +| URL scheme | Option | Wildcard certificate | Pages daemon | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.gitlab.io` | 1 | no | no | no | no | no | +| `https://page.gitlab.io` | 1 | yes | no | no | no | no | +| `http://page.gitlab.io` and `http://page.com` | 2 | no | yes | yes | no | yes | +| `https://page.gitlab.io` and `https://page.com` | 2 | yes | yes | yes/no | yes | yes | + +As you see from the table above, each URL scheme comes with an option: + +1. Pages enabled, daemon is enabled and NGINX will proxy all requests to the + daemon. Pages daemon doesn't listen to the outside world. +1. 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. ### DNS configuration -- cgit v1.2.1 From 39dff1e1066d2e33267602f11f611af145ba169a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 20:53:20 +0200 Subject: Update TOC --- doc/pages/administration.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index db0eb83b9f4..4e4bea57096 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -23,12 +23,16 @@ probably want to read the [user documentation](README.md). **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [Architecture](#architecture) +- [The GitLab Pages daemon](#the-gitlab-pages-daemon) + - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) + - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) - - [Omnibus package installations](#omnibus-package-installations) - - [Installations from source](#installations-from-source) +- [Custom domains without TLS](#custom-domains-without-tls) +- [Custom domains with TLS](#custom-domains-with-tls) +- [Installations from source](#installations-from-source) - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Omnibus package installations](#omnibus-package-installations) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) -- cgit v1.2.1 From cfc54df4a8386ec5d58ba55fff98264fe746e3ba Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 23:06:44 +0200 Subject: Set pages daemon to false --- lib/support/init.d/gitlab.default.example | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index f096298afbb..e5797d8fe3c 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -47,9 +47,9 @@ 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_log="$app_root/log/gitlab-workhorse.log" -# The GitLab Pages Daemon needs to use a separate IP address on which it will -# listen. You can also use different ports than 80 or 443 that will be -# forwarded to GitLab Pages Daemon. +# 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. @@ -66,8 +66,8 @@ gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" # -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=false` if you want to disable the Pages feature. -gitlab_pages_enabled=true +# 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" -- cgit v1.2.1 From b39947864df75fc52781489fbe59185d788cb208 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Feb 2016 23:07:14 +0200 Subject: chmod 644 gitlab.default.example No need to be executable since it is sourced in /etc/init.d/gitlab --- lib/support/init.d/gitlab.default.example | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 lib/support/init.d/gitlab.default.example diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example old mode 100755 new mode 100644 -- cgit v1.2.1 From 2a484c6a291172c9c69cdcfe068ea4e440771393 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 00:28:14 +0200 Subject: Introduce custom domains setup for source installations [ci skip] --- doc/pages/administration.md | 98 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 4e4bea57096..1d7ee65a0d6 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -30,8 +30,8 @@ probably want to read the [user documentation](README.md). - [DNS configuration](#dns-configuration) - [Custom domains without TLS](#custom-domains-without-tls) - [Custom domains with TLS](#custom-domains-with-tls) -- [Installations from source](#installations-from-source) - - [Running GitLab Pages with HTTPS](#running-gitlab-pages-with-https) +- [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) +- [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Omnibus package installations](#omnibus-package-installations) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) @@ -145,9 +145,99 @@ see the [security section](#security). ### Omnibus package installations -See the relevant documentation at . +## Custom domains without TLS -### Installations from source +1. [Install the pages daemon](#install-the-pages-daemon) +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.1:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Make sure to edit the config to add your domain as well as correctly point + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. + +1. [Restart GitLab](../../administration/restart_gitlab.md) + +## Custom domains with TLS + +1. [Install the pages daemon](#install-the-pages-daemon) +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.1:80 + external_https: 1.1.1.1:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Make sure to edit the config to add your domain as well as correctly point + to the right location of the SSL certificate files. Restart Nginx for the + changes to take effect. + +1. [Restart GitLab](../../administration/restart_gitlab.md) + +## Wildcard HTTPS domain without custom domains 1. Go to the GitLab installation directory: -- cgit v1.2.1 From 54c943a59732cb0e0ce90a3d5db7f98ae807f22b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:10:49 +0200 Subject: Reword pages daemon intro --- doc/pages/administration.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 1d7ee65a0d6..8477bfa8b21 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -44,11 +44,12 @@ probably want to read the [user documentation](README.md). ## The GitLab Pages daemon Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), -a simple HTTP server written in Go that serves GitLab Pages with CNAMEs and SNI -using HTTP/HTTP2. You are encouraged to read its [README][pages-readme] to fully -understand how it works. +a simple HTTP server written in Go that can listen on an external IP address +and provide support for custom domains and custom certificates. The GitLab +Pages Daemon supports dynamic certificates through SNI and exposes pages using +HTTP2 by default. -What is supported when using the pages daemon: +Here is a brief list with what it is supported when using the pages daemon: - Multiple domains per-project - One TLS certificate per-domain @@ -56,6 +57,9 @@ What is supported when using the pages daemon: - Validation of certificate chain - Validation of private key against certificate +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` -- cgit v1.2.1 From ca26884c1538322150c8d3e08a6f2f5295b1c725 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:11:36 +0200 Subject: Add info about the loadbalancer --- doc/pages/administration.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 8477bfa8b21..c009263f648 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -67,12 +67,18 @@ and/or `443`. For that reason, there is some flexibility in the way which you can set it up, so you basically have three choices: 1. Run the pages daemon in the same server as GitLab, listening on a secondary IP -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. 1. Run the pages daemon in a separate server. In that case, the Pages [`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. First let's +install the pages daemon. [`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 -- cgit v1.2.1 From fed8b62c3d4c57dae05ed4796c19bccdaf540886 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:12:31 +0200 Subject: Remove pages daemon from table and add it as prerequisite --- doc/pages/administration.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index c009263f648..eeda4af18fe 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -108,25 +108,26 @@ from. ### Configuration scenarios -Before proceeding you have to decide what Pages scenario you want to use. -Remember that in either scenario, you need: +Before proceeding with setting up GitLab Pages, you have to decide which route +you want to take. Note that in either scenario, you need: +1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) 1. A separate domain 1. A separate Nginx configuration file which needs to be explicitly added in the server under which GitLab EE runs (Omnibus does that automatically) 1. (Optional) A wildcard certificate for that domain if you decide to serve pages under HTTPS 1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that - your users don't have to bring their own. + your users don't have to bring their own The possible scenarios are depicted in the table below. -| URL scheme | Option | Wildcard certificate | Pages daemon | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | | --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.gitlab.io` | 1 | no | no | no | no | no | -| `https://page.gitlab.io` | 1 | yes | no | no | no | no | -| `http://page.gitlab.io` and `http://page.com` | 2 | no | yes | yes | no | yes | -| `https://page.gitlab.io` and `https://page.com` | 2 | yes | yes | yes/no | yes | yes | +| `http://page.example.io` | 1 | no | no | no | no | +| `https://page.example.io` | 1 | yes | no | no | no | +| `http://page.example.io` and `http://page.com` | 2 | no | yes | no | yes | +| `https://page.example.io` and `https://page.com` | 2 | yes | redirects to HTTPS | yes | yes | As you see from the table above, each URL scheme comes with an option: -- cgit v1.2.1 From d24763fa16874cf8f99a32635787134f4f76d699 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:22:28 +0200 Subject: Add the four scenarios and NGINX caveats --- doc/pages/administration.md | 203 +++++++++++++++++++++++++++----------------- 1 file changed, 126 insertions(+), 77 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index eeda4af18fe..5d7c0d76620 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -28,11 +28,12 @@ probably want to read the [user documentation](README.md). - [Configuration](#configuration) - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) -- [Custom domains without TLS](#custom-domains-without-tls) -- [Custom domains with TLS](#custom-domains-with-tls) -- [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) -- [Omnibus package installations](#omnibus-package-installations) +- [Setting up GitLab Pages](#setting-up-gitlab-pages) + - [Custom domains with HTTPS support](#custom-domains-with-https-support) + - [Custom domains without HTTPS support](#custom-domains-without-https-support) + - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) + - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) +- [NGINX caveats](#nginx-caveats) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -145,46 +146,57 @@ 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: ``` -*.gitlab.io. 60 IN A 1.2.3.4 +*.example.io. 1800 IN A 1.2.3.4 ``` -where `gitlab.io` is the domain under which GitLab Pages will be served +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. You should not use the GitLab domain to serve user pages. For more information see the [security section](#security). -### Omnibus package installations +[wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record -## Custom domains without TLS +## Setting up GitLab Pages + +Below are the four scenarios that are described in +[#configuration-scenarios](#configuration-scenarios). + +### Custom domains with HTTPS support + +**Source installations:** 1. [Install the pages daemon](#install-the-pages-daemon) 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: ```yaml + ## GitLab Pages pages: enabled: true # The location where pages are stored (default: shared/pages). # path: shared/pages host: example.io - port: 80 - https: false + port: 443 + https: true external_http: 1.1.1.1:80 + external_https: 1.1.1.1:443 ``` 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: ``` gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: @@ -194,45 +206,47 @@ see the [security section](#security). sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. + Make sure to [properly edit the config](#nginx-caveats) to add your domain + as well as correctly point to the right location of the SSL certificate + files. Restart Nginx for the changes to take effect. + +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** -1. [Restart GitLab](../../administration/restart_gitlab.md) +### Custom domains without HTTPS support -## Custom domains with TLS +**Source installations:** 1. [Install the pages daemon](#install-the-pages-daemon) 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` and `external_https` to the secondary IP on which the pages - daemon will listen for connections: + `external_http` to the secondary IP on which the pages daemon will listen + for connections: ```yaml - ## GitLab Pages pages: enabled: true # The location where pages are stored (default: shared/pages). # path: shared/pages host: example.io - port: 443 - https: true + port: 80 + https: false external_http: 1.1.1.1:80 - external_https: 1.1.1.1:443 ``` 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, - `external_http` and `external_https` settings that you set above respectively. - The `-root-cert` and `-root-key` settings are the wildcard TLS certificates - of the `example.io` domain: + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: ``` gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: @@ -242,14 +256,20 @@ see the [security section](#security). sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. + Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Restart Nginx for the changes to take effect. + +1. [Restart GitLab][restart] + +--- -1. [Restart GitLab](../../administration/restart_gitlab.md) +**Omnibus installations:** -## Wildcard HTTPS domain without custom domains +### Wildcard HTTP domain without custom domains +**Source installations:** + +1. [Install the pages daemon](#install-the-pages-daemon) 1. Go to the GitLab installation directory: ```bash @@ -266,12 +286,9 @@ see the [security section](#security). # 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: gitlab.io - port: 80 # Set to 443 if you serve the pages with HTTPS - https: false # Set to true if you serve the pages with HTTPS + host: example.io + port: 80 + https: false ``` 1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: @@ -281,39 +298,27 @@ see the [security section](#security). sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Don't forget to add your domain name in the Nginx config. For example if - your GitLab pages domain is `gitlab.io`, replace + Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Restart Nginx for the changes to take effect. - ```bash - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; - ``` +1. [Restart GitLab][restart] - with +--- - ``` - server_name ~^(?.*)\.gitlabpages\.com$; - ``` +**Omnibus installations:** - You must be extra careful to not remove the backslashes. If you are using - a subdomain, make sure to escape all dots (`.`) with a backslash (\). - For example `pages.gitlab.io` would be: +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + ```ruby + pages_external_url 'http://example.io' ``` - server_name ~^(?.*)\.pages\.gitlab\.io$; - ``` +1. [Reconfigure GitLab][reconfigure] -1. Restart Nginx and GitLab: +### Wildcard HTTPS domain without custom domains - ```bash - sudo service nginx restart - sudo service gitlab restart - ``` - -### Running GitLab Pages with HTTPS - -If you want the pages to be served under HTTPS, a wildcard SSL certificate is -required. +**Source installations:** +1. [Install the pages daemon](#install-the-pages-daemon) 1. In `gitlab.yml`, set the port to `443` and https to `true`: ```bash @@ -323,24 +328,65 @@ required. # 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: gitlab.io - port: 443 # Set to 443 if you serve the pages with HTTPS - https: true # Set to true if you serve the pages with HTTPS + host: example.io + port: 443 + https: true ``` 1. Copy the `gitlab-pages-ssl` Nginx configuration file: ```bash sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to edit the config to add your domain as well as correctly point - to the right location of the SSL certificate files. Restart Nginx for the - changes to take effect. + Make sure to [properly edit the config](#nginx-caveats) to add your domain + as well as correctly point to the right location of the SSL certificate + files. Restart Nginx for the changes to take effect. + +--- + +**Omnibus installations:** + +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] + +## NGINX caveats + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^(?.*)\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) with a +backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^(?.*)\.pages\.example\.io$; +``` ## Set maximum pages size @@ -413,3 +459,6 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md --- + +[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart]: ../../administration/restart_gitlab.md#installations-from-source -- cgit v1.2.1 From 228455af6baefff6bea679c30216d6ede703b2de Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 01:28:28 +0200 Subject: Rename NGINX section --- doc/pages/administration.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 5d7c0d76620..675a41d8023 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -33,7 +33,7 @@ probably want to read the [user documentation](README.md). - [Custom domains without HTTPS support](#custom-domains-without-https-support) - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [NGINX caveats](#nginx-caveats) +- [NGINX configuration](#nginx-configuration) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -206,7 +206,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain + Make sure to [properly edit the config](#nginx-configuration) to add your domain as well as correctly point to the right location of the SSL certificate files. Restart Nginx for the changes to take effect. @@ -249,14 +249,14 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: +1. Copy the `gitlab-pages` Nginx configuration file: ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Make sure to [properly edit the config](#nginx-configuration) to add your domain. Restart Nginx for the changes to take effect. 1. [Restart GitLab][restart] @@ -298,7 +298,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain. + Make sure to [properly edit the config](#nginx-configuration) to add your domain. Restart Nginx for the changes to take effect. 1. [Restart GitLab][restart] @@ -340,7 +340,7 @@ Below are the four scenarios that are described in sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf ``` - Make sure to [properly edit the config](#nginx-caveats) to add your domain + Make sure to [properly edit the config](#nginx-configuration) to add your domain as well as correctly point to the right location of the SSL certificate files. Restart Nginx for the changes to take effect. @@ -364,7 +364,7 @@ Below are the four scenarios that are described in 1. [Reconfigure GitLab][reconfigure] -## NGINX caveats +## NGINX configuration Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. -- cgit v1.2.1 From 3858443fdf96435d21dc3331169f9066793cf5e7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:37:54 +0200 Subject: Add heading to the GitLab Pages setup scenarios --- doc/pages/administration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 675a41d8023..2b861cc55cf 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -24,6 +24,7 @@ probably want to read the [user documentation](README.md). **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [The GitLab Pages daemon](#the-gitlab-pages-daemon) + - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) - [Configuration scenarios](#configuration-scenarios) @@ -61,7 +62,7 @@ Here is a brief list with what it is supported when using the pages daemon: You are encouraged to read its [README][pages-readme] to fully understand how it works. ---- +### The GitLab Pages daemon and the case of custom domains 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 -- cgit v1.2.1 From f8c8dc03d007efeb52a6f81add6b49697001cb09 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:39:27 +0200 Subject: Add remaining Omnibus configs --- doc/pages/administration.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2b861cc55cf..2b50ed1a126 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -217,6 +217,23 @@ Below are the four scenarios that are described in **Omnibus installations:** +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + nginx['listen_addresses'] = ['1.1.1.1'] + pages_nginx['enable'] = false + 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. + Read more at the + [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + section. + +1. [Reconfigure GitLab][reconfigure] + ### Custom domains without HTTPS support **Source installations:** @@ -266,6 +283,22 @@ Below are the four scenarios that are described in **Omnibus installations:** +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + 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. + Read more at the + [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + section. + +1. [Reconfigure GitLab][reconfigure] + ### Wildcard HTTP domain without custom domains **Source installations:** @@ -313,6 +346,7 @@ Below are the four scenarios that are described in ```ruby pages_external_url 'http://example.io' ``` + 1. [Reconfigure GitLab][reconfigure] ### Wildcard HTTPS domain without custom domains -- cgit v1.2.1 From d9e3bb0e7def068c5b24937bf887b20784d4bd8e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:40:04 +0200 Subject: Add a separate NGINX section --- doc/pages/administration.md | 89 ++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2b50ed1a126..f67bb63ff07 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -35,6 +35,9 @@ probably want to read the [user documentation](README.md). - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) - [NGINX configuration](#nginx-configuration) + - [NGINX configuration files](#nginx-configuration-files) + - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) + - [NGINX caveats](#nginx-caveats) - [Set maximum pages size](#set-maximum-pages-size) - [Change storage path](#change-storage-path) - [Backup](#backup) @@ -200,17 +203,7 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain - as well as correctly point to the right location of the SSL certificate - files. Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -267,16 +260,7 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Copy the `gitlab-pages` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain. - Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -325,16 +309,7 @@ Below are the four scenarios that are described in https: false ``` -1. Make sure you have copied the new `gitlab-pages` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain. - Restart Nginx for the changes to take effect. - +1. Make sure to [configure NGINX](#nginx-configuration) properly. 1. [Restart GitLab][restart] --- @@ -368,16 +343,7 @@ Below are the four scenarios that are described in https: true ``` -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Make sure to [properly edit the config](#nginx-configuration) to add your domain - as well as correctly point to the right location of the SSL certificate - files. Restart Nginx for the changes to take effect. +1. Make sure to [configure NGINX](#nginx-configuration) properly. --- @@ -401,6 +367,47 @@ Below are the four scenarios that are described in ## NGINX configuration +Depending on your setup, you will need to make some changes to NGINX. +Specifically you must change the domain name and the IP address where NGINX +listens to. Read the following sections for more details. + +### NGINX configuration files + +Copy the `gitlab-pages-ssl` Nginx configuration file: + +```bash +sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf +sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf +``` + +Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +### NGINX configuration for custom domains + +> If you are not using custom domains ignore this section. + +[In the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains), +if you have the secondary IP address configured on the same server as GitLab, +you need to change **all** NGINX configs to listen on the first IP address. + +**Source installations:** + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX + +**Omnibus installations:** + +1. Edit `/etc/gitlab/gilab.rb`: + + ``` + nginx['listen_addresses'] = ['1.1.1.1'] + ``` +1. [Reconfigure GitLab][reconfigure] + +### NGINX caveats + Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. -- cgit v1.2.1 From 84ff07cdcc846cec7a58aca641363531cda86d92 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 02:49:02 +0200 Subject: Simplify NGINX server_name regex --- doc/pages/administration.md | 10 +++++----- lib/support/nginx/gitlab-pages | 2 +- lib/support/nginx/gitlab-pages-ssl | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index f67bb63ff07..30d2b46c36a 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -414,20 +414,20 @@ not remove the backslashes. If your GitLab pages domain is `example.io`, replace: ```bash -server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; ``` with: ``` -server_name ~^(?.*)\.example\.io$; +server_name ~^.*\.example\.io$; ``` -If you are using a subdomain, make sure to escape all dots (`.`) with a -backslash (\). For example `pages.example.io` would be: +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: ``` -server_name ~^(?.*)\.pages\.example\.io$; +server_name ~^.*\.pages\.example\.io$; ``` ## Set maximum pages size diff --git a/lib/support/nginx/gitlab-pages b/lib/support/nginx/gitlab-pages index 169d7d11ca7..d9746c5c1aa 100644 --- a/lib/support/nginx/gitlab-pages +++ b/lib/support/nginx/gitlab-pages @@ -7,7 +7,7 @@ server { listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; ## Individual nginx logs for GitLab pages access_log /var/log/nginx/gitlab_pages_access.log; diff --git a/lib/support/nginx/gitlab-pages-ssl b/lib/support/nginx/gitlab-pages-ssl index 16edd337e10..a1ccf266835 100644 --- a/lib/support/nginx/gitlab-pages-ssl +++ b/lib/support/nginx/gitlab-pages-ssl @@ -11,7 +11,7 @@ server { listen [::]:80 ipv6only=on; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + 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; @@ -26,7 +26,7 @@ server { listen [::]:443 ipv6only=on ssl http2; ## Replace this with something like pages.gitlab.com - server_name ~^(?.*)\.YOUR_GITLAB_PAGES\.DOMAIN$; + server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; server_tokens off; ## Don't show the nginx version number, a security best practice ## Strong SSL Security -- cgit v1.2.1 From aadbb6065b58d3d308b1fe7a15f5dfad290e0771 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:03:05 +0200 Subject: Move Omnibus storage path --- doc/pages/administration.md | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 30d2b46c36a..c99ff87e87d 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -72,9 +72,9 @@ and/or `443`. For that reason, there is some flexibility in the way which you can set it up, so you basically have three choices: 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`] - 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 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 @@ -85,8 +85,6 @@ can set it up, so you basically have three choices: In this document, we will proceed assuming the first option. First let's install the pages daemon. -[`path`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/config/gitlab.yml.example#L155 - ### Install the Pages daemon **Install the Pages daemon on a source installation** @@ -404,6 +402,7 @@ you need to change **all** NGINX configs to listen on the first IP address. ``` nginx['listen_addresses'] = ['1.1.1.1'] ``` + 1. [Reconfigure GitLab][reconfigure] ### NGINX caveats @@ -438,22 +437,32 @@ The default is 100MB. ## Change storage path -Pages are stored by default in `/home/git/gitlab/shared/pages`. -If you wish to store them in another location you must set it up in -`gitlab.yml` under the `pages` section: +**Source installations:** -```yaml -pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages -``` +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: -Restart GitLab for the changes to take effect: + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` -```bash -sudo service gitlab restart -``` +1. [Restart GitLab][restart] + +**Omnibus installations:** + +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] ## Backup -- cgit v1.2.1 From 37fc486c73d91d25ddeb35eb6d528e16dcb1e9a9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:03:42 +0200 Subject: Link to backup task --- doc/pages/administration.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index c99ff87e87d..62665a94f1c 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -466,7 +466,7 @@ The default is 100MB. ## Backup -Pages are part of the regular backup so there is nothing to configure. +Pages are part of the [regular backup][backup] so there is nothing to configure. ## Security @@ -509,7 +509,6 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md ---- - [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source +[backup]: ../../raketasks/backup_restore.md -- cgit v1.2.1 From 9ca234615e44e2b365b8d6c1cc5a91c5bdd7159d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:16:27 +0200 Subject: Add link to Omnibus 8-3 docs --- doc/pages/administration.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 62665a94f1c..4aaabdb3442 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -490,6 +490,7 @@ latest previous version. - 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 [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md [gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 @@ -503,12 +504,12 @@ No new changes. --- -**GitLab 8.3 ([documentation][8-3-docs])** +**GitLab 8.3 ([source docs][8-3-docs], [Omnibus docs][8-3-omnidocs])** - 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-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source [backup]: ../../raketasks/backup_restore.md -- cgit v1.2.1 From e5c7c8ca5b56563e1c133d736eb13c87aa749fa9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:27:44 +0200 Subject: Small reword fixes --- doc/pages/administration.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 4aaabdb3442..ad9c04a64bb 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -48,7 +48,7 @@ probably want to read the [user documentation](README.md). ## The GitLab Pages daemon -Starting from GitLab EE 8.5, Pages make use of a separate tool ([gitlab-pages]), +Starting from GitLab EE 8.5, GitLab Pages make 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. The GitLab Pages Daemon supports dynamic certificates through SNI and exposes pages using @@ -65,6 +65,9 @@ Here is a brief list with what it is supported when using the pages daemon: You are encouraged to read its [README][pages-readme] to fully understand how it works. +[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md + ### The GitLab Pages daemon and the case of custom domains In the case of custom domains, the Pages daemon needs to listen on ports `80` @@ -87,7 +90,7 @@ install the pages daemon. ### Install the Pages daemon -**Install the Pages daemon on a source installation** +**Source installations** ``` cd /home/git @@ -97,11 +100,10 @@ sudo -u git -H git checkout 0.2.0 sudo -u git -H make ``` -**Install the Pages daemon on Omnibus** +**Omnibus installations** The `gitlab-pages` daemon is included in the Omnibus package. -[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md ## Configuration -- cgit v1.2.1 From bdc7301aa448b79766fa342459aa1749deeb7b85 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 03:33:20 +0200 Subject: Add configuration prerequisites section [ci skip] --- doc/pages/administration.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index ad9c04a64bb..7bdd331df65 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -27,6 +27,7 @@ probably want to read the [user documentation](README.md). - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - [Install the Pages daemon](#install-the-pages-daemon) - [Configuration](#configuration) + - [Configuration prerequisites](#configuration-prerequisites) - [Configuration scenarios](#configuration-scenarios) - [DNS configuration](#dns-configuration) - [Setting up GitLab Pages](#setting-up-gitlab-pages) @@ -108,13 +109,13 @@ The `gitlab-pages` daemon is included in the Omnibus package. ## Configuration There are multiple ways to set up GitLab Pages according to what URL scheme you -are willing to support. Below you will find all possible scenarios to choose -from. +are willing to support. -### Configuration scenarios +### Configuration prerequisites -Before proceeding with setting up GitLab Pages, you have to decide which route -you want to take. Note that in either scenario, you need: +In the next section you will find all possible scenarios to choose from. + +In either scenario, you will need: 1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) 1. A separate domain @@ -125,6 +126,11 @@ you want to take. Note that in either scenario, you need: 1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that your users don't have to bring their own +### Configuration scenarios + +Before proceeding with setting up GitLab Pages, you have to decide which route +you want to take. + The possible scenarios are depicted in the table below. | URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -- cgit v1.2.1 From e40693047aeb293afa00800a5eea743559337308 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 09:57:27 +0200 Subject: Checkout the tag of pages daemon --- doc/pages/administration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 7bdd331df65..ddbc8a7765f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -86,8 +86,8 @@ can set it up, so you basically have three choices: 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. First let's -install the pages daemon. +In this document, we will proceed assuming the first option. Let's begin by +installing the pages daemon. ### Install the Pages daemon @@ -97,7 +97,7 @@ install the pages daemon. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages -sudo -u git -H git checkout 0.2.0 +sudo -u git -H git checkout v0.2.0 sudo -u git -H make ``` -- cgit v1.2.1 From 8094a9d1115bfbe2899fd63862f0d3f9fcce438b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 12:19:02 +0200 Subject: Add missing Omnibus settings --- doc/pages/administration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index ddbc8a7765f..d0fdbeafa5b 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -219,8 +219,11 @@ Below are the four scenarios that are described in 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' ``` @@ -276,6 +279,7 @@ Below are the four scenarios that are described in 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['external_http'] = '1.1.1.2:80' -- cgit v1.2.1 From 5556db04040c8c97834728dcf0fb26d2ea2c9a16 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Feb 2016 12:26:22 +0200 Subject: Add missing gitlab-pages related vars in init.d/gitlab --- lib/support/init.d/gitlab | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 9f2ce01d931..97414ead3dd 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -107,7 +107,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing its pid 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; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -240,7 +240,7 @@ start_gitlab() { if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo "Starting GitLab MailRoom" fi - if [ "gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then + if [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then echo "Starting GitLab Pages" fi -- cgit v1.2.1 From 639cf728f8c14560e85e0f54d5f4f27329d98c3c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 23 Feb 2016 12:03:03 +0100 Subject: Fix adding pages domain to projects in groups --- app/views/projects/pages_domains/_form.html.haml | 2 +- features/project/pages.feature | 9 +++++++++ features/steps/shared/project.rb | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index e97d19653d5..ca1b41b140a 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace, @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| += 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 diff --git a/features/project/pages.feature b/features/project/pages.feature index 392f2d29c3c..87d88348d09 100644 --- a/features/project/pages.feature +++ b/features/project/pages.feature @@ -40,6 +40,15 @@ Feature: Project Pages 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 diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 7a6707a7dfb..dae248b8b7e 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -7,6 +7,12 @@ module SharedProject @project.team << [@user, :master] 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 @group = create(:group, name: 'some group') @project = create(:project, :repository, namespace: @group, public_builds: false) -- cgit v1.2.1 From 55214fe1ebed923b23df43afc6da34aede2a00d2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 23 Feb 2016 15:45:03 +0200 Subject: First iteration on simplifying the pages user docs [ci skip] --- doc/pages/README.md | 156 ++++++++++++++++++++++++++++++------- doc/pages/img/create_user_page.png | Bin 0 -> 66593 bytes 2 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 doc/pages/img/create_user_page.png diff --git a/doc/pages/README.md b/doc/pages/README.md index c9d0d7e2b49..73e18d1b9a7 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,11 +1,38 @@ # GitLab Pages -_**Note:** This feature was [introduced][ee-80] in GitLab EE 8.3_ +> **Note:** +> This feature was [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. With GitLab Pages you can host for free 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. +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) +- [Understanding how GitLab Pages work](#understanding-how-gitlab-pages-work) +- [Two kinds of GitLab Pages](#two-kinds-of-gitlab-pages) + - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) + - [GitLab pages per project](#gitlab-pages-per-project) +- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) +- [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) +- [Example projects](#example-projects) +- [Custom error codes pages](#custom-error-codes-pages) + - [Adding a custom domain to your Pages](#adding-a-custom-domain-to-your-pages) + - [Securing your Pages with TLS](#securing-your-pages-with-tls) +- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) +- [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [Limitations](#limitations) +- [Frequently Asked Questions](#frequently-asked-questions) + + + ## Enable the pages feature in your GitLab EE instance The administrator guide is located at [administration](administration.md). @@ -16,41 +43,60 @@ GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. The steps that are performed from the initialization of a project to the creation of the static content, can be summed up to: -1. Create project (its name could be specific according to the case) -1. Provide a specific job in `.gitlab-ci.yml` +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project (its name should be specific according to the case) +1. Provide a specific job named `pages` in `.gitlab-ci.yml` 1. GitLab Runner builds the project 1. GitLab CI uploads the artifacts -1. Nginx serves the content +1. The [GitLab Pages daemon][pages-daemon] serves the content -As a user, you should normally be concerned only with the first three items. -If [shared runners](../ci/runners/README.md) are enabled by your GitLab +As a user, you should normally be concerned only with the first three or four +items. If [shared runners](../ci/runners/README.md) are enabled by your GitLab administrator, you should be able to use them instead of bringing your own. -In general there are four kinds of pages one might create. This is better -explained with an example so let's make some assumptions. +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + +## Two kinds of GitLab Pages + +In general there are two kinds of pages one might create: + +- Pages per user/group +- Pages per project -The domain under which the pages are hosted is named `gitlab.io`. There is a -user with the username `walter` and they are the owner of an organization named -`therug`. The personal project of `walter` is named `area51` and don't forget -that the organization has also a project under its namespace, called -`welovecats`. +In GitLab, usernames and groupnames are unique and often people refer to them +as namespaces. There can be only one namespace in a GitLab instance. -The following table depicts what the project's name should be and under which -URL it will be accessible. +> **Warning:** +> There are some known [limitations](#limitations) regarding namespaces served +> under the general domain name and HTTPS. Make sure to read that section. + +### GitLab pages per user or group + +Head over your GitLab instance that supports GitLab Pages and create a +repository named `username.example.io`, where `username` is your username on +GitLab. If the first part of the project name doesn’t match exactly your +username, it won’t work, so make sure to get it right. + +![Create a user-based pages repository](img/create_user_page.png) + +--- -| Pages type | Repository name | URL schema | -| ---------- | --------------- | ---------- | -| User page | `walter/walter.gitlab.io` | `https://walter.gitlab.io` | -| Group page | `therug/therug.gitlab.io` | `https://therug.gitlab.io` | -| Specific project under a user's page | `walter/area51` | `https://walter.gitlab.io/area51` | -| Specific project under a group's page | `therug/welovecats` | `https://therug.gitlab.io/welovecats` | +To create a group page the steps are exactly the same. Just make sure that +you are creating the project within the group's namespace. -## Group pages +After you upload some static content to your repository, you will be able to +access it under `http(s)://username.example.io`. Keep reading to find out how. -To create a page for a group, add a new project to it. The project name must be lowercased. +### GitLab pages per project + +> **Note:** +> You do _not_ have to create a project named `username.example.io` in order to +> serve a project's page. -For example, if you have a group called `TheRug` and pages are hosted under `Example.com`, -create a project named `therug.example.com`. ## Enable the pages feature in your project @@ -60,7 +106,7 @@ under its **Settings**. ## Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** -in a project's **Settings**. +Go to your project's **Settings > Pages**. ## Explore the contents of .gitlab-ci.yml @@ -86,8 +132,8 @@ a commit is pushed only on the `master` branch, which is advisable to do so. The pages are created after the build completes successfully and the artifacts for the `pages` job are uploaded to GitLab. -The example below is using [Jekyll][] and assumes that the created HTML files -are generated under the `public/` directory. +The example below uses [Jekyll][] and generates the created HTML files +under the `public/` directory. ```yaml image: ruby:2.1 @@ -103,11 +149,31 @@ pages: - master ``` +The example below doesn't use any static site generator, but 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. + +```yaml +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master +``` + ## Example projects -Below is a list of example projects that make use of static generators. -Contributions are very welcome. +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. +* [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) * [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) ## Custom error codes pages @@ -116,6 +182,34 @@ You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. +### Adding a custom domain to your Pages + + + +### Securing your Pages with TLS + + +## Enable the pages feature in your project + +The GitLab Pages feature needs to be explicitly enabled for each project +under **Settings > Pages**. + +## Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +in a project's **Settings**. + +## Limitations + +When using Pages under the general domain of a GitLab instance (`*.example.io`), +you _cannot_ use HTTPS with sub-subdomains. That means that if your +username/groupname contains a dot, for example `foo.bar`, the domain +`https://foo.bar.example.io` will _not_ work. This is a limitation of the +[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you +don't redirect HTTP to HTTPS. + +[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" + ## Frequently Asked Questions **Q: Can I download my generated pages?** @@ -126,3 +220,5 @@ Sure. All you need to do is download the artifacts archive from the build page. [jekyll]: http://jekyllrb.com/ [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages diff --git a/doc/pages/img/create_user_page.png b/doc/pages/img/create_user_page.png new file mode 100644 index 00000000000..8c2b67e233a Binary files /dev/null and b/doc/pages/img/create_user_page.png differ -- cgit v1.2.1 From 47bff50ffd68fccd6db71bc800194d49fad6eaa1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 23 Feb 2016 19:29:24 +0200 Subject: Reorganize sections [ci skip] --- doc/pages/README.md | 72 ++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 73e18d1b9a7..50f45a4cae1 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -15,19 +15,17 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) -- [Understanding how GitLab Pages work](#understanding-how-gitlab-pages-work) -- [Two kinds of GitLab Pages](#two-kinds-of-gitlab-pages) +- [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) -- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) -- [Remove the contents of your pages](#remove-the-contents-of-your-pages) -- [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) -- [Example projects](#example-projects) -- [Custom error codes pages](#custom-error-codes-pages) - - [Adding a custom domain to your Pages](#adding-a-custom-domain-to-your-pages) - - [Securing your Pages with TLS](#securing-your-pages-with-tls) -- [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) -- [Remove the contents of your pages](#remove-the-contents-of-your-pages) + - [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) + - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) +- [Next steps](#next-steps) + - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) + - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) + - [Example projects](#example-projects) + - [Custom error codes pages](#custom-error-codes-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) @@ -37,7 +35,7 @@ deploy static pages for your individual projects, your user or your group. The administrator guide is located at [administration](administration.md). -## Understanding how GitLab Pages work +## Getting started with GitLab Pages GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. The steps that are performed from the initialization of a project to the @@ -60,8 +58,6 @@ administrator, you should be able to use them instead of bringing your own. > In the rest of this document we will assume that the general domain name that > is used for GitLab Pages is `example.io`. -## Two kinds of GitLab Pages - In general there are two kinds of pages one might create: - Pages per user/group @@ -78,7 +74,7 @@ as namespaces. There can be only one namespace in a GitLab instance. Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on -GitLab. If the first part of the project name doesn’t match exactly your +GitLab. If the first part of the project name doesn't match exactly your username, it won’t work, so make sure to get it right. ![Create a user-based pages repository](img/create_user_page.png) @@ -97,18 +93,19 @@ access it under `http(s)://username.example.io`. Keep reading to find out how. > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. +GitLab Pages for projects -## Enable the pages feature in your project +### Enable the pages feature in your project The GitLab Pages feature needs to be explicitly enabled for each project under its **Settings**. -## Remove the contents of your pages +### Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** Go to your project's **Settings > Pages**. -## Explore the contents of .gitlab-ci.yml +### Explore the contents of .gitlab-ci.yml Before reading this section, make sure you familiarize yourself with GitLab CI and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by @@ -151,7 +148,7 @@ pages: The example below doesn't use any static site generator, but 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 +workaround is so `cp` doesn't also copy `public/` to itself in an infinite loop. ```yaml @@ -168,36 +165,27 @@ pages: - master ``` -## Example projects +## Next steps + +### Adding a custom domain to your Pages website + + +### Securing your custom domain website with TLS + +### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. -* [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -* [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) +- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -## Custom error codes pages +### Custom error codes pages You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. -### Adding a custom domain to your Pages - - - -### Securing your Pages with TLS - - -## Enable the pages feature in your project - -The GitLab Pages feature needs to be explicitly enabled for each project -under **Settings > Pages**. - -## Remove the contents of your pages - -Pages can be explicitly removed from a project by clicking **Remove Pages** -in a project's **Settings**. ## Limitations @@ -216,6 +204,12 @@ don't redirect HTTP to HTTPS. Sure. All you need to do is download the artifacts archive from the build page. + +**Q: Can I use GitLab Pages if my project is private?** + +Yes. GitLab Pages doesn't care whether you set your project's visibility level +to private, internal or public. + --- [jekyll]: http://jekyllrb.com/ -- cgit v1.2.1 From 5b9d886963dd9be2487226f34012bf0ed0de406d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 09:49:48 +0200 Subject: Add links to CI and runner --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 50f45a4cae1..0d24d92a23c 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -5,7 +5,7 @@ > Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. With GitLab Pages you can host for free your static websites on GitLab. -Combined with the power of GitLab CI and the help of GitLab Runner you can +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. --- @@ -216,3 +216,5 @@ to private, internal or public. [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[gitlab ci]: https://about.gitlab.com/gitlab-ci +[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner -- cgit v1.2.1 From 81533b8f0c3efb9c1e11933f13e768550afa9d42 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 12:36:31 +0200 Subject: More section cleanup --- doc/pages/README.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 0d24d92a23c..17063ef3ba0 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -4,6 +4,10 @@ > This feature was [introduced][ee-80] in GitLab EE 8.3. > Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> **Note:** +> This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](administration.md). + With GitLab Pages you can host for free 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. @@ -14,13 +18,11 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [Enable the pages feature in your GitLab EE instance](#enable-the-pages-feature-in-your-gitlab-ee-instance) - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) - - [Enable the pages feature in your project](#enable-the-pages-feature-in-your-project) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) @@ -31,10 +33,6 @@ deploy static pages for your individual projects, your user or your group. -## Enable the pages feature in your GitLab EE instance - -The administrator guide is located at [administration](administration.md). - ## Getting started with GitLab Pages GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. @@ -95,16 +93,6 @@ access it under `http(s)://username.example.io`. Keep reading to find out how. GitLab Pages for projects -### Enable the pages feature in your project - -The GitLab Pages feature needs to be explicitly enabled for each project -under its **Settings**. - -### Remove the contents of your pages - -Pages can be explicitly removed from a project by clicking **Remove Pages** -Go to your project's **Settings > Pages**. - ### Explore the contents of .gitlab-ci.yml Before reading this section, make sure you familiarize yourself with GitLab CI @@ -165,6 +153,11 @@ pages: - master ``` +### Remove the contents of your pages + +Pages can be explicitly removed from a project by clicking **Remove Pages** +Go to your project's **Settings > Pages**. + ## Next steps ### Adding a custom domain to your Pages website -- cgit v1.2.1 From edc8782e85bfde761b4f2af52d62889176295703 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 14:10:51 +0200 Subject: Split user and group sections --- doc/pages/README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 17063ef3ba0..7337120298f 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -19,7 +19,8 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) + - [GitLab pages per user](#gitlab-pages-per-user) + - [GitLab pages per group](#gitlab-pages-per-group) - [GitLab pages per project](#gitlab-pages-per-project) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) @@ -68,7 +69,7 @@ as namespaces. There can be only one namespace in a GitLab instance. > There are some known [limitations](#limitations) regarding namespaces served > under the general domain name and HTTPS. Make sure to read that section. -### GitLab pages per user or group +### GitLab pages per user Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -79,19 +80,25 @@ username, it won’t work, so make sure to get it right. --- -To create a group page the steps are exactly the same. Just make sure that -you are creating the project within the group's namespace. - After you upload some static content to your repository, you will be able to access it under `http(s)://username.example.io`. Keep reading to find out how. +### GitLab pages per group + +To create a group page the steps are the same like when creating a website for +users. Just make sure that you are creating the project within the group's +namespace. + +After you upload some static content to your repository, you will be able to +access it under `http(s)://groupname.example.io`. + ### GitLab pages per project > **Note:** > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. -GitLab Pages for projects + ### Explore the contents of .gitlab-ci.yml -- cgit v1.2.1 From bcf891719f89620544ab8f4ea7d91748fa277251 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 14:13:44 +0200 Subject: Convert CI quick start guide into a note [ci skip] --- doc/pages/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 7337120298f..f63cfb3cda2 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -102,11 +102,10 @@ access it under `http(s)://groupname.example.io`. ### Explore the contents of .gitlab-ci.yml -Before reading this section, make sure you familiarize yourself with GitLab CI -and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by -following our [quick start guide](../ci/quick_start/README.md). - ---- +> **Note:** +> Before reading this section, make sure you familiarize yourself with GitLab CI +> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +> following our [quick start guide](../ci/quick_start/README.md). To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: -- cgit v1.2.1 From dbfde1d06f984ff37c5c953f914acf8d2ffe1c63 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:12 +0200 Subject: Add requirements section --- doc/pages/README.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f63cfb3cda2..42df9c79f22 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -19,6 +19,7 @@ deploy static pages for your individual projects, your user or your group. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) + - [GitLab Pages requirements](#gitlab-pages-requirements) - [GitLab pages per user](#gitlab-pages-per-user) - [GitLab pages per group](#gitlab-pages-per-group) - [GitLab pages per project](#gitlab-pages-per-project) @@ -36,39 +37,40 @@ deploy static pages for your individual projects, your user or your group. ## Getting started with GitLab Pages -GitLab Pages rely heavily on GitLab CI and its ability to upload artifacts. -The steps that are performed from the initialization of a project to the -creation of the static content, can be summed up to: - -1. Find out the general domain name that is used for GitLab Pages - (ask your administrator). This is very important, so you should first make - sure you get that right. -1. Create a project (its name should be specific according to the case) -1. Provide a specific job named `pages` in `.gitlab-ci.yml` -1. GitLab Runner builds the project -1. GitLab CI uploads the artifacts -1. The [GitLab Pages daemon][pages-daemon] serves the content - -As a user, you should normally be concerned only with the first three or four -items. If [shared runners](../ci/runners/README.md) are enabled by your GitLab -administrator, you should be able to use them instead of bringing your own. - -> **Note:** -> In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. +GitLab Pages rely heavily on GitLab CI and its ability to upload +[artifacts](../ci/yaml/README.md#artifacts). In general there are two kinds of pages one might create: -- Pages per user/group -- Pages per project +- Pages per user/group (`username.example.io`) +- Pages per project (`username.example.io/projectname`) In GitLab, usernames and groupnames are unique and often people refer to them as namespaces. There can be only one namespace in a GitLab instance. +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + > **Warning:** > There are some known [limitations](#limitations) regarding namespaces served > under the general domain name and HTTPS. Make sure to read that section. +### GitLab Pages requirements + +In brief, this is what you need to upload your website in GitLab Pages: + +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project +1. Provide a specific job named [`pages`](../ci/yaml/README.md#pages) in + [`.gitlab-ci.yml`](../ci/yaml/README.md) +1. A GitLab Runner to build GitLab Pages + +If [shared runners](../ci/runners/README.md) are enabled by your GitLab +administrator, you should be able to use them instead of bringing your own. + ### GitLab pages per user Head over your GitLab instance that supports GitLab Pages and create a -- cgit v1.2.1 From 34d75cff99d32f7f3a5e53fc47292328aa7ac1de Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:38 +0200 Subject: Rephrase --- doc/pages/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 42df9c79f22..4413a327246 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -82,8 +82,9 @@ username, it won’t work, so make sure to get it right. --- -After you upload some static content to your repository, you will be able to -access it under `http(s)://username.example.io`. Keep reading to find out how. +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. ### GitLab pages per group -- cgit v1.2.1 From 8f7eb4e3d04edc415af7a1dc58e96de366823c19 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:12:47 +0200 Subject: Add image for pages removal --- doc/pages/README.md | 4 +++- doc/pages/img/pages_remove.png | Bin 0 -> 19232 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 doc/pages/img/pages_remove.png diff --git a/doc/pages/README.md b/doc/pages/README.md index 4413a327246..6f1d2d27554 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -165,7 +165,9 @@ pages: ### Remove the contents of your pages Pages can be explicitly removed from a project by clicking **Remove Pages** -Go to your project's **Settings > Pages**. +in your project's **Settings > Pages**. + +![Remove pages](img/pages_remove.png) ## Next steps diff --git a/doc/pages/img/pages_remove.png b/doc/pages/img/pages_remove.png new file mode 100644 index 00000000000..36141bd4d12 Binary files /dev/null and b/doc/pages/img/pages_remove.png differ -- cgit v1.2.1 From c9b63ee44ccedbbd1fd8b1e7da57442a25dd2e2c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:26:34 +0200 Subject: Add pages job in yaml document --- doc/ci/yaml/README.md | 29 +++++++++++++++++++++++++++++ doc/pages/README.md | 5 +++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index f11257be5c3..ca293d54cb6 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1281,6 +1281,35 @@ with an API call. [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 Each instance of GitLab CI has an embedded debug tool called Lint. diff --git a/doc/pages/README.md b/doc/pages/README.md index 6f1d2d27554..c44d48e9cbf 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -64,7 +64,7 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Provide a specific job named [`pages`](../ci/yaml/README.md#pages) in +1. Provide a specific job named [`pages`][pages] in [`.gitlab-ci.yml`](../ci/yaml/README.md) 1. A GitLab Runner to build GitLab Pages @@ -112,7 +112,7 @@ access it under `http(s)://groupname.example.io`. To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: -1. A special `pages` job must be defined +1. A special [`pages`][pages] job must be defined 1. Any static content must be placed under a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined @@ -222,3 +222,4 @@ to private, internal or public. [pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages [gitlab ci]: https://about.gitlab.com/gitlab-ci [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner +[pages]: ../ci/yaml/README.md#pages -- cgit v1.2.1 From 5009acc04de0daf78472df5ec7061aac53bd6576 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 16:28:08 +0200 Subject: Convert shared runners into a note --- doc/pages/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index c44d48e9cbf..3cb560f0150 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -68,8 +68,9 @@ In brief, this is what you need to upload your website in GitLab Pages: [`.gitlab-ci.yml`](../ci/yaml/README.md) 1. A GitLab Runner to build GitLab Pages -If [shared runners](../ci/runners/README.md) are enabled by your GitLab -administrator, you should be able to use them instead of bringing your own. +> **Note:** +> If [shared runners](../ci/runners/README.md) are enabled by your GitLab +> administrator, you should be able to use them instead of bringing your own. ### GitLab pages per user -- cgit v1.2.1 From 69854d9786c5080c32a982f0dda5d29fe9f92f46 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:14:28 +0200 Subject: Merge user and group pages --- doc/pages/README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 3cb560f0150..78100fa452b 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -20,8 +20,7 @@ deploy static pages for your individual projects, your user or your group. - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab Pages requirements](#gitlab-pages-requirements) - - [GitLab pages per user](#gitlab-pages-per-user) - - [GitLab pages per group](#gitlab-pages-per-group) + - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - [GitLab pages per project](#gitlab-pages-per-project) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) @@ -72,7 +71,7 @@ In brief, this is what you need to upload your website in GitLab Pages: > If [shared runners](../ci/runners/README.md) are enabled by your GitLab > administrator, you should be able to use them instead of bringing your own. -### GitLab pages per user +### GitLab pages per user or group Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -83,18 +82,13 @@ username, it won’t work, so make sure to get it right. --- -After you push some static content to your repository and GitLab Runner uploads -the artifacts to GitLab CI, you will be able to access your website under -`http(s)://username.example.io`. Keep reading to find out how. - -### GitLab pages per group - -To create a group page the steps are the same like when creating a website for +To create a group page, the steps are the same like when creating a website for users. Just make sure that you are creating the project within the group's namespace. -After you upload some static content to your repository, you will be able to -access it under `http(s)://groupname.example.io`. +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. ### GitLab pages per project -- cgit v1.2.1 From 7e7da5b2cbce6f4edab40f92d664fc4030314121 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:14:50 +0200 Subject: Add table explaining the types of pages [ci skip] --- doc/pages/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 78100fa452b..e36b651b9b3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -36,10 +36,14 @@ deploy static pages for your individual projects, your user or your group. ## Getting started with GitLab Pages +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + GitLab Pages rely heavily on GitLab CI and its ability to upload [artifacts](../ci/yaml/README.md#artifacts). -In general there are two kinds of pages one might create: +In general there are two types of pages one might create: - Pages per user/group (`username.example.io`) - Pages per project (`username.example.io/projectname`) @@ -47,9 +51,12 @@ In general there are two kinds of pages one might create: In GitLab, usernames and groupnames are unique and often people refer to them as namespaces. There can be only one namespace in a GitLab instance. -> **Note:** -> In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. +| Type of GitLab Pages | Project name | Website served under | +| -------------------- | --------------- | -------------------- | +| User pages | `username.example.io` | `http(s)://username.example.io` | +| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | +| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | +| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| > **Warning:** > There are some known [limitations](#limitations) regarding namespaces served -- cgit v1.2.1 From ea8ff3922cec940024b03d7b3f87d07e8a02695f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 24 Feb 2016 18:19:17 +0200 Subject: Rename user/project pages headings --- doc/pages/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e36b651b9b3..ea46003cb3b 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -20,8 +20,8 @@ deploy static pages for your individual projects, your user or your group. - [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - [GitLab Pages requirements](#gitlab-pages-requirements) - - [GitLab pages per user or group](#gitlab-pages-per-user-or-group) - - [GitLab pages per project](#gitlab-pages-per-project) + - [User or group Pages](#user-or-group-pages) + - [Project Pages](#project-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) @@ -78,7 +78,7 @@ In brief, this is what you need to upload your website in GitLab Pages: > If [shared runners](../ci/runners/README.md) are enabled by your GitLab > administrator, you should be able to use them instead of bringing your own. -### GitLab pages per user or group +### User or group Pages Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on @@ -97,14 +97,13 @@ After you push some static content to your repository and GitLab Runner uploads the artifacts to GitLab CI, you will be able to access your website under `http(s)://username.example.io`. Keep reading to find out how. -### GitLab pages per project +### Project Pages > **Note:** > You do _not_ have to create a project named `username.example.io` in order to > serve a project's page. - ### Explore the contents of .gitlab-ci.yml > **Note:** -- cgit v1.2.1 From d1fe6a6f840ea07304d0da0b8a8477c7946dd1d3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:12:10 +0200 Subject: Mention the power of CI and that Pages support all static generators --- doc/pages/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index ea46003cb3b..dadce43f724 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,6 +12,12 @@ With GitLab Pages you can host for free 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. +The key thing about GitLab Pages is the [`.gitlab-ci.yml`](../ci/yaml/README.md) +file, something that gives you absolute control over the build process. You can +actually watch your website being built live by following the CI build traces. + +GitLab Pages support any kind of [static site generator][staticgen]. + --- @@ -224,3 +230,4 @@ to private, internal or public. [gitlab ci]: https://about.gitlab.com/gitlab-ci [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages +[staticgen]: https://www.staticgen.com/ -- cgit v1.2.1 From 758f5599bd0b4f4a624d1da247063d1384cd395f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:13:26 +0200 Subject: Move Pages removal to Next steps section --- doc/pages/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index dadce43f724..f0d2ecb995d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -29,12 +29,12 @@ GitLab Pages support any kind of [static site generator][staticgen]. - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Next steps](#next-steps) - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) + - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) @@ -197,6 +197,13 @@ You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the `public/` directory that will be included in the artifacts. +### Remove the contents of your pages + +If you ever feel the need to purge your Pages content, you can do so by going +to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. + +![Remove pages](img/pages_remove.png) + ## Limitations -- cgit v1.2.1 From fa374abd9830fc2f21ee740c21f49d72179e9602 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:14:13 +0200 Subject: Add note to use gitlab.io when using GitLab.com --- doc/pages/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f0d2ecb995d..ccad79fb026 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -44,10 +44,8 @@ GitLab Pages support any kind of [static site generator][staticgen]. > **Note:** > In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. - -GitLab Pages rely heavily on GitLab CI and its ability to upload -[artifacts](../ci/yaml/README.md#artifacts). +> is used for GitLab Pages is `example.io`. If you are using GitLab.com to +> host your website, replace `example.io` with `gitlab.io`. In general there are two types of pages one might create: -- cgit v1.2.1 From 3d91145cd0db52000eab67df7db0afec705b7f40 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:16:16 +0200 Subject: Be more precise who people are --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index ccad79fb026..db445f10088 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -52,7 +52,7 @@ In general there are two types of pages one might create: - Pages per user/group (`username.example.io`) - Pages per project (`username.example.io/projectname`) -In GitLab, usernames and groupnames are unique and often people refer to them +In GitLab, usernames and groupnames are unique and we often refer to them as namespaces. There can be only one namespace in a GitLab instance. | Type of GitLab Pages | Project name | Website served under | -- cgit v1.2.1 From a3aef3551a7d35241da51ec59060e83434da982b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:16:44 +0200 Subject: Be more clear what project name is referred to --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index db445f10088..a5c9b9c23c3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -55,8 +55,8 @@ In general there are two types of pages one might create: In GitLab, usernames and groupnames are unique and we often refer to them as namespaces. There can be only one namespace in a GitLab instance. -| Type of GitLab Pages | Project name | Website served under | -| -------------------- | --------------- | -------------------- | +| Type of GitLab Pages | Project name created in GitLab | Website URL | +| -------------------- | ------------ | ----------- | | User pages | `username.example.io` | `http(s)://username.example.io` | | Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | | Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | -- cgit v1.2.1 From 18478e5d4fedf80ce8b62acf3c1a765b3eaa127d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Feb 2016 10:19:09 +0200 Subject: Reword --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index a5c9b9c23c3..d6f8d28fa7c 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -74,8 +74,8 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Provide a specific job named [`pages`][pages] in - [`.gitlab-ci.yml`](../ci/yaml/README.md) +1. Push a [`.gitlab-ci.yml`](../ci/yaml/README.md) file in your repository with + a specific job named [`pages`][pages] 1. A GitLab Runner to build GitLab Pages > **Note:** -- cgit v1.2.1 From e7b2784c956a7b6698ec23d6c5a62f4a83bc2fbd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:09:25 +0200 Subject: Add new sections and clean-up - Finish user/group/project sections - Give more .gitlab-ci.yml examples [ci skip] --- doc/pages/README.md | 221 ++++++++++++++++++++++-------- doc/pages/img/create_user_page.png | Bin 66593 -> 0 bytes doc/pages/img/pages_create_project.png | Bin 0 -> 33597 bytes doc/pages/img/pages_create_user_page.png | Bin 0 -> 87071 bytes doc/pages/img/pages_new_domain_button.png | Bin 0 -> 51136 bytes doc/pages/img/pages_remove.png | Bin 19232 -> 27259 bytes 6 files changed, 165 insertions(+), 56 deletions(-) delete mode 100644 doc/pages/img/create_user_page.png create mode 100644 doc/pages/img/pages_create_project.png create mode 100644 doc/pages/img/pages_create_user_page.png create mode 100644 doc/pages/img/pages_new_domain_button.png diff --git a/doc/pages/README.md b/doc/pages/README.md index d6f8d28fa7c..8ca80b2f0bc 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,12 +12,6 @@ With GitLab Pages you can host for free 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. -The key thing about GitLab Pages is the [`.gitlab-ci.yml`](../ci/yaml/README.md) -file, something that gives you absolute control over the build process. You can -actually watch your website being built live by following the CI build traces. - -GitLab Pages support any kind of [static site generator][staticgen]. - --- @@ -28,15 +22,22 @@ GitLab Pages support any kind of [static site generator][staticgen]. - [GitLab Pages requirements](#gitlab-pages-requirements) - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - - [Explore the contents of .gitlab-ci.yml](#explore-the-contents-of-gitlab-ci-yml) + - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) + - [How `.gitlab-ci.yml` looks like when using plain HTML files](#how-gitlab-ciyml-looks-like-when-using-plain-html-files) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) - - [Adding a custom domain to your Pages website](#adding-a-custom-domain-to-your-pages-website) - - [Securing your custom domain website with TLS](#securing-your-custom-domain-website-with-tls) - - [Example projects](#example-projects) + - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) + - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) + - [Use a static generator to develop your website](#use-a-static-generator-to-develop-your-website) + - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) + - [Can I download my generated pages?](#can-i-download-my-generated-pages) + - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) + - [Q: Do I have to create a project named `username.example.io` in order to host a project website?](#q-do-i-have-to-create-a-project-named-username-example-io-in-order-to-host-a-project-website) @@ -49,13 +50,16 @@ GitLab Pages support any kind of [static site generator][staticgen]. In general there are two types of pages one might create: -- Pages per user/group (`username.example.io`) -- Pages per project (`username.example.io/projectname`) +- Pages per user (`username.example.io`) or per group (`groupname.example.io`) +- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) In GitLab, usernames and groupnames are unique and we often refer to them -as namespaces. There can be only one namespace in a GitLab instance. +as namespaces. There can be only one namespace in a GitLab instance. Below you +can see the connection between the type of GitLab Pages, what the project name +that is created on GitLab looks like and the website URL it will be ultimately +be served on. -| Type of GitLab Pages | Project name created in GitLab | Website URL | +| Type of GitLab Pages | The name of the project created in GitLab | Website URL | | -------------------- | ------------ | ----------- | | User pages | `username.example.io` | `http(s)://username.example.io` | | Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | @@ -74,9 +78,9 @@ In brief, this is what you need to upload your website in GitLab Pages: (ask your administrator). This is very important, so you should first make sure you get that right. 1. Create a project -1. Push a [`.gitlab-ci.yml`](../ci/yaml/README.md) file in your repository with - a specific job named [`pages`][pages] -1. A GitLab Runner to build GitLab Pages +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website > **Note:** > If [shared runners](../ci/runners/README.md) are enabled by your GitLab @@ -84,63 +88,91 @@ In brief, this is what you need to upload your website in GitLab Pages: ### User or group Pages +For user and group pages, the name of the project should be specific to the +username or groupname and the general domain name that is used for GitLab Pages. Head over your GitLab instance that supports GitLab Pages and create a repository named `username.example.io`, where `username` is your username on GitLab. If the first part of the project name doesn't match exactly your username, it won’t work, so make sure to get it right. -![Create a user-based pages repository](img/create_user_page.png) - ---- - To create a group page, the steps are the same like when creating a website for users. Just make sure that you are creating the project within the group's namespace. +![Create a user-based pages project](img/pages_create_user_page.png) + +--- + After you push some static content to your repository and GitLab Runner uploads the artifacts to GitLab CI, you will be able to access your website under `http(s)://username.example.io`. Keep reading to find out how. +>**Note:** +If your username/groupname contains a dot, for example `foo.bar`, you will not +be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). + ### Project Pages -> **Note:** -> You do _not_ have to create a project named `username.example.io` in order to -> serve a project's page. +GitLab Pages for projects can be created by both user and group accounts. +The steps to create a project page for a user or a group are identical: + +1. Create a new project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website + +A user's project will be served under `http(s)://username.example.io/projectname` +whereas a group's project under `http(s)://groupname.example.io/projectname`. +### Explore the contents of `.gitlab-ci.yml` -### Explore the contents of .gitlab-ci.yml +The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that +gives you absolute control over the build process. You can actually watch your +website being built live by following the CI build traces. > **Note:** > Before reading this section, make sure you familiarize yourself with GitLab CI > and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by > following our [quick start guide](../ci/quick_start/README.md). -To make use of GitLab Pages, your `.gitlab-ci.yml` must follow the rules below: +To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the +rules below: -1. A special [`pages`][pages] job must be defined -1. Any static content must be placed under a `public/` directory +1. A special job named [`pages`][pages] must be defined +1. Any static content which will be served by GitLab Pages must be placed under + a `public/` directory 1. `artifacts` with a path to the `public/` directory must be defined +In its simplest form, `.gitlab-ci.yml` looks like: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public +``` + +When the Runner reaches to build the `pages` job, it executes whatever is +defined in the `script` parameter and if the build completes with a non-zero +exit status, it then uploads the `public/` directory to GitLab Pages. + +The `public/` directory should contain all the static content of your website. +Depending on how you plan to publish your website, the steps defined in the +[`script` parameter](../ci/yaml/README.md#script) may differ. + Be aware that Pages are by default branch/tag agnostic and their deployment relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), whenever a new commit is pushed to whatever branch or tag, the Pages will be -overwritten. In the examples below, we limit the Pages to be deployed whenever -a commit is pushed only on the `master` branch, which is advisable to do so. - -The pages are created after the build completes successfully and the artifacts -for the `pages` job are uploaded to GitLab. - -The example below uses [Jekyll][] and generates the created HTML files -under the `public/` directory. +overwritten. In the example below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch: ```yaml -image: ruby:2.1 - pages: script: - - gem install jekyll - - jekyll build -d public/ + - my_commands artifacts: paths: - public @@ -148,14 +180,28 @@ pages: - master ``` -The example below doesn't use any static site generator, but 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. +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. And since all these parameters were all under a `pages` +job, the contents of the `public` directory will be served by GitLab Pages. + +#### How `.gitlab-ci.yml` looks like when the static content is in your repository + +Supposedly your repository contained the following files: + +``` +├── index.html +├── css +│   └── main.css +└── js + └── main.js +``` + +Then the `.gitlab-ci.yml` example below simply moves all files from the root +directory of the project to the `public/` directory. The `.public` workaround +is so `cp` doesn't also copy `public/` to itself in an infinite loop: ```yaml pages: - stage: deploy script: - mkdir .public - cp -r * .public @@ -167,27 +213,84 @@ pages: - master ``` -### Remove the contents of your pages +### How `.gitlab-ci.yml` looks like when using a static generator -Pages can be explicitly removed from a project by clicking **Remove Pages** -in your project's **Settings > Pages**. +In general, GitLab Pages support any kind of [static site generator][staticgen], +since the Runner can be configured to run any possible command. -![Remove pages](img/pages_remove.png) +In the root directory of your Git repository, place the source files of your +favorite static generator. Then provide a `.gitlab-ci.yml` file which is +specific to your static generator. + +The example below, uses [Jekyll] to build the static site: + +```yaml +pages: + images: jekyll/jekyll:latest + script: + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - master +``` + +Here, we used the Docker executor and in the first line we specified the base +image against which our builds will run. + +You have to make sure that the generated static files are ultimately placed +under the `public` directory, that's why in the `script` section we run the +`jekyll` command that builds the website and puts all content in the `public/` +directory. + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. + +--- + +See the [jekyll example project][pages-jekyll] to better understand how this +works. + +For a list of Pages projects, see [example projects](#example-projects) to get +you started. + +#### How to set up GitLab Pages in a repository where there's also actual code + +You can have your project's code in the `master` branch and use an orphan +`pages` branch that will host your static generator site. ## Next steps -### Adding a custom domain to your Pages website +### Add a custom domain to your Pages website +If this setting is enabled by your GitLab administrator, you should be able to +see the **New Domain** button when visiting your project's **Settings > Pages**. -### Securing your custom domain website with TLS +![New domain button](img/pages_new_domain_button.png) -### Example projects +--- + +You are not limited to one domain per can add multiple domains pointing to your +website hosted under GitLab. + +### Secure your custom domain website with TLS + +### Use a static generator to develop your website + +#### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. - [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) - [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) +- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) +- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) +- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) +- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) +- [Harp](https://gitlab.com/gitlab-examples/pages-harp) ### Custom error codes pages @@ -216,16 +319,21 @@ don't redirect HTTP to HTTPS. ## Frequently Asked Questions -**Q: Can I download my generated pages?** +### Can I download my generated pages? Sure. All you need to do is download the artifacts archive from the build page. +### Can I use GitLab Pages if my project is private? -**Q: Can I use GitLab Pages if my project is private?** - -Yes. GitLab Pages doesn't care whether you set your project's visibility level +Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. +### Q: Do I have to create a project named `username.example.io` in order to host a project website? + +No. You can create a new project named `foo` and have it served under +`http(s)://username.example.io/foo` without having previously created a +user page. + --- [jekyll]: http://jekyllrb.com/ @@ -236,3 +344,4 @@ to private, internal or public. [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ +[pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll diff --git a/doc/pages/img/create_user_page.png b/doc/pages/img/create_user_page.png deleted file mode 100644 index 8c2b67e233a..00000000000 Binary files a/doc/pages/img/create_user_page.png and /dev/null differ diff --git a/doc/pages/img/pages_create_project.png b/doc/pages/img/pages_create_project.png new file mode 100644 index 00000000000..a936d8e5dbd Binary files /dev/null and b/doc/pages/img/pages_create_project.png differ diff --git a/doc/pages/img/pages_create_user_page.png b/doc/pages/img/pages_create_user_page.png new file mode 100644 index 00000000000..3f615d3757d Binary files /dev/null and b/doc/pages/img/pages_create_user_page.png differ diff --git a/doc/pages/img/pages_new_domain_button.png b/doc/pages/img/pages_new_domain_button.png new file mode 100644 index 00000000000..c3640133bb2 Binary files /dev/null and b/doc/pages/img/pages_new_domain_button.png differ diff --git a/doc/pages/img/pages_remove.png b/doc/pages/img/pages_remove.png index 36141bd4d12..adbfb654877 100644 Binary files a/doc/pages/img/pages_remove.png and b/doc/pages/img/pages_remove.png differ -- cgit v1.2.1 From 52dcde272a7766dd22a045fe7ca94e3690a9590e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:56:05 +0200 Subject: Move examples to own section --- doc/pages/README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 8ca80b2f0bc..f614781e2ba 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -23,14 +23,13 @@ deploy static pages for your individual projects, your user or your group. - [User or group Pages](#user-or-group-pages) - [Project Pages](#project-pages) - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - - [How `.gitlab-ci.yml` looks like when using plain HTML files](#how-gitlab-ciyml-looks-like-when-using-plain-html-files) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) + - [Example projects](#example-projects) - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - - [Use a static generator to develop your website](#use-a-static-generator-to-develop-your-website) - - [Example projects](#example-projects) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [Limitations](#limitations) @@ -262,6 +261,20 @@ You can have your project's code in the `master` branch and use an orphan ## Next steps +### Example projects + +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. + +- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) +- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) +- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) +- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) +- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) +- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) +- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) +- [Harp](https://gitlab.com/gitlab-examples/pages-harp) + ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to @@ -305,7 +318,6 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. ![Remove pages](img/pages_remove.png) - ## Limitations When using Pages under the general domain of a GitLab instance (`*.example.io`), -- cgit v1.2.1 From 50d32d6cc6cf8643c1676f6241a125623a5af06e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 16:56:23 +0200 Subject: Add examples to 404 pages [ci skip] --- doc/pages/README.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index f614781e2ba..dc425f6a564 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -289,27 +289,22 @@ website hosted under GitLab. ### Secure your custom domain website with TLS -### Use a static generator to develop your website - -#### Example projects - -Below is a list of example projects for GitLab Pages with a plain HTML website -or various static site generators. Contributions are very welcome. - -- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) -- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) -- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) -- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) -- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) -- [Harp](https://gitlab.com/gitlab-examples/pages-harp) ### Custom error codes pages You can provide your own 403 and 404 error pages by creating the `403.html` and -`404.html` files respectively in the `public/` directory that will be included -in the artifacts. +`404.html` files respectively in the root directory of the `public/` directory +that will be included in the artifacts. + +If the case of `404.html`, there are different scenarios. For example: + +- If you use project Pages (served under `/projectname/`) and try to access + `/projectname/non/exsiting_file`, GitLab Pages will try to serve first + `/projectname/404.html`, and then `/404.html`. +- If you use user/group Pages (served under `/`) and try to access + `/non/existing_file` GitLab Pages will try to serve `/404.html`. +- If you use a custom domain and try to access `/non/existing_file`, GitLab + Pages will try to server only `/404.html`. ### Remove the contents of your pages -- cgit v1.2.1 From 231e3a2b13ef4b4cae59c1c3828a8a4c0dd28239 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 18:07:00 +0200 Subject: Add example of hosting Pages in a specific branch [ci skip] --- doc/pages/README.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index dc425f6a564..effa2a35938 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -256,8 +256,46 @@ you started. #### How to set up GitLab Pages in a repository where there's also actual code -You can have your project's code in the `master` branch and use an orphan -`pages` branch that will host your static generator site. +Remember that GitLab Pages are by default branch/tag agnostic and their +deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit +the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to a branch that will be used specifically for +your pages. + +That way, you can have your project's code in the `master` branch and use an +orphan branch (let's name it `pages`) that will host your static generator site. + +You can create a new empty branch like this: + +```bash +git checkout --orphan pages +``` + +The first commit made on this new branch will have no parents and it will be +the root of a new history totally disconnected from all the other branches and +commits. Push the source files of your static generator in the `pages` branch. + +Below is a copy of `.gitlab-ci.yml` where the most significant line is the last +one, specifying to execute everything in the `pages` branch: + +``` +pages: + images: jekyll/jekyll:latest + script: + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - pages +``` + +See an example that has different files in the [`master` branch][jekyll-master] +and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which +also includes `.gitlab-ci.yml`. + +[jekyll-master]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/pages ## Next steps -- cgit v1.2.1 From 8442e3f05dfd822630934f6d3f7b4c478b78e0f5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 4 Mar 2016 18:43:21 +0200 Subject: Add sections on custom domains [ci skip] --- doc/pages/README.md | 30 ++++++++++++++++++++++++++---- doc/pages/img/pages_dns_details.png | Bin 0 -> 34686 bytes doc/pages/img/pages_multiple_domains.png | Bin 0 -> 63716 bytes doc/pages/img/pages_upload_cert.png | Bin 0 -> 103730 bytes 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 doc/pages/img/pages_dns_details.png create mode 100644 doc/pages/img/pages_multiple_domains.png create mode 100644 doc/pages/img/pages_upload_cert.png diff --git a/doc/pages/README.md b/doc/pages/README.md index effa2a35938..080570cdaff 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -24,7 +24,7 @@ deploy static pages for your individual projects, your user or your group. - [Project Pages](#project-pages) - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) + - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) - [Next steps](#next-steps) - [Example projects](#example-projects) @@ -212,7 +212,7 @@ pages: - master ``` -### How `.gitlab-ci.yml` looks like when using a static generator +#### How `.gitlab-ci.yml` looks like when using a static generator In general, GitLab Pages support any kind of [static site generator][staticgen], since the Runner can be configured to run any possible command. @@ -299,6 +299,9 @@ also includes `.gitlab-ci.yml`. ## Next steps +So you have successfully deployed your website, congratulations! Let's check +what more you can do with GitLab Pages. + ### Example projects Below is a list of example projects for GitLab Pages with a plain HTML website @@ -313,6 +316,9 @@ or various static site generators. Contributions are very welcome. - [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) - [Harp](https://gitlab.com/gitlab-examples/pages-harp) +Visit the gitlab-examples group for a full list of projects: +. + ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to @@ -322,11 +328,27 @@ see the **New Domain** button when visiting your project's **Settings > Pages**. --- -You are not limited to one domain per can add multiple domains pointing to your -website hosted under GitLab. +You can add multiple domains pointing to your website hosted under GitLab. +Once the domain is added, you can see it listed under the **Domains** section. + +![Pages multiple domains](img/pages_multiple_domains.png) + +--- + +As a last step, you need to configure your DNS and add a CNAME pointing to your +user/group page. Click on the **Details** button of a domain for further +instructions. + +![Pages DNS details](img/pages_dns_details.png) ### Secure your custom domain website with TLS +When you add a new custom domain, you also have the chance to add a TLS +certificate. If this setting is enabled by your GitLab administrator, you +should be able to see the option to upload the public certificate and the +private key when adding a new domain. + +![Pages upload cert](img/pages_upload_cert.png) ### Custom error codes pages diff --git a/doc/pages/img/pages_dns_details.png b/doc/pages/img/pages_dns_details.png new file mode 100644 index 00000000000..8d34f3b7f38 Binary files /dev/null and b/doc/pages/img/pages_dns_details.png differ diff --git a/doc/pages/img/pages_multiple_domains.png b/doc/pages/img/pages_multiple_domains.png new file mode 100644 index 00000000000..2bc7cee07a6 Binary files /dev/null and b/doc/pages/img/pages_multiple_domains.png differ diff --git a/doc/pages/img/pages_upload_cert.png b/doc/pages/img/pages_upload_cert.png new file mode 100644 index 00000000000..06d85ab1971 Binary files /dev/null and b/doc/pages/img/pages_upload_cert.png differ -- cgit v1.2.1 From aaebb21625575675dc80c185598b5d86776e79ef Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 11:47:01 +0200 Subject: Clarify where 403 and 404 pages should exist --- doc/pages/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 080570cdaff..d9d38c1aa92 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -354,7 +354,9 @@ private key when adding a new domain. You can provide your own 403 and 404 error pages by creating the `403.html` and `404.html` files respectively in the root directory of the `public/` directory -that will be included in the artifacts. +that will be included in the artifacts. Usually this is the root directory of +your project, but that may differ depending on your static generator +configuration. If the case of `404.html`, there are different scenarios. For example: -- cgit v1.2.1 From 0255a85810f09fe6290c8e44a375d89eddd60ac7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:00:40 +0200 Subject: Use the default ruby:2.1 image --- doc/pages/README.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index d9d38c1aa92..cf4e45e17dd 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -224,15 +224,17 @@ specific to your static generator. The example below, uses [Jekyll] to build the static site: ```yaml -pages: - images: jekyll/jekyll:latest +image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 + +pages: # the build job must be named pages script: - - jekyll build -d public/ + - gem install jekyll # we install jekyll + - jekyll build -d public/ # we tell jekyll to build the site for us artifacts: paths: - - public + - public # this is where the site will live and the Runner uploads it in GitLab only: - - master + - master # this script is only affecting the master branch ``` Here, we used the Docker executor and in the first line we specified the base @@ -241,7 +243,11 @@ image against which our builds will run. You have to make sure that the generated static files are ultimately placed under the `public` directory, that's why in the `script` section we run the `jekyll` command that builds the website and puts all content in the `public/` -directory. +directory. Depending on the static generator of your choice, this command will +differ. Search in the documentation of the static generator you will use if +there is an option to explicitly set the output directory. If there is not +such an option, you can always add one more line under `script` to rename the +resulting directory in `public/`. We then tell the Runner to treat the `public/` directory as `artifacts` and upload it to GitLab. @@ -251,8 +257,8 @@ upload it to GitLab. See the [jekyll example project][pages-jekyll] to better understand how this works. -For a list of Pages projects, see [example projects](#example-projects) to get -you started. +For a list of Pages projects, see the [example projects](#example-projects) to +get you started. #### How to set up GitLab Pages in a repository where there's also actual code @@ -279,9 +285,11 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last one, specifying to execute everything in the `pages` branch: ``` +image: ruby:2.1 + pages: - images: jekyll/jekyll:latest script: + - gem install jekyll - jekyll build -d public/ artifacts: paths: -- cgit v1.2.1 From a77a101d312910175dff3d8ebe8ecd1d8da357ee Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:35:10 +0200 Subject: Add separate section for GitLab.com --- doc/pages/README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index cf4e45e17dd..8f4c0bc8081 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,6 +12,9 @@ With GitLab Pages you can host for free 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. +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) if you are +using GitLab.com to host your website. + --- @@ -25,18 +28,19 @@ deploy static pages for your individual projects, your user or your group. - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-there-s-also-actual-code) + - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-theres-also-actual-code) - [Next steps](#next-steps) - [Example projects](#example-projects) - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - [Custom error codes pages](#custom-error-codes-pages) - [Remove the contents of your pages](#remove-the-contents-of-your-pages) +- [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) - [Limitations](#limitations) - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Q: Do I have to create a project named `username.example.io` in order to host a project website?](#q-do-i-have-to-create-a-project-named-username-example-io-in-order-to-host-a-project-website) + - [Do I have to create a project named `username.example.io` in order to host a project website?](#do-i-have-to-create-a-project-named-usernameexampleio-in-order-to-host-a-project-website) @@ -44,8 +48,7 @@ deploy static pages for your individual projects, your user or your group. > **Note:** > In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. If you are using GitLab.com to -> host your website, replace `example.io` with `gitlab.io`. +> is used for GitLab Pages is `example.io`. In general there are two types of pages one might create: @@ -78,7 +81,7 @@ In brief, this is what you need to upload your website in GitLab Pages: sure you get that right. 1. Create a project 1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages]. + of your repository with a specific job named [`pages`][pages] 1. Set up a GitLab Runner to build your website > **Note:** @@ -383,6 +386,17 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. ![Remove pages](img/pages_remove.png) +## GitLab Pages on GitLab.com + +If you are using GitLab.com to host your website, then: + +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io` +- Shared runners are provided for free and can be used to build your website + if you cannot or don't want to set up your own Runner +- Custom domains and TLS support are enabled + +The rest of the guide still applies. + ## Limitations When using Pages under the general domain of a GitLab instance (`*.example.io`), @@ -405,7 +419,7 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. -### Q: Do I have to create a project named `username.example.io` in order to host a project website? +### Do I have to create a project named `username.example.io` in order to host a project website? No. You can create a new project named `foo` and have it served under `http(s)://username.example.io/foo` without having previously created a -- cgit v1.2.1 From 552e8993496fc57d15c5b712806aa87ed974fc6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:36:08 +0200 Subject: Clarification --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 8f4c0bc8081..682359ad266 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -218,7 +218,7 @@ pages: #### How `.gitlab-ci.yml` looks like when using a static generator In general, GitLab Pages support any kind of [static site generator][staticgen], -since the Runner can be configured to run any possible command. +since `.gitlab-ci.yml` can be configured to run any possible command. In the root directory of your Git repository, place the source files of your favorite static generator. Then provide a `.gitlab-ci.yml` file which is -- cgit v1.2.1 From 1fe13d63cded87f06b7ab3edf77945b15d191b91 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 12:46:35 +0200 Subject: Remove confusing FAQ question [ci skip] --- doc/pages/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 682359ad266..e9234e14a17 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -40,7 +40,6 @@ using GitLab.com to host your website. - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Do I have to create a project named `username.example.io` in order to host a project website?](#do-i-have-to-create-a-project-named-usernameexampleio-in-order-to-host-a-project-website) @@ -419,12 +418,6 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. -### Do I have to create a project named `username.example.io` in order to host a project website? - -No. You can create a new project named `foo` and have it served under -`http(s)://username.example.io/foo` without having previously created a -user page. - --- [jekyll]: http://jekyllrb.com/ -- cgit v1.2.1 From d557b3fec51eaa560d4ccbafe344a4ab4b7bb5dd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 13:00:40 +0200 Subject: Fix markdown anchor link [ci skip] --- doc/pages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e9234e14a17..fa31a3ec560 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,8 +12,8 @@ With GitLab Pages you can host for free 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. -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) if you are -using GitLab.com to host your website. +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific +information, if you are using GitLab.com to host your website. --- -- cgit v1.2.1 From 9a8818f87f8bfc34e900b8fa2903a313980cd0fc Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 7 Mar 2016 19:09:42 +0200 Subject: Add FAQ on Pages website type precedence --- doc/pages/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index fa31a3ec560..0f37cce2c8f 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -40,6 +40,7 @@ information, if you are using GitLab.com to host your website. - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) + - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) @@ -418,6 +419,11 @@ Sure. All you need to do is download the artifacts archive from the build page. Yes. GitLab Pages don't care whether you set your project's visibility level to private, internal or public. +### Do I need to create a user/group website before creating a project website? + +No, you don't. You can create your project first and it will be accessed under +`http(s)://namespace.example.io/projectname`. + --- [jekyll]: http://jekyllrb.com/ -- cgit v1.2.1 From 873eacaf8c7e4612a3800a65038e24508558d06c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 8 Mar 2016 16:20:11 +0200 Subject: Mention that shared runners on GitLab.com are enabled by default [ci skip] --- doc/pages/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 0f37cce2c8f..9377135be7d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -390,10 +390,10 @@ to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. If you are using GitLab.com to host your website, then: -- The general domain name for GitLab Pages on GitLab.com is `gitlab.io` -- Shared runners are provided for free and can be used to build your website - if you cannot or don't want to set up your own Runner -- Custom domains and TLS support are enabled +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. +- Custom domains and TLS support are enabled. +- Shared runners are enabled by default, provided for free and can be used to + build your website. If you want you can still bring your own Runner. The rest of the guide still applies. -- cgit v1.2.1 From 2b18f02023496bec4c661a5134f6c6aa5753cae6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 10:51:41 +0200 Subject: Add section on redirects [ci skip] --- doc/pages/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 9377135be7d..7e9184b54d3 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -37,6 +37,7 @@ information, if you are using GitLab.com to host your website. - [Remove the contents of your pages](#remove-the-contents-of-your-pages) - [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) - [Limitations](#limitations) +- [Redirects in GitLab Pages](#redirects-in-gitlab-pages) - [Frequently Asked Questions](#frequently-asked-questions) - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) @@ -408,6 +409,16 @@ don't redirect HTTP to HTTPS. [rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" +## Redirects in GitLab Pages + +Since you cannot use any custom server configuration files, like `.htaccess` or +any `.conf` file for that matter, if you want to redirect a web page to another +location, you can use the [HTTP meta refresh tag][metarefresh]. + +Some static site generators provide plugins for that functionality so that you +don't have to create and edit HTML files manually. For example, Jekyll has the +[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). + ## Frequently Asked Questions ### Can I download my generated pages? @@ -435,3 +446,4 @@ No, you don't. You can create your project first and it will be accessed under [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ [pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll +[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh -- cgit v1.2.1 From 1bdfdd87d81c9415def30ac17518a991b900d095 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 11:00:56 +0200 Subject: Add known issues section --- doc/pages/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 7e9184b54d3..53462f81edb 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -42,6 +42,7 @@ information, if you are using GitLab.com to host your website. - [Can I download my generated pages?](#can-i-download-my-generated-pages) - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) +- [Known issues](#known-issues) @@ -435,6 +436,10 @@ to private, internal or public. No, you don't. You can create your project first and it will be accessed under `http(s)://namespace.example.io/projectname`. +## Known issues + +For a list of known issues, visit GitLab's [public issue tracker]. + --- [jekyll]: http://jekyllrb.com/ @@ -447,3 +452,4 @@ No, you don't. You can create your project first and it will be accessed under [staticgen]: https://www.staticgen.com/ [pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll [metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh +[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages -- cgit v1.2.1 From 8fc3030ab64ca41d506fcc40eefcde64fa59f83e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 9 Mar 2016 11:07:52 +0200 Subject: Add note on custom domains limitation [ci skip] --- doc/pages/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/pages/README.md b/doc/pages/README.md index 53462f81edb..335c37b4814 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -354,6 +354,14 @@ instructions. ![Pages DNS details](img/pages_dns_details.png) +--- + +>**Note:** +Currently there is support only for custom domains on per-project basis. That +means that if you add a custom domain (`example.com`) for your user website +(`username.example.io`), a project that is served under `username.example.io/foo`, +will not be accessible under `example.com/foo`. + ### Secure your custom domain website with TLS When you add a new custom domain, you also have the chance to add a TLS -- cgit v1.2.1 From 8f5e7c36e4e8f60ab34a29da18e499705216c261 Mon Sep 17 00:00:00 2001 From: Heather McNamee Date: Wed, 23 Mar 2016 10:58:28 +0000 Subject: Update links to new pages group. --- doc/pages/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 335c37b4814..938311ebf95 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -307,8 +307,8 @@ See an example that has different files in the [`master` branch][jekyll-master] and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which also includes `.gitlab-ci.yml`. -[jekyll-master]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/master -[jekyll-pages]: https://gitlab.com/gitlab-examples/pages-jekyll-branched/tree/pages +[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages ## Next steps @@ -320,17 +320,17 @@ what more you can do with GitLab Pages. Below is a list of example projects for GitLab Pages with a plain HTML website or various static site generators. Contributions are very welcome. -- [Plain HTML](https://gitlab.com/gitlab-examples/pages-plain-html) -- [Jekyll](https://gitlab.com/gitlab-examples/pages-jekyll) -- [Hugo](https://gitlab.com/gitlab-examples/pages-hugo) -- [Middleman](https://gitlab.com/gitlab-examples/pages-middleman) -- [Hexo](https://gitlab.com/gitlab-examples/pages-hexo) -- [Brunch](https://gitlab.com/gitlab-examples/pages-brunch) -- [Metalsmith](https://gitlab.com/gitlab-examples/pages-metalsmith) -- [Harp](https://gitlab.com/gitlab-examples/pages-harp) +- [Plain HTML](https://gitlab.com/pages/plain-html) +- [Jekyll](https://gitlab.com/pages/jekyll) +- [Hugo](https://gitlab.com/pages/hugo) +- [Middleman](https://gitlab.com/pages/middleman) +- [Hexo](https://gitlab.com/pages/hexo) +- [Brunch](https://gitlab.com/pages/brunch) +- [Metalsmith](https://gitlab.com/pages/metalsmith) +- [Harp](https://gitlab.com/pages/harp) -Visit the gitlab-examples group for a full list of projects: -. +Visit the GitLab Pages group for a full list of example projects: +. ### Add a custom domain to your Pages website @@ -458,6 +458,6 @@ For a list of known issues, visit GitLab's [public issue tracker]. [gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner [pages]: ../ci/yaml/README.md#pages [staticgen]: https://www.staticgen.com/ -[pages-jekyll]: https://gitlab.com/gitlab-examples/pages-jekyll +[pages-jekyll]: https://gitlab.com/pages/jekyll [metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh [public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages -- cgit v1.2.1 From 39f9056dbb45f8aec3b97ecf85ca4b0c00b49534 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 31 Mar 2016 11:54:33 +0200 Subject: Update GitLab Pages to 0.2.1 --- GITLAB_PAGES_VERSION | 2 +- doc/pages/administration.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 0ea3a944b39..0c62199f16a 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.0 +0.2.1 diff --git a/doc/pages/administration.md b/doc/pages/administration.md index d0fdbeafa5b..68b9003a420 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -97,7 +97,7 @@ installing the pages daemon. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages -sudo -u git -H git checkout v0.2.0 +sudo -u git -H git checkout v0.2.1 sudo -u git -H make ``` @@ -505,7 +505,7 @@ latest previous version. - Documentation was moved to one place [8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.0 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 [NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx --- -- cgit v1.2.1 From 2c1eeb5c73d625b0f4481e8a7985d95071e7640c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 5 Apr 2016 18:06:11 +0200 Subject: Update GitLab Pages to 0.2.2 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 0c62199f16a..ee1372d33a2 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.1 +0.2.2 -- cgit v1.2.1 From fd61cd08ceb6d05b6e1bf103c60add02bdd3edbe Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 21 Apr 2016 17:33:08 +0000 Subject: pages: Fix "undefined local variable or method `total_size'" when maximum page size is exceeded --- app/services/projects/update_pages_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index ceabd29fd52..45a28b46d06 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -85,7 +85,7 @@ module Projects public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) if public_entry.total_size > max_size - raise "artifacts for pages are too large: #{total_size}" + raise "artifacts for pages are too large: #{public_entry.total_size}" end # Requires UnZip at least 6.00 Info-ZIP. -- cgit v1.2.1 From b22c06d311525cbb9edc95ab99da26fa9b921395 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 May 2016 09:38:27 -0500 Subject: Bump GitLab Pages to 0.2.4 --- GITLAB_PAGES_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index ee1372d33a2..abd410582de 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.2.2 +0.2.4 -- cgit v1.2.1 From caedc996bdb37718b8397988662835b3c8163e46 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 3 Jun 2016 13:01:54 -0600 Subject: Adds algorithm to the pages domain key and remote mirror credentials encrypted attributes for forward compatibility with attr_encrypted 3.0.0. aes-256-cbc is the default algorithm for attr_encrypted 1.x, but the default is changed in 3.0 and thus must be declared explicitly. See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4216/ for more information. This will prevent OpenSSL errors once the code from that MR is merged into EE. --- app/models/pages_domain.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 9155e57331d..2f4cded15c8 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -10,7 +10,10 @@ class PagesDomain < ActiveRecord::Base 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, key: Gitlab::Application.secrets.db_key_base + attr_encrypted :key, + mode: :per_attribute_iv_and_salt, + key: Gitlab::Application.secrets.db_key_base, + algorithm: 'aes-256-cbc' after_create :update after_save :update -- cgit v1.2.1 From 0168a24064a5748ab0de902a12c0fe9851668fb1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 8 Aug 2016 20:53:31 +0800 Subject: Only show the message if user is not the owner Closes #323 --- app/views/projects/pages/_destroy.haml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 6a7b6baf767..df5add89545 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,11 +1,12 @@ -- if can?(current_user, :remove_pages, @project) && @project.pages_deployed? - .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" +- if can?(current_user, :remove_pages, @project) + - if @project.pages_deployed? + .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 -- cgit v1.2.1 From e6a7eb43b2dda4d47d25b4d7ecc068ba01f5b54d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 18 Aug 2016 23:36:31 +0800 Subject: If no pages were deployed, show nothing. Feedback: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/628#note_14000640 --- app/views/projects/pages/_destroy.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index df5add89545..42d9ef5ccba 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,5 +1,5 @@ -- if can?(current_user, :remove_pages, @project) - - if @project.pages_deployed? +- if @project.pages_deployed? + - if can?(current_user, :remove_pages, @project) .panel.panel-default.panel.panel-danger .panel-heading Remove pages .errors-holder @@ -8,5 +8,5 @@ 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 + - else + .nothing-here-block Only the project owner can remove pages -- cgit v1.2.1 From 0763b5ea4a5e463b6cc0e94ae05ba2e58262cf74 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 19 Aug 2016 00:24:27 +0800 Subject: Add a test for checking pages setting --- spec/features/projects/pages_spec.rb | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 spec/features/projects/pages_spec.rb diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb new file mode 100644 index 00000000000..acd7a1abb9a --- /dev/null +++ b/spec/features/projects/pages_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +feature 'Pages', feature: true do + given(:project) { create(:empty_project) } + given(:user) { create(:user) } + given(:role) { :master } + + background do + 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 -- cgit v1.2.1 From c3e483b05b11e862dfe14104c8ba63cbdb11d953 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Aug 2016 21:47:22 +0800 Subject: Stub to enable it so that we could test this --- spec/features/projects/pages_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index acd7a1abb9a..11793c0f303 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -6,6 +6,8 @@ feature 'Pages', feature: true do given(:role) { :master } background do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + project.team << [user, role] login_as(user) -- cgit v1.2.1 From 109553afd0ba80d47a282eb5e68ce3b5eda1d818 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 7 Jun 2016 16:40:15 +0200 Subject: Fix EE specs after ci_commit rename to pipeline --- app/services/projects/update_pages_service.rb | 2 +- features/steps/project/pages.rb | 12 ++++++------ spec/services/projects/update_pages_service_spec.rb | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 45a28b46d06..f588f6feb8c 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -52,7 +52,7 @@ module Projects def create_status GenericCommitStatus.new( project: project, - commit: build.commit, + pipeline: build.pipeline, user: build.user, ref: build.ref, stage: 'deploy', diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index 34f97f1ea8b..a76672168fb 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -27,13 +27,13 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'pages are deployed' do - commit = @project.ensure_ci_commit(@project.commit('HEAD').sha) + pipeline = @project.ensure_pipeline(@project.commit('HEAD').sha, 'HEAD') build = build(:ci_build, - project: @project, - commit: commit, - 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') + 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) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 51da582c497..4eac7875864 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -2,8 +2,8 @@ require "spec_helper" describe Projects::UpdatePagesService 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(: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) } @@ -47,7 +47,7 @@ describe Projects::UpdatePagesService do end it 'fails if sha on branch is not latest' do - commit.update_attributes(sha: 'old_sha') + pipeline.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) expect(execute).to_not eq(:success) end -- cgit v1.2.1 From 8fd926fe862fdaa5f44c11c2184c7e2734e5e173 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 22:33:39 +0800 Subject: Project#ensure_pipeline changed the args order --- features/steps/project/pages.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb index a76672168fb..c80c6273807 100644 --- a/features/steps/project/pages.rb +++ b/features/steps/project/pages.rb @@ -27,7 +27,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps end step 'pages are deployed' do - pipeline = @project.ensure_pipeline(@project.commit('HEAD').sha, 'HEAD') + pipeline = @project.ensure_pipeline('HEAD', @project.commit('HEAD').sha) build = build(:ci_build, project: @project, pipeline: pipeline, -- cgit v1.2.1 From d8ae09229a7798450263534a486c9d11b087d816 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 19 Nov 2016 19:20:05 +0100 Subject: Remove Pages ToC from docs [ci skip] --- doc/pages/administration.md | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 68b9003a420..2ef46932c13 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -14,39 +14,6 @@ configuration. If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 - ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [The GitLab Pages daemon](#the-gitlab-pages-daemon) - - [The GitLab Pages daemon and the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains) - - [Install the Pages daemon](#install-the-pages-daemon) -- [Configuration](#configuration) - - [Configuration prerequisites](#configuration-prerequisites) - - [Configuration scenarios](#configuration-scenarios) - - [DNS configuration](#dns-configuration) -- [Setting up GitLab Pages](#setting-up-gitlab-pages) - - [Custom domains with HTTPS support](#custom-domains-with-https-support) - - [Custom domains without HTTPS support](#custom-domains-without-https-support) - - [Wildcard HTTP domain without custom domains](#wildcard-http-domain-without-custom-domains) - - [Wildcard HTTPS domain without custom domains](#wildcard-https-domain-without-custom-domains) -- [NGINX configuration](#nginx-configuration) - - [NGINX configuration files](#nginx-configuration-files) - - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - - [NGINX caveats](#nginx-caveats) -- [Set maximum pages size](#set-maximum-pages-size) -- [Change storage path](#change-storage-path) -- [Backup](#backup) -- [Security](#security) -- [Changelog](#changelog) - - - ## The GitLab Pages daemon Starting from GitLab EE 8.5, GitLab Pages make use of the [GitLab Pages daemon], @@ -522,6 +489,8 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md [8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md +[backup]: ../../raketasks/backup_restore.md +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source -[backup]: ../../raketasks/backup_restore.md -- cgit v1.2.1 From baac53652c258241b45dbe77ba99b02f22ef020f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 29 Nov 2016 10:59:40 +0100 Subject: Clarify where the settings are in Pages docs Fixes https://gitlab.com/gitlab-org/gitlab-ee/issues/1333 [ci skip] --- doc/pages/README.md | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 938311ebf95..e70ca7b48bd 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -12,40 +12,9 @@ With GitLab Pages you can host for free 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. -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific information, if you are using GitLab.com to host your website. ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Getting started with GitLab Pages](#getting-started-with-gitlab-pages) - - [GitLab Pages requirements](#gitlab-pages-requirements) - - [User or group Pages](#user-or-group-pages) - - [Project Pages](#project-pages) - - [Explore the contents of `.gitlab-ci.yml`](#explore-the-contents-of-gitlab-ciyml) - - [How `.gitlab-ci.yml` looks like when the static content is in your repository](#how-gitlab-ciyml-looks-like-when-the-static-content-is-in-your-repository) - - [How `.gitlab-ci.yml` looks like when using a static generator](#how-gitlab-ciyml-looks-like-when-using-a-static-generator) - - [How to set up GitLab Pages in a repository where there's also actual code](#how-to-set-up-gitlab-pages-in-a-repository-where-theres-also-actual-code) -- [Next steps](#next-steps) - - [Example projects](#example-projects) - - [Add a custom domain to your Pages website](#add-a-custom-domain-to-your-pages-website) - - [Secure your custom domain website with TLS](#secure-your-custom-domain-website-with-tls) - - [Custom error codes pages](#custom-error-codes-pages) - - [Remove the contents of your pages](#remove-the-contents-of-your-pages) -- [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) -- [Limitations](#limitations) -- [Redirects in GitLab Pages](#redirects-in-gitlab-pages) -- [Frequently Asked Questions](#frequently-asked-questions) - - [Can I download my generated pages?](#can-i-download-my-generated-pages) - - [Can I use GitLab Pages if my project is private?](#can-i-use-gitlab-pages-if-my-project-is-private) - - [Do I need to create a user/group website before creating a project website?](#do-i-need-to-create-a-usergroup-website-before-creating-a-project-website) -- [Known issues](#known-issues) - - - ## Getting started with GitLab Pages > **Note:** @@ -335,7 +304,8 @@ Visit the GitLab Pages group for a full list of example projects: ### Add a custom domain to your Pages website If this setting is enabled by your GitLab administrator, you should be able to -see the **New Domain** button when visiting your project's **Settings > Pages**. +see the **New Domain** button when visiting your project's settings through the +gear icon in the top right and then navigating to **Pages**. ![New domain button](img/pages_new_domain_button.png) @@ -392,7 +362,9 @@ If the case of `404.html`, there are different scenarios. For example: ### Remove the contents of your pages If you ever feel the need to purge your Pages content, you can do so by going -to your project's **Settings > Pages** and hit **Remove pages**. Simple as that. +to your project's settings through the gear icon in the top right, and then +navigating to **Pages**. Hit the **Remove pages** button and your Pages website +will be deleted. Simple as that. ![Remove pages](img/pages_remove.png) -- cgit v1.2.1 From f6c66f4567f6818ec4678dcfb3e543d478f9dc36 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 15 Dec 2016 20:01:55 +0000 Subject: Fix reconfigure link on doc/pages/administration.md --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 2ef46932c13..00100466ce2 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -492,5 +492,5 @@ No new changes. [backup]: ../../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -[reconfigure]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure [restart]: ../../administration/restart_gitlab.md#installations-from-source -- cgit v1.2.1 From 80f9794c3e05362c2b06978d8a664ebf4e0ae0a6 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Sat, 17 Dec 2016 00:52:06 +0000 Subject: Fix restart link on doc/pages/administration.md --- doc/pages/administration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 00100466ce2..07b603f78c7 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -489,8 +489,8 @@ No new changes. [8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md [8-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md -[backup]: ../../raketasks/backup_restore.md +[backup]: ../raketasks/backup_restore.md [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 [reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure -[restart]: ../../administration/restart_gitlab.md#installations-from-source +[restart]: ../administration/restart_gitlab.md#installations-from-source -- cgit v1.2.1 From 86f4767dc1afea9f0744e4fb0c5ce663bf7e3de8 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 23 Dec 2016 00:26:33 +0530 Subject: Fix 500 error while navigating to the `pages_domains` 'show' page. ================== = Implementation = ================== 1. The path of the page is of the form 'group/project/pages/domains/' 2. Rails looks at `params[:id]` (which should be the domain name), and finds the relevant model record. 3. Given a domain like `foo.bar`, Rails sets `params[:id]` to `foo` (should be `foo.bar`), and sets `params[:format]` to `bar` 4. This commit fixes the issue by adding a route constraint, so that `params[:id]` is set to the entire `foo.bar` domain name. ========= = Tests = ========= 1. Add controller specs for the `PagesDomainController`. These are slightly orthogonal to this bug fix (they don't fail when this bug is present), but should be present nonetheless. 2. Add routing specs that catch this bug (by asserting that the `id` param is passed as expected when it contains a domain name). 3. Modify the 'RESTful project resources' routing spec shared example to accomodate controllers where the controller path (such as `pages/domains`) is different from the controller name (such as `pages_domains`). --- config/routes/project.rb | 2 +- .../projects/pages_domains_controller_spec.rb | 64 ++++++++++++++++++++++ spec/routing/project_routing_spec.rb | 37 ++++++++++--- 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 spec/controllers/projects/pages_domains_controller_spec.rb diff --git a/config/routes/project.rb b/config/routes/project.rb index ea3bfdd45e6..b6b432256df 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do end resource :pages, only: [:show, :destroy] do - resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains' + resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ } end resources :compare, only: [:index, :create] do diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb new file mode 100644 index 00000000000..2362df895a8 --- /dev/null +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -0,0 +1,64 @@ +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 diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77549db2927..43be785ad9d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -27,35 +27,42 @@ describe 'project routing' do # let(:actions) { [:index] } # let(:controller) { 'issues' } # 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 let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + let(:controller_path) { controller } 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 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 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 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 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 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 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 @@ -539,4 +546,20 @@ describe 'project routing' do 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') 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 -- cgit v1.2.1 From 66bfc9e9e78257d9e6e232b004f6152440ebe27b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 17 Aug 2016 13:27:19 +0300 Subject: [CE->EE] Fix specs --- spec/services/pages_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index e6ad93358a0..254b4f688cf 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PagesService, services: true do let(:build) { create(:ci_build) } - let(:data) { Gitlab::BuildDataBuilder.build(build) } + let(:data) { Gitlab::DataBuilder::Build.build(build) } let(:service) { PagesService.new(data) } before do -- cgit v1.2.1 From 12d44272ec68e38760d5886b27546e8c13f7942a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 30 May 2016 10:11:46 +0200 Subject: Fix Rubocop offenses --- spec/models/pages_domain_spec.rb | 14 +++++++------- spec/services/pages_service_spec.rb | 4 ++-- spec/services/projects/update_pages_service_spec.rb | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 0b95bf594c5..0cbea5be106 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -23,13 +23,13 @@ describe PagesDomain, models: true do context 'no domain' do let(:domain) { nil } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'invalid domain' do let(:domain) { '0123123' } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end context 'domain from .example.com' do @@ -37,7 +37,7 @@ describe PagesDomain, models: true do before { allow(Settings.pages).to receive(:host).and_return('domain.com') } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end end @@ -47,13 +47,13 @@ describe PagesDomain, models: true do context 'when only certificate is specified' do let(:domain) { build(:pages_domain, :with_certificate) } - it { is_expected.to_not be_valid } + 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.to_not be_valid } + it { is_expected.not_to be_valid } end context 'with matching key' do @@ -65,7 +65,7 @@ describe PagesDomain, models: true do context 'for not matching key' do let(:domain) { build(:pages_domain, :with_missing_chain, :with_key) } - it { is_expected.to_not be_valid } + it { is_expected.not_to be_valid } end end @@ -157,6 +157,6 @@ describe PagesDomain, models: true do subject { domain.certificate_text } # We test only existence of output, since the output is long - it { is_expected.to_not be_empty } + it { is_expected.not_to be_empty } end end diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index 254b4f688cf..b4215b2cd02 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -26,7 +26,7 @@ describe PagesService, services: true do before { build.status = status } it 'should not execute worker' do - expect(PagesWorker).to_not receive(:perform_async) + expect(PagesWorker).not_to receive(:perform_async) service.execute end end @@ -40,7 +40,7 @@ describe PagesService, services: true do end it 'should not execute worker' do - expect(PagesWorker).to_not receive(:perform_async) + expect(PagesWorker).not_to receive(:perform_async) service.execute end end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 4eac7875864..af1c6a5e7b5 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -34,7 +34,7 @@ describe Projects::UpdatePagesService do it 'limits pages size' do stub_application_setting(max_pages_size: 1) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'removes pages after destroy' do @@ -49,29 +49,29 @@ describe Projects::UpdatePagesService do it 'fails if sha on branch is not latest' do pipeline.update_attributes(sha: 'old_sha') build.update_attributes(artifacts_file: file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'fails for empty file fails' do build.update_attributes(artifacts_file: empty_file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end end end it 'fails to remove project pages when no pages is deployed' do - expect(PagesWorker).to_not receive(:perform_in) + 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).to_not eq(:success) + expect(execute).not_to eq(:success) end it 'fails for invalid archive' do build.update_attributes(artifacts_file: invalid_file) - expect(execute).to_not eq(:success) + expect(execute).not_to eq(:success) end def execute -- cgit v1.2.1 From 91c07d16cd371a545a9ad089ccc0313c7a13bb5b Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 13 May 2016 12:51:52 +0200 Subject: Fixed Rubocop deprecation warnings --- app/services/projects/update_pages_service.rb | 2 +- spec/services/projects/update_pages_service_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index f588f6feb8c..90fff91dd9c 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -25,7 +25,7 @@ module Projects # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') - raise 'pages miss the public folder' unless Dir.exists?(archive_public_path) + raise 'pages miss the public folder' unless Dir.exist?(archive_public_path) raise 'pages are outdated' unless latest? deploy_page!(archive_public_path) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index af1c6a5e7b5..411b22a0fb8 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -5,7 +5,7 @@ describe Projects::UpdatePagesService do 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 @@ -18,7 +18,7 @@ describe Projects::UpdatePagesService do 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.exists?(filename) + fixture_file_upload(filename) if File.exist?(filename) end before do @@ -73,7 +73,7 @@ describe Projects::UpdatePagesService do build.update_attributes(artifacts_file: invalid_file) expect(execute).not_to eq(:success) end - + def execute subject.execute[:status] end -- cgit v1.2.1 From 7163da6046c2b57f9e9cf3b83959a57763e2f460 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 13 Aug 2016 11:15:31 +0200 Subject: Fix GitLab Pages test failures --- app/services/projects/update_pages_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 90fff91dd9c..c52f3d3e230 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -13,6 +13,7 @@ module Projects def execute # Create status notifying the deployment of pages @status = create_status + @status.enqueue! @status.run! raise 'missing pages artifacts' unless build.artifacts_file? -- cgit v1.2.1 From 6ba149279445bd376e145dab2d7fa58808031692 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 20 Dec 2016 11:24:44 +0000 Subject: Update validates_hostname to 1.0.6 to fix a bug in parsing hexadecimal-looking domain names --- Gemfile | 2 +- Gemfile.lock | 4 ++-- spec/models/pages_domain_spec.rb | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index bc1b13c7331..49851aabe19 100644 --- a/Gemfile +++ b/Gemfile @@ -49,7 +49,7 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # GitLab Pages -gem 'validates_hostname', '~> 1.0.0' +gem 'validates_hostname', '~> 1.0.6' # Browser detection gem 'browser', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 6263b02b041..5736862e5ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -799,7 +799,7 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - validates_hostname (1.0.5) + validates_hostname (1.0.6) activerecord (>= 3.0) activesupport (>= 3.0) version_sorter (2.1.0) @@ -1017,7 +1017,7 @@ DEPENDENCIES unf (~> 0.1.4) unicorn (~> 5.1.0) unicorn-worker-killer (~> 0.4.4) - validates_hostname (~> 1.0.0) + validates_hostname (~> 1.0.6) version_sorter (~> 2.1.0) virtus (~> 1.0.1) vmstat (~> 2.3.0) diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 0cbea5be106..e6a4583a8fb 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -4,7 +4,7 @@ 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) } @@ -20,6 +20,12 @@ describe PagesDomain, models: true do 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 } -- cgit v1.2.1 From 8a09a25185bb05a9360370f40a8f7ff50279e60b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2016 11:51:00 +0000 Subject: small but mighty confusing typo --- doc/pages/administration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 07b603f78c7..0e1665fa832 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -246,7 +246,7 @@ Below are the four scenarios that are described in 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby - pages_external_url "https://example.io" + 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' -- cgit v1.2.1 From 877c121cfcca1f9bb116ff0a8831c5a9096e2853 Mon Sep 17 00:00:00 2001 From: Rebecca Skinner Date: Mon, 28 Mar 2016 14:15:48 +0000 Subject: Fixed typo ("server" -> "serve") --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index e70ca7b48bd..e427d7f283d 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -357,7 +357,7 @@ If the case of `404.html`, there are different scenarios. For example: - If you use user/group Pages (served under `/`) and try to access `/non/existing_file` GitLab Pages will try to serve `/404.html`. - If you use a custom domain and try to access `/non/existing_file`, GitLab - Pages will try to server only `/404.html`. + Pages will try to serve only `/404.html`. ### Remove the contents of your pages -- cgit v1.2.1 From 5075fb3bb7d0d91cec697095dc7c7803333a7ffb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 28 Jun 2016 10:14:24 +0200 Subject: fix attr_encrypted in EE --- app/models/pages_domain.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 2f4cded15c8..0b9ebf1ffe2 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -12,6 +12,7 @@ class PagesDomain < ActiveRecord::Base attr_encrypted :key, mode: :per_attribute_iv_and_salt, + insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' -- cgit v1.2.1 From 94545e58f3be29f40fd4e20bb58324246a1f6a60 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 11 Aug 2016 18:30:18 +0300 Subject: Active tense test coverage in pages spec Ports change from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/642 --- spec/services/pages_service_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb index b4215b2cd02..aa63fe3a5c1 100644 --- a/spec/services/pages_service_spec.rb +++ b/spec/services/pages_service_spec.rb @@ -15,7 +15,7 @@ describe PagesService, services: true do context 'on success' do before { build.success } - it 'should execute worker' do + it 'executes worker' do expect(PagesWorker).to receive(:perform_async) service.execute end @@ -25,7 +25,7 @@ describe PagesService, services: true do context "on #{status}" do before { build.status = status } - it 'should not execute worker' do + it 'does not execute worker' do expect(PagesWorker).not_to receive(:perform_async) service.execute end @@ -39,7 +39,7 @@ describe PagesService, services: true do build.success end - it 'should not execute worker' do + it 'does not execute worker' do expect(PagesWorker).not_to receive(:perform_async) service.execute end -- cgit v1.2.1 From 9677b5387201b2530b5cd18fe2b0bc897eae581e Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 1 Feb 2017 22:43:36 +0000 Subject: Excluded pages_domains from import/export spec --- spec/lib/gitlab/import_export/all_models.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 7fb6829f582..6aa10a6509a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -191,6 +191,7 @@ project: - environments - deployments - project_feature +- pages_domains - authorized_users - project_authorizations - route -- cgit v1.2.1 From 239743345a23c53b2c41509cefb288450d3bb563 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 26 Jan 2017 17:58:42 -0800 Subject: Fix GitLab Pages not refreshing upon new content Due to autoloading and Ruby scoping, the .update file was never being updated due to this error: ``` NoMethodError: undefined method `pages' for Projects::Settings:Module from /opt/gitlab/embedded/service/gitlab-rails/app/services/projects/update_pages_configuration_service.rb:50:in `pages_update_file' from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/instrumentation.rb:157:in `pages_update_file' from (irb):6 from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/console.rb:110:in `start' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/console.rb:9:in `start' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/commands_tasks.rb:68:in `console' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands/commands_tasks.rb:39:in `run_command!' from /opt/gitlab/embedded/lib/ruby/gems/2.3.0/gems/railties-4.2.7.1/lib/rails/commands.rb:17:in `' from bin/rails:9:in `require' ``` This error was caught and discarded quietly. This fix exercises this code and fixes the scope problem. Closes gitlab-com/infrastructure#1058 --- .../projects/update_pages_configuration_service.rb | 2 +- .../update_pages_configuration_service_spec.rb | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 spec/services/projects/update_pages_configuration_service_spec.rb diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 188847b5ad6..eb4809afa85 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -47,7 +47,7 @@ module Projects end def pages_update_file - File.join(Settings.pages.path, '.update') + File.join(::Settings.pages.path, '.update') end def update_file(file, data) diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb new file mode 100644 index 00000000000..8b329bc21c3 --- /dev/null +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -0,0 +1,24 @@ +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 -- cgit v1.2.1 From 7cacaf18de37987f15a3c41d1d022621c810d699 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 10 Jan 2017 15:46:47 +0000 Subject: Fix constant resolution in UpdatePagesService There is now a `Projects::Settings` module, for the members controller. Ensure that we get the actual settings, not that module. --- app/services/projects/update_pages_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index c52f3d3e230..f5f9ee88912 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -130,7 +130,7 @@ module Projects end def tmp_path - @tmp_path ||= File.join(Settings.pages.path, 'tmp') + @tmp_path ||= File.join(::Settings.pages.path, 'tmp') end def pages_path -- cgit v1.2.1 From e7d4b8a03068419794162ffcfa13703c09dbcd02 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 17 Jan 2017 19:47:42 -0500 Subject: First iteration on Pages refactoring --- doc/pages/administration.md | 352 ++++++++++++++++++++++---------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 0e1665fa832..9a94282a229 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,8 +1,9 @@ # GitLab Pages Administration -> **Note:** -> This feature was first [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> **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 to Community Edition in GitLab 8.16. --- @@ -14,33 +15,20 @@ configuration. If you are looking for ways to upload your static content in GitLab Pages, you probably want to read the [user documentation](README.md). -## The GitLab Pages daemon - -Starting from GitLab EE 8.5, GitLab Pages make 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. The GitLab -Pages Daemon supports dynamic certificates through SNI and exposes pages using -HTTP2 by default. - -Here is a brief list with what it is supported when using the pages daemon: - -- Multiple domains per-project -- One TLS certificate per-domain - - Validation of certificate - - Validation of certificate chain - - Validation of private key against certificate +## 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. -[gitlab pages daemon]: https://gitlab.com/gitlab-org/gitlab-pages -[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md - -### The GitLab Pages daemon and the case of custom domains +--- 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, so you basically have three choices: +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 @@ -53,68 +41,18 @@ can set it up, so you basically have three choices: 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. Let's begin by -installing the pages daemon. - -### Install the Pages daemon - -**Source installations** - -``` -cd /home/git -sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git -cd gitlab-pages -sudo -u git -H git checkout v0.2.1 -sudo -u git -H make -``` - -**Omnibus installations** - -The `gitlab-pages` daemon is included in the Omnibus package. - - -## Configuration - -There are multiple ways to set up GitLab Pages according to what URL scheme you -are willing to support. - -### Configuration prerequisites +In this document, we will proceed assuming the first option. -In the next section you will find all possible scenarios to choose from. +## Prerequisites -In either scenario, you will need: +Before proceeding with the Pages configuration, you will need to: -1. To use the [GitLab Pages daemon](#the-gitlab-pages-daemon) -1. A separate domain -1. A separate Nginx configuration file which needs to be explicitly added in - the server under which GitLab EE runs (Omnibus does that automatically) -1. (Optional) A wildcard certificate for that domain if you decide to serve - pages under HTTPS -1. (Optional but recommended) [Shared runners](../ci/runners/README.md) so that - your users don't have to bring their own - -### Configuration scenarios - -Before proceeding with setting up GitLab Pages, you have to decide which route -you want to take. - -The possible scenarios are depicted in the table below. - -| URL scheme | Option | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | -| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| `http://page.example.io` | 1 | no | no | no | no | -| `https://page.example.io` | 1 | yes | no | no | no | -| `http://page.example.io` and `http://page.com` | 2 | no | yes | no | yes | -| `https://page.example.io` and `https://page.com` | 2 | yes | redirects to HTTPS | yes | yes | - -As you see from the table above, each URL scheme comes with an option: - -1. Pages enabled, daemon is enabled and NGINX will proxy all requests to the - daemon. Pages daemon doesn't listen to the outside world. -1. 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. Have a separate domain under which the GitLab Pages will be served +1. (Optional) Have a wildcard certificate for that domain if you decide to serve + Pages under HTTPS +1. Configure a wildcard DNS record +1. (Optional but recommended) Enable [Shared runners](../ci/runners/README.md) + so that your users don't have to bring their own ### DNS configuration @@ -129,21 +67,39 @@ host that GitLab runs. For example, an entry would look like this: 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 -## Setting up GitLab Pages +## Configuration -Below are the four scenarios that are described in -[#configuration-scenarios](#configuration-scenarios). +Depending on your needs, you can install GitLab Pages in four different ways. -### Custom domains with HTTPS support +### 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. **Source installations:** -1. [Install the pages daemon](#install-the-pages-daemon) +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set `external_http` and `external_https` to the secondary IP on which the pages @@ -176,7 +132,19 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX 1. [Restart GitLab][restart] --- @@ -197,17 +165,32 @@ Below are the four scenarios that are described in 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. - Read more at the - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - section. 1. [Reconfigure GitLab][reconfigure] -### Custom domains without HTTPS support +### 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. **Source installations:** -1. [Install the pages daemon](#install-the-pages-daemon) +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + 1. Edit `gitlab.yml` to look like the example below. You need to change the `host` to the FQDN under which GitLab Pages will be served. Set `external_http` to the secondary IP on which the pages daemon will listen @@ -236,7 +219,19 @@ Below are the four scenarios that are described in gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX 1. [Restart GitLab][restart] --- @@ -254,58 +249,29 @@ Below are the four scenarios that are described in 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. - Read more at the - [NGINX configuration for custom domains](#nginx-configuration-for-custom-domains) - section. 1. [Reconfigure GitLab][reconfigure] -### Wildcard HTTP domain without custom domains +### Option 3. Wildcard HTTPS domain without custom domains -**Source installations:** - -1. [Install the pages daemon](#install-the-pages-daemon) -1. Go to the GitLab installation directory: - - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Make sure to [configure NGINX](#nginx-configuration) properly. -1. [Restart GitLab][restart] +| 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. -**Omnibus installations:** +**Source installations:** -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: +1. Install the Pages daemon: - ```ruby - pages_external_url 'http://example.io' ``` - -1. [Reconfigure GitLab][reconfigure] - -### Wildcard HTTPS domain without custom domains - -**Source installations:** - -1. [Install the pages daemon](#install-the-pages-daemon) + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` 1. In `gitlab.yml`, set the port to `443` and https to `true`: ```bash @@ -320,7 +286,14 @@ Below are the four scenarios that are described in https: true ``` -1. Make sure to [configure NGINX](#nginx-configuration) properly. +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. --- @@ -342,49 +315,76 @@ Below are the four scenarios that are described in 1. [Reconfigure GitLab][reconfigure] -## NGINX configuration +### Option 4. Wildcard HTTP domain without custom domains -Depending on your setup, you will need to make some changes to NGINX. -Specifically you must change the domain name and the IP address where NGINX -listens to. Read the following sections for more details. +| URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | +| --- |:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| `http://page.example.io` | no | no | no | no | -### NGINX configuration files +Pages enabled, daemon is enabled and NGINX will proxy all requests to the +daemon. Pages daemon doesn't listen to the outside world. -Copy the `gitlab-pages-ssl` Nginx configuration file: +**Source installations:** -```bash -sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf -sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf -``` +1. Install the Pages daemon: -Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` -### NGINX configuration for custom domains +1. Go to the GitLab installation directory: -> If you are not using custom domains ignore this section. + ```bash + cd /home/git/gitlab + ``` -[In the case of custom domains](#the-gitlab-pages-daemon-and-the-case-of-custom-domains), -if you have the secondary IP address configured on the same server as GitLab, -you need to change **all** NGINX configs to listen on the first IP address. +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: -**Source installations:** + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. 1. Restart NGINX +1. [Restart GitLab][restart] + +--- **Omnibus installations:** -1. Edit `/etc/gitlab/gilab.rb`: +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - ``` - nginx['listen_addresses'] = ['1.1.1.1'] + ```ruby + pages_external_url 'http://example.io' ``` 1. [Reconfigure GitLab][reconfigure] -### NGINX caveats +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. Be extra careful when setting up the domain name in the NGINX config. You must not remove the backslashes. @@ -462,35 +462,35 @@ latest previous version. --- +**GitLab 8.16 ([documentation][8-16-docs])** + +- GitLab Pages were ported to Community Edition in GitLab 8.16. +- 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 - -[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.1 -[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx +- Custom CNAME and TLS certificates support. +- Documentation was moved to one place. --- -**GitLab 8.4** - -No new changes. - ---- - -**GitLab 8.3 ([source docs][8-3-docs], [Omnibus docs][8-3-omnidocs])** +**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-3-omnidocs]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/8-3-stable-ee/doc/settings/pages.md +[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md +[8-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/pages/administration.md [backup]: ../raketasks/backup_restore.md [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 [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.1 -- cgit v1.2.1 From b14ee42ffad4b5f47a9c440d8467677b1f41ce06 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 18 Jan 2017 12:46:52 -0500 Subject: Move Pages docs to new location --- doc/README.md | 4 +- .../high_availability/load_balancer.md | 2 +- doc/administration/pages/index.md | 497 +++++++++++++++++++++ doc/install/installation.md | 2 +- doc/pages/README.md | 436 +----------------- doc/pages/administration.md | 497 +-------------------- doc/pages/img/pages_create_project.png | Bin 33597 -> 0 bytes doc/pages/img/pages_create_user_page.png | Bin 87071 -> 0 bytes doc/pages/img/pages_dns_details.png | Bin 34686 -> 0 bytes doc/pages/img/pages_multiple_domains.png | Bin 63716 -> 0 bytes doc/pages/img/pages_new_domain_button.png | Bin 51136 -> 0 bytes doc/pages/img/pages_remove.png | Bin 27259 -> 0 bytes doc/pages/img/pages_upload_cert.png | Bin 103730 -> 0 bytes doc/university/README.md | 2 +- doc/university/support/README.md | 2 +- .../project/pages/img/pages_create_project.png | Bin 0 -> 33597 bytes .../project/pages/img/pages_create_user_page.png | Bin 0 -> 87071 bytes doc/user/project/pages/img/pages_dns_details.png | Bin 0 -> 34686 bytes .../project/pages/img/pages_multiple_domains.png | Bin 0 -> 63716 bytes .../project/pages/img/pages_new_domain_button.png | Bin 0 -> 51136 bytes doc/user/project/pages/img/pages_remove.png | Bin 0 -> 27259 bytes doc/user/project/pages/img/pages_upload_cert.png | Bin 0 -> 103730 bytes doc/user/project/pages/index.md | 435 ++++++++++++++++++ 23 files changed, 940 insertions(+), 937 deletions(-) create mode 100644 doc/administration/pages/index.md delete mode 100644 doc/pages/img/pages_create_project.png delete mode 100644 doc/pages/img/pages_create_user_page.png delete mode 100644 doc/pages/img/pages_dns_details.png delete mode 100644 doc/pages/img/pages_multiple_domains.png delete mode 100644 doc/pages/img/pages_new_domain_button.png delete mode 100644 doc/pages/img/pages_remove.png delete mode 100644 doc/pages/img/pages_upload_cert.png create mode 100644 doc/user/project/pages/img/pages_create_project.png create mode 100644 doc/user/project/pages/img/pages_create_user_page.png create mode 100644 doc/user/project/pages/img/pages_dns_details.png create mode 100644 doc/user/project/pages/img/pages_multiple_domains.png create mode 100644 doc/user/project/pages/img/pages_new_domain_button.png create mode 100644 doc/user/project/pages/img/pages_remove.png create mode 100644 doc/user/project/pages/img/pages_upload_cert.png create mode 100644 doc/user/project/pages/index.md diff --git a/doc/README.md b/doc/README.md index 951a302f8ba..6e94f1e8e87 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,7 +12,7 @@ - [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. - [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. +- [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 and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. @@ -54,7 +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. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Pages configuration](pages/administration.md) +- [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 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. diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md index 1824829903c..dad8e956c0e 100644 --- a/doc/administration/high_availability/load_balancer.md +++ b/doc/administration/high_availability/load_balancer.md @@ -66,4 +66,4 @@ Read more on high-availability configuration: configure custom domains with custom SSL, which would not be possible if SSL was terminated at the load balancer. -[gitlab-pages]: http://docs.gitlab.com/ee/pages/administration.html +[gitlab-pages]: ../pages/index.md diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md new file mode 100644 index 00000000000..da148a0f2bb --- /dev/null +++ b/doc/administration/pages/index.md @@ -0,0 +1,497 @@ +# 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 to Community Edition in GitLab 8.16. + +--- + +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 +1. (Optional) Have a wildcard certificate for that domain if you decide to serve + Pages under HTTPS +1. Configure a wildcard DNS record +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. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.1:80 + external_https: 1.1.1.1:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +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. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.1:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +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. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +--- + +**Omnibus installations:** + +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. + +**Source installations:** + +1. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.1 + sudo -u git -H make + ``` + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +--- + +**Omnibus installations:** + +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url 'http://example.io' + ``` + +1. [Reconfigure GitLab][reconfigure] + +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^.*\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^.*\.pages\.example\.io$; +``` + +## 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. + +## Change storage path + +**Source installations:** + +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +**Omnibus installations:** + +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] + +## 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.16 ([documentation][8-16-docs])** + +- GitLab Pages were ported to Community Edition in GitLab 8.16. +- 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-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/administration/pages/index.md +[backup]: ../raketasks/backup_restore.md +[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.1 diff --git a/doc/install/installation.md b/doc/install/installation.md index 4496243da25..276e7f6916e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -489,7 +489,7 @@ Make sure to edit the config file to match your setup. Also, ensure that you mat 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](../pages/administration.md). +[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. diff --git a/doc/pages/README.md b/doc/pages/README.md index e427d7f283d..44b74513fd9 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1,435 +1 @@ -# GitLab Pages - -> **Note:** -> This feature was [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. - -> **Note:** -> This document is about the user guide. To learn how to enable GitLab Pages -> across your GitLab instance, visit the [administrator documentation](administration.md). - -With GitLab Pages you can host for free 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. - -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific -information, if you are using GitLab.com to host your website. - -## Getting started with GitLab Pages - -> **Note:** -> In the rest of this document we will assume that the general domain name that -> is used for GitLab Pages is `example.io`. - -In general there are two types of pages one might create: - -- Pages per user (`username.example.io`) or per group (`groupname.example.io`) -- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) - -In GitLab, usernames and groupnames are unique and we often refer to them -as namespaces. There can be only one namespace in a GitLab instance. Below you -can see the connection between the type of GitLab Pages, what the project name -that is created on GitLab looks like and the website URL it will be ultimately -be served on. - -| Type of GitLab Pages | The name of the project created in GitLab | Website URL | -| -------------------- | ------------ | ----------- | -| User pages | `username.example.io` | `http(s)://username.example.io` | -| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | -| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | -| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| - -> **Warning:** -> There are some known [limitations](#limitations) regarding namespaces served -> under the general domain name and HTTPS. Make sure to read that section. - -### GitLab Pages requirements - -In brief, this is what you need to upload your website in GitLab Pages: - -1. Find out the general domain name that is used for GitLab Pages - (ask your administrator). This is very important, so you should first make - sure you get that right. -1. Create a project -1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages] -1. Set up a GitLab Runner to build your website - -> **Note:** -> If [shared runners](../ci/runners/README.md) are enabled by your GitLab -> administrator, you should be able to use them instead of bringing your own. - -### User or group Pages - -For user and group pages, the name of the project should be specific to the -username or groupname and the general domain name that is used for GitLab Pages. -Head over your GitLab instance that supports GitLab Pages and create a -repository named `username.example.io`, where `username` is your username on -GitLab. If the first part of the project name doesn't match exactly your -username, it won’t work, so make sure to get it right. - -To create a group page, the steps are the same like when creating a website for -users. Just make sure that you are creating the project within the group's -namespace. - -![Create a user-based pages project](img/pages_create_user_page.png) - ---- - -After you push some static content to your repository and GitLab Runner uploads -the artifacts to GitLab CI, you will be able to access your website under -`http(s)://username.example.io`. Keep reading to find out how. - ->**Note:** -If your username/groupname contains a dot, for example `foo.bar`, you will not -be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). - -### Project Pages - -GitLab Pages for projects can be created by both user and group accounts. -The steps to create a project page for a user or a group are identical: - -1. Create a new project -1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory - of your repository with a specific job named [`pages`][pages]. -1. Set up a GitLab Runner to build your website - -A user's project will be served under `http(s)://username.example.io/projectname` -whereas a group's project under `http(s)://groupname.example.io/projectname`. - -### Explore the contents of `.gitlab-ci.yml` - -The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that -gives you absolute control over the build process. You can actually watch your -website being built live by following the CI build traces. - -> **Note:** -> Before reading this section, make sure you familiarize yourself with GitLab CI -> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by -> following our [quick start guide](../ci/quick_start/README.md). - -To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the -rules below: - -1. A special job named [`pages`][pages] must be defined -1. Any static content which will be served by GitLab Pages must be placed under - a `public/` directory -1. `artifacts` with a path to the `public/` directory must be defined - -In its simplest form, `.gitlab-ci.yml` looks like: - -```yaml -pages: - script: - - my_commands - artifacts: - paths: - - public -``` - -When the Runner reaches to build the `pages` job, it executes whatever is -defined in the `script` parameter and if the build completes with a non-zero -exit status, it then uploads the `public/` directory to GitLab Pages. - -The `public/` directory should contain all the static content of your website. -Depending on how you plan to publish your website, the steps defined in the -[`script` parameter](../ci/yaml/README.md#script) may differ. - -Be aware that Pages are by default branch/tag agnostic and their deployment -relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the -`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), -whenever a new commit is pushed to whatever branch or tag, the Pages will be -overwritten. In the example below, we limit the Pages to be deployed whenever -a commit is pushed only on the `master` branch: - -```yaml -pages: - script: - - my_commands - artifacts: - paths: - - public - only: - - master -``` - -We then tell the Runner to treat the `public/` directory as `artifacts` and -upload it to GitLab. And since all these parameters were all under a `pages` -job, the contents of the `public` directory will be served by GitLab Pages. - -#### How `.gitlab-ci.yml` looks like when the static content is in your repository - -Supposedly your repository contained the following files: - -``` -├── index.html -├── css -│   └── main.css -└── js - └── main.js -``` - -Then the `.gitlab-ci.yml` example below simply moves all files from the root -directory of the project to the `public/` directory. The `.public` workaround -is so `cp` doesn't also copy `public/` to itself in an infinite loop: - -```yaml -pages: - script: - - mkdir .public - - cp -r * .public - - mv .public public - artifacts: - paths: - - public - only: - - master -``` - -#### How `.gitlab-ci.yml` looks like when using a static generator - -In general, GitLab Pages support any kind of [static site generator][staticgen], -since `.gitlab-ci.yml` can be configured to run any possible command. - -In the root directory of your Git repository, place the source files of your -favorite static generator. Then provide a `.gitlab-ci.yml` file which is -specific to your static generator. - -The example below, uses [Jekyll] to build the static site: - -```yaml -image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 - -pages: # the build job must be named pages - script: - - gem install jekyll # we install jekyll - - jekyll build -d public/ # we tell jekyll to build the site for us - artifacts: - paths: - - public # this is where the site will live and the Runner uploads it in GitLab - only: - - master # this script is only affecting the master branch -``` - -Here, we used the Docker executor and in the first line we specified the base -image against which our builds will run. - -You have to make sure that the generated static files are ultimately placed -under the `public` directory, that's why in the `script` section we run the -`jekyll` command that builds the website and puts all content in the `public/` -directory. Depending on the static generator of your choice, this command will -differ. Search in the documentation of the static generator you will use if -there is an option to explicitly set the output directory. If there is not -such an option, you can always add one more line under `script` to rename the -resulting directory in `public/`. - -We then tell the Runner to treat the `public/` directory as `artifacts` and -upload it to GitLab. - ---- - -See the [jekyll example project][pages-jekyll] to better understand how this -works. - -For a list of Pages projects, see the [example projects](#example-projects) to -get you started. - -#### How to set up GitLab Pages in a repository where there's also actual code - -Remember that GitLab Pages are by default branch/tag agnostic and their -deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit -the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), -whenever a new commit is pushed to a branch that will be used specifically for -your pages. - -That way, you can have your project's code in the `master` branch and use an -orphan branch (let's name it `pages`) that will host your static generator site. - -You can create a new empty branch like this: - -```bash -git checkout --orphan pages -``` - -The first commit made on this new branch will have no parents and it will be -the root of a new history totally disconnected from all the other branches and -commits. Push the source files of your static generator in the `pages` branch. - -Below is a copy of `.gitlab-ci.yml` where the most significant line is the last -one, specifying to execute everything in the `pages` branch: - -``` -image: ruby:2.1 - -pages: - script: - - gem install jekyll - - jekyll build -d public/ - artifacts: - paths: - - public - only: - - pages -``` - -See an example that has different files in the [`master` branch][jekyll-master] -and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which -also includes `.gitlab-ci.yml`. - -[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master -[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages - -## Next steps - -So you have successfully deployed your website, congratulations! Let's check -what more you can do with GitLab Pages. - -### Example projects - -Below is a list of example projects for GitLab Pages with a plain HTML website -or various static site generators. Contributions are very welcome. - -- [Plain HTML](https://gitlab.com/pages/plain-html) -- [Jekyll](https://gitlab.com/pages/jekyll) -- [Hugo](https://gitlab.com/pages/hugo) -- [Middleman](https://gitlab.com/pages/middleman) -- [Hexo](https://gitlab.com/pages/hexo) -- [Brunch](https://gitlab.com/pages/brunch) -- [Metalsmith](https://gitlab.com/pages/metalsmith) -- [Harp](https://gitlab.com/pages/harp) - -Visit the GitLab Pages group for a full list of example projects: -. - -### Add a custom domain to your Pages website - -If this setting is enabled by your GitLab administrator, you should be able to -see the **New Domain** button when visiting your project's settings through the -gear icon in the top right and then navigating to **Pages**. - -![New domain button](img/pages_new_domain_button.png) - ---- - -You can add multiple domains pointing to your website hosted under GitLab. -Once the domain is added, you can see it listed under the **Domains** section. - -![Pages multiple domains](img/pages_multiple_domains.png) - ---- - -As a last step, you need to configure your DNS and add a CNAME pointing to your -user/group page. Click on the **Details** button of a domain for further -instructions. - -![Pages DNS details](img/pages_dns_details.png) - ---- - ->**Note:** -Currently there is support only for custom domains on per-project basis. That -means that if you add a custom domain (`example.com`) for your user website -(`username.example.io`), a project that is served under `username.example.io/foo`, -will not be accessible under `example.com/foo`. - -### Secure your custom domain website with TLS - -When you add a new custom domain, you also have the chance to add a TLS -certificate. If this setting is enabled by your GitLab administrator, you -should be able to see the option to upload the public certificate and the -private key when adding a new domain. - -![Pages upload cert](img/pages_upload_cert.png) - -### Custom error codes pages - -You can provide your own 403 and 404 error pages by creating the `403.html` and -`404.html` files respectively in the root directory of the `public/` directory -that will be included in the artifacts. Usually this is the root directory of -your project, but that may differ depending on your static generator -configuration. - -If the case of `404.html`, there are different scenarios. For example: - -- If you use project Pages (served under `/projectname/`) and try to access - `/projectname/non/exsiting_file`, GitLab Pages will try to serve first - `/projectname/404.html`, and then `/404.html`. -- If you use user/group Pages (served under `/`) and try to access - `/non/existing_file` GitLab Pages will try to serve `/404.html`. -- If you use a custom domain and try to access `/non/existing_file`, GitLab - Pages will try to serve only `/404.html`. - -### Remove the contents of your pages - -If you ever feel the need to purge your Pages content, you can do so by going -to your project's settings through the gear icon in the top right, and then -navigating to **Pages**. Hit the **Remove pages** button and your Pages website -will be deleted. Simple as that. - -![Remove pages](img/pages_remove.png) - -## GitLab Pages on GitLab.com - -If you are using GitLab.com to host your website, then: - -- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. -- Custom domains and TLS support are enabled. -- Shared runners are enabled by default, provided for free and can be used to - build your website. If you want you can still bring your own Runner. - -The rest of the guide still applies. - -## Limitations - -When using Pages under the general domain of a GitLab instance (`*.example.io`), -you _cannot_ use HTTPS with sub-subdomains. That means that if your -username/groupname contains a dot, for example `foo.bar`, the domain -`https://foo.bar.example.io` will _not_ work. This is a limitation of the -[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you -don't redirect HTTP to HTTPS. - -[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" - -## Redirects in GitLab Pages - -Since you cannot use any custom server configuration files, like `.htaccess` or -any `.conf` file for that matter, if you want to redirect a web page to another -location, you can use the [HTTP meta refresh tag][metarefresh]. - -Some static site generators provide plugins for that functionality so that you -don't have to create and edit HTML files manually. For example, Jekyll has the -[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). - -## Frequently Asked Questions - -### Can I download my generated pages? - -Sure. All you need to do is download the artifacts archive from the build page. - -### Can I use GitLab Pages if my project is private? - -Yes. GitLab Pages don't care whether you set your project's visibility level -to private, internal or public. - -### Do I need to create a user/group website before creating a project website? - -No, you don't. You can create your project first and it will be accessed under -`http(s)://namespace.example.io/projectname`. - -## Known issues - -For a list of known issues, visit GitLab's [public issue tracker]. - ---- - -[jekyll]: http://jekyllrb.com/ -[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages -[gitlab ci]: https://about.gitlab.com/gitlab-ci -[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner -[pages]: ../ci/yaml/README.md#pages -[staticgen]: https://www.staticgen.com/ -[pages-jekyll]: https://gitlab.com/pages/jekyll -[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh -[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages +This document was moved to [user/project/pages](../user/project/pages). diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 9a94282a229..4eb3bb32c77 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1,496 +1 @@ -# 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 to Community Edition in GitLab 8.16. - ---- - -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](README.md). - -## 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 -1. (Optional) Have a wildcard certificate for that domain if you decide to serve - Pages under HTTPS -1. Configure a wildcard DNS record -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. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` and `external_https` to the secondary IP on which the pages - daemon will listen for connections: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - - external_http: 1.1.1.1:80 - external_https: 1.1.1.1:443 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, - `external_http` and `external_https` settings that you set above respectively. - The `-root-cert` and `-root-key` settings are the wildcard TLS certificates - of the `example.io` domain: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -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. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - - external_http: 1.1.1.1:80 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -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. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` -1. In `gitlab.yml`, set the port to `443` and https to `true`: - - ```bash - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - ---- - -**Omnibus installations:** - -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. - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.1 - sudo -u git -H make - ``` - -1. Go to the GitLab installation directory: - - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - ---- - -**Omnibus installations:** - -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: - - ```ruby - pages_external_url 'http://example.io' - ``` - -1. [Reconfigure GitLab][reconfigure] - -## NGINX caveats - ->**Note:** -The following information applies only for installations from source. - -Be extra careful when setting up the domain name in the NGINX config. You must -not remove the backslashes. - -If your GitLab pages domain is `example.io`, replace: - -```bash -server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; -``` - -with: - -``` -server_name ~^.*\.example\.io$; -``` - -If you are using a subdomain, make sure to escape all dots (`.`) except from -the first one with a backslash (\). For example `pages.example.io` would be: - -``` -server_name ~^.*\.pages\.example\.io$; -``` - -## 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. - -## Change storage path - -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` - -1. [Restart GitLab][restart] - -**Omnibus installations:** - -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] - -## 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.16 ([documentation][8-16-docs])** - -- GitLab Pages were ported to Community Edition in GitLab 8.16. -- 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-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/pages/administration.md -[backup]: ../raketasks/backup_restore.md -[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 -[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.1 +This document was moved to [administration/pages](../administration/pages/index.md). diff --git a/doc/pages/img/pages_create_project.png b/doc/pages/img/pages_create_project.png deleted file mode 100644 index a936d8e5dbd..00000000000 Binary files a/doc/pages/img/pages_create_project.png and /dev/null differ diff --git a/doc/pages/img/pages_create_user_page.png b/doc/pages/img/pages_create_user_page.png deleted file mode 100644 index 3f615d3757d..00000000000 Binary files a/doc/pages/img/pages_create_user_page.png and /dev/null differ diff --git a/doc/pages/img/pages_dns_details.png b/doc/pages/img/pages_dns_details.png deleted file mode 100644 index 8d34f3b7f38..00000000000 Binary files a/doc/pages/img/pages_dns_details.png and /dev/null differ diff --git a/doc/pages/img/pages_multiple_domains.png b/doc/pages/img/pages_multiple_domains.png deleted file mode 100644 index 2bc7cee07a6..00000000000 Binary files a/doc/pages/img/pages_multiple_domains.png and /dev/null differ diff --git a/doc/pages/img/pages_new_domain_button.png b/doc/pages/img/pages_new_domain_button.png deleted file mode 100644 index c3640133bb2..00000000000 Binary files a/doc/pages/img/pages_new_domain_button.png and /dev/null differ diff --git a/doc/pages/img/pages_remove.png b/doc/pages/img/pages_remove.png deleted file mode 100644 index adbfb654877..00000000000 Binary files a/doc/pages/img/pages_remove.png and /dev/null differ diff --git a/doc/pages/img/pages_upload_cert.png b/doc/pages/img/pages_upload_cert.png deleted file mode 100644 index 06d85ab1971..00000000000 Binary files a/doc/pages/img/pages_upload_cert.png and /dev/null differ diff --git a/doc/university/README.md b/doc/university/README.md index 12727e9d56f..379a7b4b40f 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -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. [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 diff --git a/doc/university/support/README.md b/doc/university/support/README.md index 6e415e4d219..ca538ef6dc3 100644 --- a/doc/university/support/README.md +++ b/doc/university/support/README.md @@ -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 - 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) -- 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: - 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) diff --git a/doc/user/project/pages/img/pages_create_project.png b/doc/user/project/pages/img/pages_create_project.png new file mode 100644 index 00000000000..a936d8e5dbd Binary files /dev/null and b/doc/user/project/pages/img/pages_create_project.png differ diff --git a/doc/user/project/pages/img/pages_create_user_page.png b/doc/user/project/pages/img/pages_create_user_page.png new file mode 100644 index 00000000000..3f615d3757d Binary files /dev/null and b/doc/user/project/pages/img/pages_create_user_page.png differ diff --git a/doc/user/project/pages/img/pages_dns_details.png b/doc/user/project/pages/img/pages_dns_details.png new file mode 100644 index 00000000000..8d34f3b7f38 Binary files /dev/null and b/doc/user/project/pages/img/pages_dns_details.png differ diff --git a/doc/user/project/pages/img/pages_multiple_domains.png b/doc/user/project/pages/img/pages_multiple_domains.png new file mode 100644 index 00000000000..2bc7cee07a6 Binary files /dev/null and b/doc/user/project/pages/img/pages_multiple_domains.png differ diff --git a/doc/user/project/pages/img/pages_new_domain_button.png b/doc/user/project/pages/img/pages_new_domain_button.png new file mode 100644 index 00000000000..c3640133bb2 Binary files /dev/null and b/doc/user/project/pages/img/pages_new_domain_button.png differ diff --git a/doc/user/project/pages/img/pages_remove.png b/doc/user/project/pages/img/pages_remove.png new file mode 100644 index 00000000000..adbfb654877 Binary files /dev/null and b/doc/user/project/pages/img/pages_remove.png differ diff --git a/doc/user/project/pages/img/pages_upload_cert.png b/doc/user/project/pages/img/pages_upload_cert.png new file mode 100644 index 00000000000..06d85ab1971 Binary files /dev/null and b/doc/user/project/pages/img/pages_upload_cert.png differ diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md new file mode 100644 index 00000000000..e427d7f283d --- /dev/null +++ b/doc/user/project/pages/index.md @@ -0,0 +1,435 @@ +# GitLab Pages + +> **Note:** +> This feature was [introduced][ee-80] in GitLab EE 8.3. +> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. + +> **Note:** +> This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](administration.md). + +With GitLab Pages you can host for free 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. + +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific +information, if you are using GitLab.com to host your website. + +## Getting started with GitLab Pages + +> **Note:** +> In the rest of this document we will assume that the general domain name that +> is used for GitLab Pages is `example.io`. + +In general there are two types of pages one might create: + +- Pages per user (`username.example.io`) or per group (`groupname.example.io`) +- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`) + +In GitLab, usernames and groupnames are unique and we often refer to them +as namespaces. There can be only one namespace in a GitLab instance. Below you +can see the connection between the type of GitLab Pages, what the project name +that is created on GitLab looks like and the website URL it will be ultimately +be served on. + +| Type of GitLab Pages | The name of the project created in GitLab | Website URL | +| -------------------- | ------------ | ----------- | +| User pages | `username.example.io` | `http(s)://username.example.io` | +| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` | +| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` | +| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`| + +> **Warning:** +> There are some known [limitations](#limitations) regarding namespaces served +> under the general domain name and HTTPS. Make sure to read that section. + +### GitLab Pages requirements + +In brief, this is what you need to upload your website in GitLab Pages: + +1. Find out the general domain name that is used for GitLab Pages + (ask your administrator). This is very important, so you should first make + sure you get that right. +1. Create a project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages] +1. Set up a GitLab Runner to build your website + +> **Note:** +> If [shared runners](../ci/runners/README.md) are enabled by your GitLab +> administrator, you should be able to use them instead of bringing your own. + +### User or group Pages + +For user and group pages, the name of the project should be specific to the +username or groupname and the general domain name that is used for GitLab Pages. +Head over your GitLab instance that supports GitLab Pages and create a +repository named `username.example.io`, where `username` is your username on +GitLab. If the first part of the project name doesn't match exactly your +username, it won’t work, so make sure to get it right. + +To create a group page, the steps are the same like when creating a website for +users. Just make sure that you are creating the project within the group's +namespace. + +![Create a user-based pages project](img/pages_create_user_page.png) + +--- + +After you push some static content to your repository and GitLab Runner uploads +the artifacts to GitLab CI, you will be able to access your website under +`http(s)://username.example.io`. Keep reading to find out how. + +>**Note:** +If your username/groupname contains a dot, for example `foo.bar`, you will not +be able to use the wildcard domain HTTPS, read more at [limitations](#limitations). + +### Project Pages + +GitLab Pages for projects can be created by both user and group accounts. +The steps to create a project page for a user or a group are identical: + +1. Create a new project +1. Push a [`.gitlab-ci.yml` file](../ci/yaml/README.md) in the root directory + of your repository with a specific job named [`pages`][pages]. +1. Set up a GitLab Runner to build your website + +A user's project will be served under `http(s)://username.example.io/projectname` +whereas a group's project under `http(s)://groupname.example.io/projectname`. + +### Explore the contents of `.gitlab-ci.yml` + +The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that +gives you absolute control over the build process. You can actually watch your +website being built live by following the CI build traces. + +> **Note:** +> Before reading this section, make sure you familiarize yourself with GitLab CI +> and the specific syntax of[`.gitlab-ci.yml`](../ci/yaml/README.md) by +> following our [quick start guide](../ci/quick_start/README.md). + +To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the +rules below: + +1. A special job named [`pages`][pages] must be defined +1. Any static content which will be served by GitLab Pages must be placed under + a `public/` directory +1. `artifacts` with a path to the `public/` directory must be defined + +In its simplest form, `.gitlab-ci.yml` looks like: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public +``` + +When the Runner reaches to build the `pages` job, it executes whatever is +defined in the `script` parameter and if the build completes with a non-zero +exit status, it then uploads the `public/` directory to GitLab Pages. + +The `public/` directory should contain all the static content of your website. +Depending on how you plan to publish your website, the steps defined in the +[`script` parameter](../ci/yaml/README.md#script) may differ. + +Be aware that Pages are by default branch/tag agnostic and their deployment +relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the +`pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to whatever branch or tag, the Pages will be +overwritten. In the example below, we limit the Pages to be deployed whenever +a commit is pushed only on the `master` branch: + +```yaml +pages: + script: + - my_commands + artifacts: + paths: + - public + only: + - master +``` + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. And since all these parameters were all under a `pages` +job, the contents of the `public` directory will be served by GitLab Pages. + +#### How `.gitlab-ci.yml` looks like when the static content is in your repository + +Supposedly your repository contained the following files: + +``` +├── index.html +├── css +│   └── main.css +└── js + └── main.js +``` + +Then the `.gitlab-ci.yml` example below simply moves all files from the root +directory of the project to the `public/` directory. The `.public` workaround +is so `cp` doesn't also copy `public/` to itself in an infinite loop: + +```yaml +pages: + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master +``` + +#### How `.gitlab-ci.yml` looks like when using a static generator + +In general, GitLab Pages support any kind of [static site generator][staticgen], +since `.gitlab-ci.yml` can be configured to run any possible command. + +In the root directory of your Git repository, place the source files of your +favorite static generator. Then provide a `.gitlab-ci.yml` file which is +specific to your static generator. + +The example below, uses [Jekyll] to build the static site: + +```yaml +image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1 + +pages: # the build job must be named pages + script: + - gem install jekyll # we install jekyll + - jekyll build -d public/ # we tell jekyll to build the site for us + artifacts: + paths: + - public # this is where the site will live and the Runner uploads it in GitLab + only: + - master # this script is only affecting the master branch +``` + +Here, we used the Docker executor and in the first line we specified the base +image against which our builds will run. + +You have to make sure that the generated static files are ultimately placed +under the `public` directory, that's why in the `script` section we run the +`jekyll` command that builds the website and puts all content in the `public/` +directory. Depending on the static generator of your choice, this command will +differ. Search in the documentation of the static generator you will use if +there is an option to explicitly set the output directory. If there is not +such an option, you can always add one more line under `script` to rename the +resulting directory in `public/`. + +We then tell the Runner to treat the `public/` directory as `artifacts` and +upload it to GitLab. + +--- + +See the [jekyll example project][pages-jekyll] to better understand how this +works. + +For a list of Pages projects, see the [example projects](#example-projects) to +get you started. + +#### How to set up GitLab Pages in a repository where there's also actual code + +Remember that GitLab Pages are by default branch/tag agnostic and their +deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit +the `pages` job with the [`only` parameter](../ci/yaml/README.md#only-and-except), +whenever a new commit is pushed to a branch that will be used specifically for +your pages. + +That way, you can have your project's code in the `master` branch and use an +orphan branch (let's name it `pages`) that will host your static generator site. + +You can create a new empty branch like this: + +```bash +git checkout --orphan pages +``` + +The first commit made on this new branch will have no parents and it will be +the root of a new history totally disconnected from all the other branches and +commits. Push the source files of your static generator in the `pages` branch. + +Below is a copy of `.gitlab-ci.yml` where the most significant line is the last +one, specifying to execute everything in the `pages` branch: + +``` +image: ruby:2.1 + +pages: + script: + - gem install jekyll + - jekyll build -d public/ + artifacts: + paths: + - public + only: + - pages +``` + +See an example that has different files in the [`master` branch][jekyll-master] +and the source files for Jekyll are in a [`pages` branch][jekyll-pages] which +also includes `.gitlab-ci.yml`. + +[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master +[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages + +## Next steps + +So you have successfully deployed your website, congratulations! Let's check +what more you can do with GitLab Pages. + +### Example projects + +Below is a list of example projects for GitLab Pages with a plain HTML website +or various static site generators. Contributions are very welcome. + +- [Plain HTML](https://gitlab.com/pages/plain-html) +- [Jekyll](https://gitlab.com/pages/jekyll) +- [Hugo](https://gitlab.com/pages/hugo) +- [Middleman](https://gitlab.com/pages/middleman) +- [Hexo](https://gitlab.com/pages/hexo) +- [Brunch](https://gitlab.com/pages/brunch) +- [Metalsmith](https://gitlab.com/pages/metalsmith) +- [Harp](https://gitlab.com/pages/harp) + +Visit the GitLab Pages group for a full list of example projects: +. + +### Add a custom domain to your Pages website + +If this setting is enabled by your GitLab administrator, you should be able to +see the **New Domain** button when visiting your project's settings through the +gear icon in the top right and then navigating to **Pages**. + +![New domain button](img/pages_new_domain_button.png) + +--- + +You can add multiple domains pointing to your website hosted under GitLab. +Once the domain is added, you can see it listed under the **Domains** section. + +![Pages multiple domains](img/pages_multiple_domains.png) + +--- + +As a last step, you need to configure your DNS and add a CNAME pointing to your +user/group page. Click on the **Details** button of a domain for further +instructions. + +![Pages DNS details](img/pages_dns_details.png) + +--- + +>**Note:** +Currently there is support only for custom domains on per-project basis. That +means that if you add a custom domain (`example.com`) for your user website +(`username.example.io`), a project that is served under `username.example.io/foo`, +will not be accessible under `example.com/foo`. + +### Secure your custom domain website with TLS + +When you add a new custom domain, you also have the chance to add a TLS +certificate. If this setting is enabled by your GitLab administrator, you +should be able to see the option to upload the public certificate and the +private key when adding a new domain. + +![Pages upload cert](img/pages_upload_cert.png) + +### Custom error codes pages + +You can provide your own 403 and 404 error pages by creating the `403.html` and +`404.html` files respectively in the root directory of the `public/` directory +that will be included in the artifacts. Usually this is the root directory of +your project, but that may differ depending on your static generator +configuration. + +If the case of `404.html`, there are different scenarios. For example: + +- If you use project Pages (served under `/projectname/`) and try to access + `/projectname/non/exsiting_file`, GitLab Pages will try to serve first + `/projectname/404.html`, and then `/404.html`. +- If you use user/group Pages (served under `/`) and try to access + `/non/existing_file` GitLab Pages will try to serve `/404.html`. +- If you use a custom domain and try to access `/non/existing_file`, GitLab + Pages will try to serve only `/404.html`. + +### Remove the contents of your pages + +If you ever feel the need to purge your Pages content, you can do so by going +to your project's settings through the gear icon in the top right, and then +navigating to **Pages**. Hit the **Remove pages** button and your Pages website +will be deleted. Simple as that. + +![Remove pages](img/pages_remove.png) + +## GitLab Pages on GitLab.com + +If you are using GitLab.com to host your website, then: + +- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`. +- Custom domains and TLS support are enabled. +- Shared runners are enabled by default, provided for free and can be used to + build your website. If you want you can still bring your own Runner. + +The rest of the guide still applies. + +## Limitations + +When using Pages under the general domain of a GitLab instance (`*.example.io`), +you _cannot_ use HTTPS with sub-subdomains. That means that if your +username/groupname contains a dot, for example `foo.bar`, the domain +`https://foo.bar.example.io` will _not_ work. This is a limitation of the +[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you +don't redirect HTTP to HTTPS. + +[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC" + +## Redirects in GitLab Pages + +Since you cannot use any custom server configuration files, like `.htaccess` or +any `.conf` file for that matter, if you want to redirect a web page to another +location, you can use the [HTTP meta refresh tag][metarefresh]. + +Some static site generators provide plugins for that functionality so that you +don't have to create and edit HTML files manually. For example, Jekyll has the +[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from). + +## Frequently Asked Questions + +### Can I download my generated pages? + +Sure. All you need to do is download the artifacts archive from the build page. + +### Can I use GitLab Pages if my project is private? + +Yes. GitLab Pages don't care whether you set your project's visibility level +to private, internal or public. + +### Do I need to create a user/group website before creating a project website? + +No, you don't. You can create your project first and it will be accessed under +`http(s)://namespace.example.io/projectname`. + +## Known issues + +For a list of known issues, visit GitLab's [public issue tracker]. + +--- + +[jekyll]: http://jekyllrb.com/ +[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 +[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 +[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages +[gitlab ci]: https://about.gitlab.com/gitlab-ci +[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner +[pages]: ../ci/yaml/README.md#pages +[staticgen]: https://www.staticgen.com/ +[pages-jekyll]: https://gitlab.com/pages/jekyll +[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh +[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Pages -- cgit v1.2.1 From 749d8051ed379f79cab9c378ee1ac564d2d09e9b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:47:39 +0100 Subject: Bump GitLab version that Pages where ported to CE --- doc/administration/pages/index.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index da148a0f2bb..c460c8e1b86 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -462,9 +462,9 @@ latest previous version. --- -**GitLab 8.16 ([documentation][8-16-docs])** +**GitLab 8.17 ([documentation][8-17-docs])** -- GitLab Pages were ported to Community Edition in GitLab 8.16. +- 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])** @@ -476,15 +476,13 @@ latest previous version. - 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-16-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable-ce/doc/administration/pages/index.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 [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 [ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173 -- cgit v1.2.1 From 39e74b1cba3773fd0fb37bc78a7400ec7c6b7ab3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:48:27 +0100 Subject: Fix link to new pages location --- doc/pages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pages/README.md b/doc/pages/README.md index 44b74513fd9..c9715eed598 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1 +1 @@ -This document was moved to [user/project/pages](../user/project/pages). +This document was moved to [user/project/pages](../user/project/pages/index.md). -- cgit v1.2.1 From 452b9c8b955274f8892d3fa4a0fc335d28515272 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:48:43 +0100 Subject: Merge multiple notes into one in Pages user docs --- doc/user/project/pages/index.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index e427d7f283d..a07c23a3274 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -1,12 +1,10 @@ # GitLab Pages -> **Note:** -> This feature was [introduced][ee-80] in GitLab EE 8.3. -> Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. - -> **Note:** -> This document is about the user guide. To learn how to enable GitLab Pages -> across your GitLab instance, visit the [administrator documentation](administration.md). +> **Notes:** +> - This feature was [introduced][ee-80] in GitLab EE 8.3. +> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5. +> - This document is about the user guide. To learn how to enable GitLab Pages +> across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md). With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can -- cgit v1.2.1 From 2c787bca1f0566517a2ddae7d1f23a822285a44d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 13:54:09 +0100 Subject: Better highlight prerequisites for Pages --- doc/administration/pages/index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index c460c8e1b86..5e84eb85667 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -47,12 +47,13 @@ In this document, we will proceed assuming the first option. Before proceeding with the Pages configuration, you will need to: -1. Have a separate domain under which the GitLab Pages will be served -1. (Optional) Have a wildcard certificate for that domain if you decide to serve - Pages under HTTPS -1. Configure a wildcard DNS record +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 + so that your users don't have to bring their own. ### DNS configuration -- cgit v1.2.1 From e9c08231d73d2f3c73eb2687bc887076c9b6ed83 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 14:03:27 +0100 Subject: Bump pages daemon and place Omnibus settings on top --- doc/administration/pages/index.md | 216 ++++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 104 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 5e84eb85667..5b1d6ee9998 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -89,6 +89,27 @@ 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. +**Omnibus installations:** + +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] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -97,7 +118,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -148,20 +169,26 @@ world. Custom domains and TLS are supported. 1. Restart NGINX 1. [Restart GitLab][restart] ---- +### 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. **Omnibus installations:** 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby - pages_external_url "https://example.io" + pages_external_url "http://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 @@ -169,16 +196,7 @@ world. Custom domains and TLS are supported. 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. +--- **Source installations:** @@ -188,7 +206,7 @@ world. Custom domains and TLS are supported. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -235,32 +253,34 @@ world. Custom domains and TLS are supported. 1. Restart NGINX 1. [Restart GitLab][restart] ---- +### 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. **Omnibus installations:** -1. Edit `/etc/gitlab/gitlab.rb`: +1. Place the certificate and key inside `/etc/gitlab/ssl` +1. In `/etc/gitlab/gitlab.rb` specify the following configuration: ```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' + 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 `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. + where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, + respectively. 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. +--- **Source installations:** @@ -270,7 +290,7 @@ daemon. Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` 1. In `gitlab.yml`, set the port to `443` and https to `true`: @@ -296,25 +316,8 @@ daemon. Pages daemon doesn't listen to the outside world. Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. ---- - -**Omnibus installations:** - -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] +1. Restart NGINX +1. [Restart GitLab][restart] ### Option 4. Wildcard HTTP domain without custom domains @@ -325,6 +328,18 @@ daemon. Pages daemon doesn't listen to the outside world. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. +**Omnibus installations:** + +1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: + + ```ruby + pages_external_url 'http://example.io' + ``` + +1. [Reconfigure GitLab][reconfigure] + +--- + **Source installations:** 1. Install the Pages daemon: @@ -333,7 +348,7 @@ daemon. Pages daemon doesn't listen to the outside world. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git cd gitlab-pages - sudo -u git -H git checkout v0.2.1 + sudo -u git -H git checkout v0.2.4 sudo -u git -H make ``` @@ -370,18 +385,55 @@ daemon. Pages daemon doesn't listen to the outside world. 1. Restart NGINX 1. [Restart GitLab][restart] ---- +## 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. + +## Change storage path + +Follow the steps below to change the default path where GitLab Pages' contents +are stored. **Omnibus installations:** -1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: +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 - pages_external_url 'http://example.io' - ``` + ```ruby + gitlab_rails['pages_path'] = "/mnt/storage/pages" + ``` 1. [Reconfigure GitLab][reconfigure] +--- + +**Source installations:** + +1. Pages are stored by default in `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +## 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. + ## NGINX caveats >**Note:** @@ -409,50 +461,6 @@ the first one with a backslash (\). For example `pages.example.io` would be: server_name ~^.*\.pages\.example\.io$; ``` -## 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. - -## Change storage path - -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` - -1. [Restart GitLab][restart] - -**Omnibus installations:** - -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] - -## 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 @@ -493,4 +501,4 @@ latest previous version. [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.1 +[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.2.4 -- cgit v1.2.1 From e2123de5fca072306f7247931863418864739035 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jan 2017 14:37:52 +0100 Subject: Split Omnibus and source installation Pages admin docs [ci skip] --- doc/administration/pages/index.md | 275 ++----------------------------- doc/administration/pages/source.md | 323 +++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 265 deletions(-) create mode 100644 doc/administration/pages/source.md diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 5b1d6ee9998..c352caf1115 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -1,9 +1,11 @@ # 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 to Community Edition in GitLab 8.16. +- [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). --- @@ -89,8 +91,6 @@ 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. -**Omnibus installations:** - 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby @@ -108,67 +108,6 @@ world. Custom domains and TLS are supported. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` and `external_https` to the secondary IP on which the pages - daemon will listen for connections: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - - external_http: 1.1.1.1:80 - external_https: 1.1.1.1:443 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, - `external_http` and `external_https` settings that you set above respectively. - The `-root-cert` and `-root-key` settings are the wildcard TLS certificates - of the `example.io` domain: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80 -listen-https 1.1.1.1:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 2. Custom domains without HTTPS support | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -180,8 +119,6 @@ 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. -**Omnibus installations:** - 1. Edit `/etc/gitlab/gitlab.rb`: ```ruby @@ -196,63 +133,6 @@ world. Custom domains and TLS are supported. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Edit `gitlab.yml` to look like the example below. You need to change the - `host` to the FQDN under which GitLab Pages will be served. Set - `external_http` to the secondary IP on which the pages daemon will listen - for connections: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - - external_http: 1.1.1.1:80 - ``` - -1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in - order to enable the pages daemon. In `gitlab_pages_options` the - `-pages-domain` and `-listen-http` must match the `host` and `external_http` - settings that you set above respectively: - - ``` - gitlab_pages_enabled=true - gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.1:80" - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace - `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab - listens to. -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 3. Wildcard HTTPS domain without custom domains | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -262,8 +142,6 @@ world. Custom domains and TLS are supported. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. -**Omnibus installations:** - 1. Place the certificate and key inside `/etc/gitlab/ssl` 1. In `/etc/gitlab/gitlab.rb` specify the following configuration: @@ -280,45 +158,6 @@ daemon. Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` -1. In `gitlab.yml`, set the port to `443` and https to `true`: - - ```bash - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 443 - https: true - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - ### Option 4. Wildcard HTTP domain without custom domains | URL scheme | Wildcard certificate | Custom domain with HTTP support | Custom domain with HTTPS support | Secondary IP | @@ -328,8 +167,6 @@ daemon. Pages daemon doesn't listen to the outside world. Pages enabled, daemon is enabled and NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. -**Omnibus installations:** - 1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`: ```ruby @@ -338,66 +175,11 @@ daemon. Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Install the Pages daemon: - - ``` - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git - cd gitlab-pages - sudo -u git -H git checkout v0.2.4 - sudo -u git -H make - ``` - -1. Go to the GitLab installation directory: - - ```bash - cd /home/git/gitlab - ``` - -1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and - the `host` to the FQDN under which GitLab Pages will be served: - - ```yaml - ## GitLab Pages - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - # path: shared/pages - - host: example.io - port: 80 - https: false - ``` - -1. Copy the `gitlab-pages-ssl` Nginx configuration file: - - ```bash - sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf - sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf - ``` - - Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. - -1. Restart NGINX -1. [Restart GitLab][restart] - -## 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. - ## Change storage path Follow the steps below to change the default path where GitLab Pages' contents are stored. -**Omnibus installations:** - 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`: @@ -408,22 +190,11 @@ are stored. 1. [Reconfigure GitLab][reconfigure] ---- - -**Source installations:** - -1. Pages are stored by default in `/home/git/gitlab/shared/pages`. - If you wish to store them in another location you must set it up in - `gitlab.yml` under the `pages` section: - - ```yaml - pages: - enabled: true - # The location where pages are stored (default: shared/pages). - path: /mnt/storage/pages - ``` +## Set maximum pages size -1. [Restart GitLab][restart] +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 @@ -434,33 +205,6 @@ Pages are part of the [regular backup][backup] so there is nothing to configure. You should strongly consider running GitLab pages under a different hostname than GitLab to prevent XSS attacks. -## NGINX caveats - ->**Note:** -The following information applies only for installations from source. - -Be extra careful when setting up the domain name in the NGINX config. You must -not remove the backslashes. - -If your GitLab pages domain is `example.io`, replace: - -```bash -server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; -``` - -with: - -``` -server_name ~^.*\.example\.io$; -``` - -If you are using a subdomain, make sure to escape all dots (`.`) except from -the first one with a backslash (\). For example `pages.example.io` would be: - -``` -server_name ~^.*\.pages\.example\.io$; -``` - ## Changelog GitLab Pages were first introduced in GitLab EE 8.3. Since then, many features @@ -493,6 +237,7 @@ latest previous version. [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 diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md new file mode 100644 index 00000000000..d4468b99992 --- /dev/null +++ b/doc/administration/pages/source.md @@ -0,0 +1,323 @@ +# GitLab Pages administration for source installations + +This is the documentation for configuring a GitLab Pages when you have installed +GitLab from source and not using the Omnibus packages. + +You are encouraged to read the [Omnibus documentation](index.md) as it provides +some invaluable information to the configuration of GitLab Pages. Please proceed +to read it before going forward with this guide. + +We also highly recommend that you use the Omnibus GitLab packages, as we +optimize them specifically for GitLab, and we will take care of upgrading GitLab +Pages to the latest supported version. + +## Overview + +[Read the Omnibus overview section.](index.md#overview) + +## Prerequisites + +[Read the Omnibus prerequisites section.](index.md#prerequisites) + +## 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. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` and `external_https` to the secondary IP on which the pages + daemon will listen for connections: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + + external_http: 1.1.1.2:80 + external_https: 1.1.1.2:443 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain`, `-listen-http` and `-listen-https` must match the `host`, + `external_http` and `external_https` settings that you set above respectively. + The `-root-cert` and `-root-key` settings are the wildcard TLS certificates + of the `example.io` domain: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +### 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. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Edit `gitlab.yml` to look like the example below. You need to change the + `host` to the FQDN under which GitLab Pages will be served. Set + `external_http` to the secondary IP on which the pages daemon will listen + for connections: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + + external_http: 1.1.1.2:80 + ``` + +1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in + order to enable the pages daemon. In `gitlab_pages_options` the + `-pages-domain` and `-listen-http` must match the `host` and `external_http` + settings that you set above respectively: + + ``` + gitlab_pages_enabled=true + gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80" + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace + `0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab + listens to. +1. Restart NGINX +1. [Restart GitLab][restart] + +### 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. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` +1. In `gitlab.yml`, set the port to `443` and https to `true`: + + ```bash + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 443 + https: true + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +### 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. Install the Pages daemon: + + ``` + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git + cd gitlab-pages + sudo -u git -H git checkout v0.2.4 + sudo -u git -H make + ``` + +1. Go to the GitLab installation directory: + + ```bash + cd /home/git/gitlab + ``` + +1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and + the `host` to the FQDN under which GitLab Pages will be served: + + ```yaml + ## GitLab Pages + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + # path: shared/pages + + host: example.io + port: 80 + https: false + ``` + +1. Copy the `gitlab-pages-ssl` Nginx configuration file: + + ```bash + sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf + sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf + ``` + + Replace `gitlab-pages-ssl` with `gitlab-pages` if you are not using SSL. + +1. Restart NGINX +1. [Restart GitLab][restart] + +## NGINX caveats + +>**Note:** +The following information applies only for installations from source. + +Be extra careful when setting up the domain name in the NGINX config. You must +not remove the backslashes. + +If your GitLab pages domain is `example.io`, replace: + +```bash +server_name ~^.*\.YOUR_GITLAB_PAGES\.DOMAIN$; +``` + +with: + +``` +server_name ~^.*\.example\.io$; +``` + +If you are using a subdomain, make sure to escape all dots (`.`) except from +the first one with a backslash (\). For example `pages.example.io` would be: + +``` +server_name ~^.*\.pages\.example\.io$; +``` + +## 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 `/home/git/gitlab/shared/pages`. + If you wish to store them in another location you must set it up in + `gitlab.yml` under the `pages` section: + + ```yaml + pages: + enabled: true + # The location where pages are stored (default: shared/pages). + path: /mnt/storage/pages + ``` + +1. [Restart GitLab][restart] + +## 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. + +[backup]: ../raketasks/backup_restore.md +[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 -- cgit v1.2.1 From 01b14b2d050ed32dd25907cda5a5ea49dc99d6a0 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 15:56:29 +0000 Subject: Fix rubocop error from pages EE->CE port --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0015ddf902d..71046d7860e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -268,8 +268,8 @@ Settings.registry['path'] = File.expand_path(Settings.registry['path' 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['host'] ||= "example.com" 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) -- cgit v1.2.1 From e8fd6c7e66f8a37b60d9c8cf17ec601df465dc2a Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 2 Feb 2017 17:41:21 -0200 Subject: Update V3 to V4 docs --- doc/api/README.md | 1 + doc/api/v3_to_v4.md | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 doc/api/v3_to_v4.md diff --git a/doc/api/README.md b/doc/api/README.md index 20f28e8d30e..b334ca46caf 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -49,6 +49,7 @@ following locations: - [Todos](todos.md) - [Users](users.md) - [Validate CI configuration](ci/lint.md) +- [V3 to V4](v3_to_v4.md) - [Version](version.md) ### Internal CI API diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md new file mode 100644 index 00000000000..01de1e59fcb --- /dev/null +++ b/doc/api/v3_to_v4.md @@ -0,0 +1,10 @@ +# V3 to V4 version + +Our V4 API version is currently available as *Beta*! It means that V3 +will still be supported and remain unchanged for now, but be aware that the following +changes are in V4: + +### Changes + +- Removed `/projects/:search` (use: `/projects?search=x`) + -- cgit v1.2.1 From 3c571baf5d8e2a81f8722a3b5cd38ae1f51988d8 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 17:28:53 +0000 Subject: =?UTF-8?q?Use=20non-downtime=20migration=20for=20ApplicationSetti?= =?UTF-8?q?ng=E2=80=99s=20max=5Fpages=5Fsize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...215132013_add_pages_size_to_application_settings.rb | 11 ++++++++++- db/migrate/20160210105555_create_pages_domain.rb | 2 ++ db/schema.rb | 18 +++++++++--------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb index e7fb73190e8..f3a663f805b 100644 --- a/db/migrate/20151215132013_add_pages_size_to_application_settings.rb +++ b/db/migrate/20151215132013_add_pages_size_to_application_settings.rb @@ -1,5 +1,14 @@ class AddPagesSizeToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + def up - add_column :application_settings, :max_pages_size, :integer, default: 100, null: false + 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 diff --git a/db/migrate/20160210105555_create_pages_domain.rb b/db/migrate/20160210105555_create_pages_domain.rb index 9af206143bd..0e8507c7e9a 100644 --- a/db/migrate/20160210105555_create_pages_domain.rb +++ b/db/migrate/20160210105555_create_pages_domain.rb @@ -1,4 +1,6 @@ class CreatePagesDomain < ActiveRecord::Migration + DOWNTIME = false + def change create_table :pages_domains do |t| t.integer :project_id diff --git a/db/schema.rb b/db/schema.rb index dc3d8c22e8d..9863367e312 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,7 +61,6 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false @@ -99,17 +98,18 @@ ActiveRecord::Schema.define(version: 20170130204620) do t.text "help_page_text_html" t.text "shared_runners_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_bitmaps_enabled", default: true, 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_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.string "plantuml_url" t.boolean "plantuml_enabled" + t.integer "max_pages_size", default: 100, null: false end create_table "audit_events", force: :cascade do |t| @@ -857,11 +857,11 @@ ActiveRecord::Schema.define(version: 20170130204620) do 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" + 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 -- cgit v1.2.1 From a14f11390db5b848989e00b05a63e160bf5e0fdf Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 17:31:54 +0000 Subject: Added Changelog for Pages to CE port --- changelogs/unreleased/jej-pages-picked-from-ee.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/jej-pages-picked-from-ee.yml diff --git a/changelogs/unreleased/jej-pages-picked-from-ee.yml b/changelogs/unreleased/jej-pages-picked-from-ee.yml new file mode 100644 index 00000000000..ee4a43a93db --- /dev/null +++ b/changelogs/unreleased/jej-pages-picked-from-ee.yml @@ -0,0 +1,4 @@ +--- +title: Added GitLab Pages to CE +merge_request: 8463 +author: -- cgit v1.2.1 From c6dac7da6f5402333ccdd6f95d9b7b11300719f4 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 21:23:42 +0000 Subject: Mentioned pages to CE port in project/pages/index.md --- doc/user/project/pages/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index a07c23a3274..fe477b1ed0b 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -3,6 +3,7 @@ > **Notes:** > - This feature was [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 document is about the user guide. To learn how to enable GitLab Pages > across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md). -- cgit v1.2.1 From caa7344d48080c07658331b991399cf7a86f926f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 2 Feb 2017 16:44:58 -0500 Subject: Update CHANGELOG.md for 8.16.4 [ci skip] --- CHANGELOG.md | 15 +++++++++++++++ changelogs/unreleased/19164-mobile-settings.yml | 4 ---- ...n-does-not-suggest-by-non-ascii-characters-in-name.yml | 4 ---- ...6860-27151-fix-discussion-note-permalink-collapsed.yml | 4 ---- ...oes-not-allow-filtering-labels-with-multiple-words.yml | 4 ---- ...bel-for-references-the-wrong-associated-text-input.yml | 4 ---- changelogs/unreleased/fix-cancel-integration-settings.yml | 4 ---- .../fix-filtering-username-with-multiple-words.yml | 4 ---- .../unreleased/fix-import-user-validation-error.yml | 4 ---- changelogs/unreleased/fix-search-bar-search-param.yml | 4 ---- .../sh-add-project-id-index-project-authorizations.yml | 4 ---- changelogs/unreleased/snippet-spam.yml | 4 ---- changelogs/unreleased/zj-slow-service-fetch.yml | 4 ---- 13 files changed, 15 insertions(+), 48 deletions(-) delete mode 100644 changelogs/unreleased/19164-mobile-settings.yml delete mode 100644 changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml delete mode 100644 changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml delete mode 100644 changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml delete mode 100644 changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml delete mode 100644 changelogs/unreleased/fix-cancel-integration-settings.yml delete mode 100644 changelogs/unreleased/fix-filtering-username-with-multiple-words.yml delete mode 100644 changelogs/unreleased/fix-import-user-validation-error.yml delete mode 100644 changelogs/unreleased/fix-search-bar-search-param.yml delete mode 100644 changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml delete mode 100644 changelogs/unreleased/snippet-spam.yml delete mode 100644 changelogs/unreleased/zj-slow-service-fetch.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e02b1ae1c..71d38e5453d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.16.4 (2017-02-02) + +- Support non-ASCII characters in GFM autocomplete. !8729 +- Fix search bar search param encoding. !8753 +- Fix project name label's for reference in project settings. !8795 +- Fix filtering with multiple words. !8830 +- Fixed services form cancel not redirecting back the integrations settings view. !8843 +- Fix filtering usernames with multiple words. !8851 +- Improve performance of slash commands. !8876 +- Remove old project members when retrying an export. +- Fix permalink discussion note being collapsed. +- Add project ID index to `project_authorizations` table to optimize queries. +- Check public snippets for spam. +- 19164 Add settings dropdown to mobile screens. + ## 8.16.3 (2017-01-27) - Add caching of droplab ajax requests. !8725 diff --git a/changelogs/unreleased/19164-mobile-settings.yml b/changelogs/unreleased/19164-mobile-settings.yml deleted file mode 100644 index c26a20f87e2..00000000000 --- a/changelogs/unreleased/19164-mobile-settings.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 19164 Add settings dropdown to mobile screens -merge_request: -author: diff --git a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml deleted file mode 100644 index 1758ed9e9ea..00000000000 --- a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Support non-ASCII characters in GFM autocomplete -merge_request: 8729 -author: diff --git a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml deleted file mode 100644 index ddd454da376..00000000000 --- a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix permalink discussion note being collapsed -merge_request: -author: diff --git a/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml deleted file mode 100644 index 6e036923158..00000000000 --- a/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix filtering with multiple words -merge_request: 8830 -author: diff --git a/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml deleted file mode 100644 index 2591f161bc5..00000000000 --- a/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix project name label's for reference in project settings -merge_request: 8795 -author: diff --git a/changelogs/unreleased/fix-cancel-integration-settings.yml b/changelogs/unreleased/fix-cancel-integration-settings.yml deleted file mode 100644 index 294b0aa5db9..00000000000 --- a/changelogs/unreleased/fix-cancel-integration-settings.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed services form cancel not redirecting back the integrations settings view -merge_request: 8843 -author: diff --git a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml deleted file mode 100644 index 3513f5afdfb..00000000000 --- a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix filtering usernames with multiple words -merge_request: 8851 -author: diff --git a/changelogs/unreleased/fix-import-user-validation-error.yml b/changelogs/unreleased/fix-import-user-validation-error.yml deleted file mode 100644 index 985a3b0b26f..00000000000 --- a/changelogs/unreleased/fix-import-user-validation-error.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove old project members when retrying an export -merge_request: -author: diff --git a/changelogs/unreleased/fix-search-bar-search-param.yml b/changelogs/unreleased/fix-search-bar-search-param.yml deleted file mode 100644 index 4df14d3bf13..00000000000 --- a/changelogs/unreleased/fix-search-bar-search-param.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix search bar search param encoding -merge_request: 8753 -author: diff --git a/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml deleted file mode 100644 index e69fcd2aa63..00000000000 --- a/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add project ID index to `project_authorizations` table to optimize queries -merge_request: -author: diff --git a/changelogs/unreleased/snippet-spam.yml b/changelogs/unreleased/snippet-spam.yml deleted file mode 100644 index 4867f088953..00000000000 --- a/changelogs/unreleased/snippet-spam.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Check public snippets for spam -merge_request: -author: diff --git a/changelogs/unreleased/zj-slow-service-fetch.yml b/changelogs/unreleased/zj-slow-service-fetch.yml deleted file mode 100644 index 8037361d2fc..00000000000 --- a/changelogs/unreleased/zj-slow-service-fetch.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve performance of slash commands -merge_request: 8876 -author: -- cgit v1.2.1 From 67c8526033241f6bd190fa16622970b3919e6dbd Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 2 Feb 2017 22:21:06 +0000 Subject: Ported max_pages_size in settings API to CE --- lib/api/settings.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/settings.rb b/lib/api/settings.rb index c5eff16a5de..5206ee4f521 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -57,6 +57,7 @@ module API requires :shared_runners_text, type: String, desc: 'Shared runners text ' end 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 :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' given metrics_enabled: ->(val) { val } do @@ -115,7 +116,7 @@ module API :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, :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, :akismet_enabled, :admin_notification_email, :sentry_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, -- cgit v1.2.1 From 80bd5717367ae4af63ac476244e6adee8ccefad5 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 3 Feb 2017 14:44:12 +0600 Subject: fixes flickers in avatar mention dropdown --- app/assets/stylesheets/framework/animations.scss | 21 +++++++++++++++++++++ app/assets/stylesheets/framework/markdown_area.scss | 1 + 2 files changed, 22 insertions(+) diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 8d38fc78a19..f1ff9faef7f 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -71,6 +71,27 @@ transition: $unfoldedTransitions; } +@mixin disableAllAnimation { + /*CSS transitions*/ + -o-transition-property: none !important; + -moz-transition-property: none !important; + -ms-transition-property: none !important; + -webkit-transition-property: none !important; + transition-property: none !important; + /*CSS transforms*/ + -o-transform: none !important; + -moz-transform: none !important; + -ms-transform: none !important; + -webkit-transform: none !important; + transform: none !important; + /*CSS animations*/ + -webkit-animation: none !important; + -moz-animation: none !important; + -o-animation: none !important; + -ms-animation: none !important; + animation: none !important; +} + @function unfoldTransition ($transition) { // Default values $property: all; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 5bff694658c..d4758d90352 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -159,6 +159,7 @@ .cur { .avatar { border: 1px solid $white-light; + @include disableAllAnimation; } } } -- cgit v1.2.1 From 22b77948ec8743574bdd74c98888c5c91eb1bb74 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 3 Feb 2017 14:56:49 +0600 Subject: adds changelog --- .../unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml diff --git a/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml b/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml new file mode 100644 index 00000000000..a5bb37ec8a9 --- /dev/null +++ b/changelogs/unreleased/27602-fix-avatar-border-flicker-mention-dropdown.yml @@ -0,0 +1,4 @@ +--- +title: Fixes flickering of avatar border in mention dropdown +merge_request: 8950 +author: -- cgit v1.2.1 From f30e2a6ec7c011d0649aad9f118bf8c5a57ecdbc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 3 Feb 2017 17:29:08 +0800 Subject: Use message_id_regexp variable for the regexp Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8620#note_22021001 --- lib/gitlab/incoming_email.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index a386d9b36fb..0ea42148c58 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -35,7 +35,9 @@ module Gitlab end def key_from_fallback_message_id(mail_id) - mail_id[/\Areply\-(.+)@#{Gitlab.config.gitlab.host}\z/, 1] + message_id_regexp = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\z/ + + mail_id[message_id_regexp, 1] end def scan_fallback_references(references) -- cgit v1.2.1 From 849d09cfd692c0c54d45baf1ce71cd9c1ea2c6ab Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 3 Feb 2017 17:30:54 +0800 Subject: Use references variable Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8620#note_22020035 --- lib/gitlab/email/receiver.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index fa08b5c668f..b64db5d01ae 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -56,7 +56,9 @@ module Gitlab end def key_from_additional_headers(mail) - find_key_from_references(ensure_references_array(mail.references)) + references = ensure_references_array(mail.references) + + find_key_from_references(references) end def ensure_references_array(references) -- cgit v1.2.1 From 2f80cbb6759beb412491f9b1b4f0dbcbec6619c0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 3 Feb 2017 17:37:06 +0800 Subject: Freeze regexp and add a comment Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8620#note_21590440 --- lib/gitlab/incoming_email.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 0ea42148c58..a492f904303 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -35,12 +35,14 @@ module Gitlab end def key_from_fallback_message_id(mail_id) - message_id_regexp = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\z/ + message_id_regexp = + /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\z/.freeze mail_id[message_id_regexp, 1] end def scan_fallback_references(references) + # It's looking for each <...> references.scan(/(?!<)[^<>]+(?=>)/.freeze) end -- cgit v1.2.1 From 037b4fe939696eebe6295a858470f2661d1e3878 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 27 Jan 2017 22:24:08 +0000 Subject: First iteration Create shared folder for vue common files Update paths Second iteration - refactor main component to be 100% reusable between the 3 tables --- .../javascripts/commit/pipelines_bundle.js.es6 | 61 +++++ .../javascripts/commit/pipelines_service.js.es6 | 77 ++++++ .../javascripts/commit/pipelines_store.js.es6 | 30 +++ .../components/environment_item.js.es6 | 2 +- .../javascripts/vue_common_component/commit.js.es6 | 163 ------------- .../javascripts/vue_pipelines_index/index.js.es6 | 2 +- .../vue_shared/components/commit.js.es6 | 163 +++++++++++++ .../vue_shared/components/pipelines_table.js.es6 | 270 +++++++++++++++++++++ .../vue_shared/vue_resource_interceptor.js.es6 | 10 + app/views/projects/commit/_pipelines_list.haml | 15 -- app/views/projects/commit/pipelines.html.haml | 27 ++- config/application.rb | 1 + .../vue_common_components/commit_spec.js.es6 | 2 +- 13 files changed, 641 insertions(+), 182 deletions(-) create mode 100644 app/assets/javascripts/commit/pipelines_bundle.js.es6 create mode 100644 app/assets/javascripts/commit/pipelines_service.js.es6 create mode 100644 app/assets/javascripts/commit/pipelines_store.js.es6 delete mode 100644 app/assets/javascripts/vue_common_component/commit.js.es6 create mode 100644 app/assets/javascripts/vue_shared/components/commit.js.es6 create mode 100644 app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 create mode 100644 app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 diff --git a/app/assets/javascripts/commit/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines_bundle.js.es6 new file mode 100644 index 00000000000..d2547f0b4a8 --- /dev/null +++ b/app/assets/javascripts/commit/pipelines_bundle.js.es6 @@ -0,0 +1,61 @@ +/* eslint-disable no-new */ +/* global Vue, VueResource */ + +//= require vue +//= require vue-resource +//= require ./pipelines_store +//= require ./pipelines_service +//= require vue_shared/components/commit +//= require vue_shared/vue_resource_interceptor +//= require vue_shared/components/pipelines_table + +/** + * Commits View > Pipelines Tab > Pipelines Table. + * + * Renders Pipelines table in pipelines tab in the commits show view. + * + * Uses `pipelines-table-component` to render Pipelines table with an API call. + * Endpoint is provided in HTML and passed as scope. + * We need a store to make the request and store the received environemnts. + * + * Necessary SVG in the table are provided as props. This should be refactored + * as soon as we have Webpack and can load them directly into JS files. + */ +(() => { + window.gl = window.gl || {}; + gl.Commits = gl.Commits || {}; + + if (gl.Commits.PipelinesTableView) { + gl.Commits.PipelinesTableView.$destroy(true); + } + + gl.Commits.PipelinesTableView = new Vue({ + + el: document.querySelector('#commit-pipeline-table-view'), + + /** + * Accesses the DOM to provide the needed data. + * Returns the necessary props to render `pipelines-table-component` component. + * + * @return {Object} Props for `pipelines-table-component` + */ + data() { + const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + + return { + scope: pipelinesTableData.pipelinesData, + store: new CommitsPipelineStore(), + service: new PipelinesService(), + svgs: pipelinesTableData, + }; + }, + + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + template: ` + + `, + }); +}); diff --git a/app/assets/javascripts/commit/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines_service.js.es6 new file mode 100644 index 00000000000..7d773e0d361 --- /dev/null +++ b/app/assets/javascripts/commit/pipelines_service.js.es6 @@ -0,0 +1,77 @@ +/* globals Vue */ +/* eslint-disable no-unused-vars, no-param-reassign */ + +/** + * Pipelines service. + * + * Used to fetch the data used to render the pipelines table. + * Used Vue.Resource + */ + +window.gl = window.gl || {}; +gl.pipelines = gl.pipelines || {}; + +class PipelinesService { + constructor(root) { + Vue.http.options.root = root; + + this.pipelines = Vue.resource(root); + + Vue.http.interceptors.push((request, next) => { + // needed in order to not break the tests. + if ($.rails) { + request.headers['X-CSRF-Token'] = $.rails.csrfToken(); + } + next(); + }); + } + + /** + * Given the root param provided when the class is initialized, will + * make a GET request. + * + * @return {Promise} + */ + all() { + return this.pipelines.get(); + } +} + +gl.pipelines.PipelinesService = PipelinesService; + +// const pageValues = (headers) => { +// const normalized = gl.utils.normalizeHeaders(headers); +// +// const paginationInfo = { +// perPage: +normalized['X-PER-PAGE'], +// page: +normalized['X-PAGE'], +// total: +normalized['X-TOTAL'], +// totalPages: +normalized['X-TOTAL-PAGES'], +// nextPage: +normalized['X-NEXT-PAGE'], +// previousPage: +normalized['X-PREV-PAGE'], +// }; +// +// return paginationInfo; +// }; + +// gl.PipelineStore = class { +// fetchDataLoop(Vue, pageNum, url, apiScope) { +// const goFetch = () => +// this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) +// .then((response) => { +// const pageInfo = pageValues(response.headers); +// this.pageInfo = Object.assign({}, this.pageInfo, pageInfo); +// +// const res = JSON.parse(response.body); +// this.count = Object.assign({}, this.count, res.count); +// this.pipelines = Object.assign([], this.pipelines, res); +// +// this.pageRequest = false; +// }, () => { +// this.pageRequest = false; +// return new Flash('Something went wrong on our end.'); +// }); +// +// goFetch(); +// } +// }; diff --git a/app/assets/javascripts/commit/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines_store.js.es6 new file mode 100644 index 00000000000..bc748bece5d --- /dev/null +++ b/app/assets/javascripts/commit/pipelines_store.js.es6 @@ -0,0 +1,30 @@ +/* global gl, Flash */ +/* eslint-disable no-param-reassign, no-underscore-dangle */ +/*= require vue_realtime_listener/index.js */ + +/** + * Pipelines' Store for commits view. + * + * Used to store the Pipelines rendered in the commit view in the pipelines table. + * + * TODO: take care of timeago instances in here + */ + +(() => { + const CommitPipelineStore = { + state: {}, + + create() { + this.state.pipelines = []; + + return this; + }, + + storePipelines(pipelines = []) { + this.state.pipelines = pipelines; + return pipelines; + }, + }; + + return CommitPipelineStore; +})(); diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 0e6bc3fdb2c..c7cb0913213 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -3,7 +3,7 @@ /*= require timeago */ /*= require lib/utils/text_utility */ -/*= require vue_common_component/commit */ +/*= require vue_shared/components/commit */ /*= require ./environment_actions */ /*= require ./environment_external_url */ /*= require ./environment_stop */ diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6 deleted file mode 100644 index 62a22e39a3b..00000000000 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ /dev/null @@ -1,163 +0,0 @@ -/*= require vue */ -/* global Vue */ -(() => { - window.gl = window.gl || {}; - - window.gl.CommitComponent = Vue.component('commit-component', { - - props: { - /** - * Indicates the existance of a tag. - * Used to render the correct icon, if true will render `fa-tag` icon, - * if false will render `fa-code-fork` icon. - */ - tag: { - type: Boolean, - required: false, - default: false, - }, - - /** - * If provided is used to render the branch name and url. - * Should contain the following properties: - * name - * ref_url - */ - commitRef: { - type: Object, - required: false, - default: () => ({}), - }, - - /** - * Used to link to the commit sha. - */ - commitUrl: { - type: String, - required: false, - default: '', - }, - - /** - * Used to show the commit short sha that links to the commit url. - */ - shortSha: { - type: String, - required: false, - default: '', - }, - - /** - * If provided shows the commit tile. - */ - title: { - type: String, - required: false, - default: '', - }, - - /** - * If provided renders information about the author of the commit. - * When provided should include: - * `avatar_url` to render the avatar icon - * `web_url` to link to user profile - * `username` to render alt and title tags - */ - author: { - type: Object, - required: false, - default: () => ({}), - }, - - commitIconSvg: { - type: String, - required: false, - }, - }, - - computed: { - /** - * Used to verify if all the properties needed to render the commit - * ref section were provided. - * - * TODO: Improve this! Use lodash _.has when we have it. - * - * @returns {Boolean} - */ - hasCommitRef() { - return this.commitRef && this.commitRef.name && this.commitRef.ref_url; - }, - - /** - * Used to verify if all the properties needed to render the commit - * author section were provided. - * - * TODO: Improve this! Use lodash _.has when we have it. - * - * @returns {Boolean} - */ - hasAuthor() { - return this.author && - this.author.avatar_url && - this.author.web_url && - this.author.username; - }, - - /** - * If information about the author is provided will return a string - * to be rendered as the alt attribute of the img tag. - * - * @returns {String} - */ - userImageAltDescription() { - return this.author && - this.author.username ? `${this.author.username}'s avatar` : null; - }, - }, - - template: ` -
- -
- - -
- - - {{commitRef.name}} - - -
- - - {{shortSha}} - - -

- - - - - - - {{title}} - - - - Cant find HEAD commit for this branch - -

-
- `, - }); -})(); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index edd01f17a97..36f861a7d02 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,5 +1,5 @@ /* global Vue, VueResource, gl */ -/*= require vue_common_component/commit */ +/*= require vue_shared/components/commit */ /*= require vue_pagination/index */ /*= require vue-resource /*= require boards/vue_resource_interceptor */ diff --git a/app/assets/javascripts/vue_shared/components/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js.es6 new file mode 100644 index 00000000000..62a22e39a3b --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/commit.js.es6 @@ -0,0 +1,163 @@ +/*= require vue */ +/* global Vue */ +(() => { + window.gl = window.gl || {}; + + window.gl.CommitComponent = Vue.component('commit-component', { + + props: { + /** + * Indicates the existance of a tag. + * Used to render the correct icon, if true will render `fa-tag` icon, + * if false will render `fa-code-fork` icon. + */ + tag: { + type: Boolean, + required: false, + default: false, + }, + + /** + * If provided is used to render the branch name and url. + * Should contain the following properties: + * name + * ref_url + */ + commitRef: { + type: Object, + required: false, + default: () => ({}), + }, + + /** + * Used to link to the commit sha. + */ + commitUrl: { + type: String, + required: false, + default: '', + }, + + /** + * Used to show the commit short sha that links to the commit url. + */ + shortSha: { + type: String, + required: false, + default: '', + }, + + /** + * If provided shows the commit tile. + */ + title: { + type: String, + required: false, + default: '', + }, + + /** + * If provided renders information about the author of the commit. + * When provided should include: + * `avatar_url` to render the avatar icon + * `web_url` to link to user profile + * `username` to render alt and title tags + */ + author: { + type: Object, + required: false, + default: () => ({}), + }, + + commitIconSvg: { + type: String, + required: false, + }, + }, + + computed: { + /** + * Used to verify if all the properties needed to render the commit + * ref section were provided. + * + * TODO: Improve this! Use lodash _.has when we have it. + * + * @returns {Boolean} + */ + hasCommitRef() { + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; + }, + + /** + * Used to verify if all the properties needed to render the commit + * author section were provided. + * + * TODO: Improve this! Use lodash _.has when we have it. + * + * @returns {Boolean} + */ + hasAuthor() { + return this.author && + this.author.avatar_url && + this.author.web_url && + this.author.username; + }, + + /** + * If information about the author is provided will return a string + * to be rendered as the alt attribute of the img tag. + * + * @returns {String} + */ + userImageAltDescription() { + return this.author && + this.author.username ? `${this.author.username}'s avatar` : null; + }, + }, + + template: ` +
+ +
+ + +
+ + + {{commitRef.name}} + + +
+ + + {{shortSha}} + + +

+ + + + + + + {{title}} + + + + Cant find HEAD commit for this branch + +

+
+ `, + }); +})(); diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 new file mode 100644 index 00000000000..0b20bf66a69 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -0,0 +1,270 @@ +/* eslint-disable no-param-reassign, no-new */ +/* global Vue */ +/* global PipelinesService */ +/* global Flash */ + +//= require vue_pipelines_index/status.js.es6 +//= require vue_pipelines_index/pipeline_url.js.es6 +//= require vue_pipelines_index/stage.js.es6 +//= require vue_pipelines_index/pipeline_actions.js.es6 +//= require vue_pipelines_index/time_ago.js.es6 +//= require vue_pipelines_index/pipelines.js.es6 + +(() => { + window.gl = window.gl || {}; + gl.pipelines = gl.pipelines || {}; + + gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', { + + props: { + + /** + * Stores the Pipelines to render. + * It's passed as a prop to allow different stores to use this Component. + * Different API calls can result in different responses, using a custom + * store allows us to use the same pipeline component. + */ + store: { + type: Object, + required: true, + default: () => ({}), + }, + + /** + * Will be used to fetch the needed data. + * This component is used in different and therefore different API calls + * to different endpoints will be made. To guarantee this is a reusable + * component, the endpoint must be provided. + */ + endpoint: { + type: String, + required: true, + }, + + /** + * Remove this. Find a better way to do this. don't want to provide this 3 times. + */ + svgs: { + type: Object, + required: true, + default: () => ({}), + }, + }, + + components: { + 'commit-component': gl.CommitComponent, + runningPipeline: gl.VueRunningPipeline, + pipelineActions: gl.VuePipelineActions, + 'vue-stage': gl.VueStage, + pipelineUrl: gl.VuePipelineUrl, + pipelineHead: gl.VuePipelineHead, + statusScope: gl.VueStatusScope, + }, + + data() { + return { + state: this.store.state, + isLoading: false, + }; + }, + + computed: { + /** + * If provided, returns the commit tag. + * + * @returns {Object|Undefined} + */ + commitAuthor() { + if (this.pipeline && + this.pipeline.commit && + this.pipeline.commit.author) { + return this.pipeline.commit.author; + } + + return undefined; + }, + + /** + * If provided, returns the commit tag. + * + * @returns {String|Undefined} + */ + commitTag() { + if (this.model.last_deployment && + this.model.last_deployment.tag) { + return this.model.last_deployment.tag; + } + return undefined; + }, + + /** + * If provided, returns the commit ref. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.pipeline.ref) { + return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { + if (prop === 'url') { + accumulator.path = this.pipeline.ref[prop]; + } else { + accumulator[prop] = this.pipeline.ref[prop]; + } + return accumulator; + }, {}); + } + + return undefined; + }, + + /** + * If provided, returns the commit url. + * + * @returns {String|Undefined} + */ + commitUrl() { + if (this.pipeline.commit && + this.pipeline.commit.commit_path) { + return this.pipeline.commit.commit_path; + } + return undefined; + }, + + /** + * If provided, returns the commit short sha. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if (this.pipeline.commit && + this.pipeline.commit.short_id) { + return this.pipeline.commit.short_id; + } + return undefined; + }, + + /** + * If provided, returns the commit title. + * + * @returns {String|Undefined} + */ + commitTitle() { + if (this.pipeline.commit && + this.pipeline.commit.title) { + return this.pipeline.commit.title; + } + return undefined; + }, + + /** + * Figure this out! + */ + author(pipeline) { + if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; + if (pipeline.commit.author) return pipeline.commit.author; + return { + avatar_url: pipeline.commit.author_gravatar_url, + web_url: `mailto:${pipeline.commit.author_email}`, + username: pipeline.commit.author_name, + }; + }, + + /** + * Figure this out + */ + match(string) { + return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); + }, + }, + + /** + * When the component is created the service to fetch the data will be + * initialized with the correct endpoint. + * + * A request to fetch the pipelines will be made. + * In case of a successfull response we will store the data in the provided + * store, in case of a failed response we need to warn the user. + * + */ + created() { + gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); + + this.isLoading = true; + + return gl.pipelines.pipelinesService.all() + .then(resp => resp.json()) + .then((json) => { + this.store.storePipelines(json); + this.isLoading = false; + }).catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the pipelines.', 'alert'); + }); + }, + // this need to be reusable between the 3 tables :/ + template: ` +
+
+ +
+ + +
+

+ You don't have any pipelines. +

+ Put get started with pipelines button here!!! +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusPipelineCommitStages
+ + + + +
+
+
+ `, + }); +})(); diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 new file mode 100644 index 00000000000..54c2b4ad369 --- /dev/null +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -0,0 +1,10 @@ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */ +/* global Vue */ + +Vue.http.interceptors.push((request, next) => { + Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; + + next(function (response) { + Vue.activeResources -= 1; + }); +}); diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 1164627fa11..e69de29bb2d 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -1,15 +0,0 @@ -%div - - if pipelines.blank? - %div - .nothing-here-block No pipelines to show - - else - .table-holder.pipelines - %table.table.ci-table.js-pipeline-table - %thead - %th.pipeline-status Status - %th.pipeline-info Pipeline - %th.pipeline-commit Commit - %th.pipeline-stages Stages - %th.pipeline-date - %th.pipeline-actions - = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index 89968cf4e0d..f4937a03f03 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -2,4 +2,29 @@ = render 'commit_box' = render 'ci_menu' -= render 'pipelines_list', pipelines: @pipelines + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag("commit/pipelines_bundle.js") + +#commit-pipeline-table-view{ data: { pipelines_data: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)}} +.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), + "icon_status_canceled" => custom_icon("icon_status_canceled"), + "icon_status_running" => custom_icon("icon_status_running"), + "icon_status_skipped" => custom_icon("icon_status_skipped"), + "icon_status_created" => custom_icon("icon_status_created"), + "icon_status_pending" => custom_icon("icon_status_pending"), + "icon_status_success" => custom_icon("icon_status_success"), + "icon_status_failed" => custom_icon("icon_status_failed"), + "icon_status_warning" => custom_icon("icon_status_warning"), + "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), + "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), + "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), + "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), + "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), + "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), + "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), + "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), + "icon_play" => custom_icon("icon_play"), + "icon_timer" => custom_icon("icon_timer"), + "icon_status_manual" => custom_icon("icon_status_manual"), +} } diff --git a/config/application.rb b/config/application.rb index f00e58a36ca..88c5e27d17d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -105,6 +105,7 @@ module Gitlab config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js" config.assets.precompile << "boards/test_utils/simulate_drag.js" config.assets.precompile << "environments/environments_bundle.js" + config.assets.precompile << "commit/pipelines_bundle.js" config.assets.precompile << "blob_edit/blob_edit_bundle.js" config.assets.precompile << "snippet/snippet_bundle.js" config.assets.precompile << "terminal/terminal_bundle.js" diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6 index d6c6f786fb1..caf84ec63e2 100644 --- a/spec/javascripts/vue_common_components/commit_spec.js.es6 +++ b/spec/javascripts/vue_common_components/commit_spec.js.es6 @@ -1,4 +1,4 @@ -//= require vue_common_component/commit +//= require vue_shared/components/commit describe('Commit component', () => { let props; -- cgit v1.2.1 From 9972f59f94ab017d27d9278dd1c9dd89da489e64 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 28 Jan 2017 18:37:02 +0000 Subject: Use single source of truth for vue_resource_interceptor --- app/assets/javascripts/boards/boards_bundle.js.es6 | 2 +- app/assets/javascripts/boards/vue_resource_interceptor.js.es6 | 10 ---------- app/assets/javascripts/vue_pipelines_index/index.js.es6 | 2 +- .../javascripts/vue_shared/components/pipelines_table.js.es6 | 5 ++++- 4 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 app/assets/javascripts/boards/vue_resource_interceptor.js.es6 diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index f9766471780..5b53cfe59cd 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -13,7 +13,7 @@ //= require ./components/board //= require ./components/board_sidebar //= require ./components/new_list_dropdown -//= require ./vue_resource_interceptor +//= require vue_shared/vue_resource_interceptor $(() => { const $boardApp = document.getElementById('board-app'); diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 deleted file mode 100644 index 54c2b4ad369..00000000000 --- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */ -/* global Vue */ - -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next(function (response) { - Vue.activeResources -= 1; - }); -}); diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 36f861a7d02..9ca7b1a746c 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -2,7 +2,7 @@ /*= require vue_shared/components/commit */ /*= require vue_pagination/index */ /*= require vue-resource -/*= require boards/vue_resource_interceptor */ +/*= require vue_shared/vue_resource_interceptor */ /*= require ./status.js.es6 */ /*= require ./store.js.es6 */ /*= require ./pipeline_url.js.es6 */ diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index 0b20bf66a69..f602a0c44c2 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -19,10 +19,13 @@ props: { /** - * Stores the Pipelines to render. + * Object used to store the Pipelines to render. * It's passed as a prop to allow different stores to use this Component. * Different API calls can result in different responses, using a custom * store allows us to use the same pipeline component. + * + * Note: All provided stores need to have a `storePipelines` method. + * Find a better way to do this. */ store: { type: Object, -- cgit v1.2.1 From 7ad626e348faaea6f186759dada36079d531f6fd Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 28 Jan 2017 18:39:01 +0000 Subject: Use same folder structure in spec for vue shared resources --- .../commit/pipelines/pipelines_bundle.js.es6 | 61 ++++++++++ .../commit/pipelines/pipelines_service.js.es6 | 77 ++++++++++++ .../commit/pipelines/pipelines_store.js.es6 | 30 +++++ .../javascripts/commit/pipelines_bundle.js.es6 | 61 ---------- .../javascripts/commit/pipelines_service.js.es6 | 77 ------------ .../javascripts/commit/pipelines_store.js.es6 | 30 ----- app/views/projects/commit/pipelines.html.haml | 2 +- config/application.rb | 2 +- .../vue_common_components/commit_spec.js.es6 | 131 --------------------- .../vue_shared/components/commit_spec.js.es6 | 131 +++++++++++++++++++++ 10 files changed, 301 insertions(+), 301 deletions(-) create mode 100644 app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 create mode 100644 app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 create mode 100644 app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 delete mode 100644 app/assets/javascripts/commit/pipelines_bundle.js.es6 delete mode 100644 app/assets/javascripts/commit/pipelines_service.js.es6 delete mode 100644 app/assets/javascripts/commit/pipelines_store.js.es6 delete mode 100644 spec/javascripts/vue_common_components/commit_spec.js.es6 create mode 100644 spec/javascripts/vue_shared/components/commit_spec.js.es6 diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 new file mode 100644 index 00000000000..d2547f0b4a8 --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -0,0 +1,61 @@ +/* eslint-disable no-new */ +/* global Vue, VueResource */ + +//= require vue +//= require vue-resource +//= require ./pipelines_store +//= require ./pipelines_service +//= require vue_shared/components/commit +//= require vue_shared/vue_resource_interceptor +//= require vue_shared/components/pipelines_table + +/** + * Commits View > Pipelines Tab > Pipelines Table. + * + * Renders Pipelines table in pipelines tab in the commits show view. + * + * Uses `pipelines-table-component` to render Pipelines table with an API call. + * Endpoint is provided in HTML and passed as scope. + * We need a store to make the request and store the received environemnts. + * + * Necessary SVG in the table are provided as props. This should be refactored + * as soon as we have Webpack and can load them directly into JS files. + */ +(() => { + window.gl = window.gl || {}; + gl.Commits = gl.Commits || {}; + + if (gl.Commits.PipelinesTableView) { + gl.Commits.PipelinesTableView.$destroy(true); + } + + gl.Commits.PipelinesTableView = new Vue({ + + el: document.querySelector('#commit-pipeline-table-view'), + + /** + * Accesses the DOM to provide the needed data. + * Returns the necessary props to render `pipelines-table-component` component. + * + * @return {Object} Props for `pipelines-table-component` + */ + data() { + const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + + return { + scope: pipelinesTableData.pipelinesData, + store: new CommitsPipelineStore(), + service: new PipelinesService(), + svgs: pipelinesTableData, + }; + }, + + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + template: ` + + `, + }); +}); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 new file mode 100644 index 00000000000..7d773e0d361 --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 @@ -0,0 +1,77 @@ +/* globals Vue */ +/* eslint-disable no-unused-vars, no-param-reassign */ + +/** + * Pipelines service. + * + * Used to fetch the data used to render the pipelines table. + * Used Vue.Resource + */ + +window.gl = window.gl || {}; +gl.pipelines = gl.pipelines || {}; + +class PipelinesService { + constructor(root) { + Vue.http.options.root = root; + + this.pipelines = Vue.resource(root); + + Vue.http.interceptors.push((request, next) => { + // needed in order to not break the tests. + if ($.rails) { + request.headers['X-CSRF-Token'] = $.rails.csrfToken(); + } + next(); + }); + } + + /** + * Given the root param provided when the class is initialized, will + * make a GET request. + * + * @return {Promise} + */ + all() { + return this.pipelines.get(); + } +} + +gl.pipelines.PipelinesService = PipelinesService; + +// const pageValues = (headers) => { +// const normalized = gl.utils.normalizeHeaders(headers); +// +// const paginationInfo = { +// perPage: +normalized['X-PER-PAGE'], +// page: +normalized['X-PAGE'], +// total: +normalized['X-TOTAL'], +// totalPages: +normalized['X-TOTAL-PAGES'], +// nextPage: +normalized['X-NEXT-PAGE'], +// previousPage: +normalized['X-PREV-PAGE'], +// }; +// +// return paginationInfo; +// }; + +// gl.PipelineStore = class { +// fetchDataLoop(Vue, pageNum, url, apiScope) { +// const goFetch = () => +// this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) +// .then((response) => { +// const pageInfo = pageValues(response.headers); +// this.pageInfo = Object.assign({}, this.pageInfo, pageInfo); +// +// const res = JSON.parse(response.body); +// this.count = Object.assign({}, this.count, res.count); +// this.pipelines = Object.assign([], this.pipelines, res); +// +// this.pageRequest = false; +// }, () => { +// this.pageRequest = false; +// return new Flash('Something went wrong on our end.'); +// }); +// +// goFetch(); +// } +// }; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 new file mode 100644 index 00000000000..bc748bece5d --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 @@ -0,0 +1,30 @@ +/* global gl, Flash */ +/* eslint-disable no-param-reassign, no-underscore-dangle */ +/*= require vue_realtime_listener/index.js */ + +/** + * Pipelines' Store for commits view. + * + * Used to store the Pipelines rendered in the commit view in the pipelines table. + * + * TODO: take care of timeago instances in here + */ + +(() => { + const CommitPipelineStore = { + state: {}, + + create() { + this.state.pipelines = []; + + return this; + }, + + storePipelines(pipelines = []) { + this.state.pipelines = pipelines; + return pipelines; + }, + }; + + return CommitPipelineStore; +})(); diff --git a/app/assets/javascripts/commit/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines_bundle.js.es6 deleted file mode 100644 index d2547f0b4a8..00000000000 --- a/app/assets/javascripts/commit/pipelines_bundle.js.es6 +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable no-new */ -/* global Vue, VueResource */ - -//= require vue -//= require vue-resource -//= require ./pipelines_store -//= require ./pipelines_service -//= require vue_shared/components/commit -//= require vue_shared/vue_resource_interceptor -//= require vue_shared/components/pipelines_table - -/** - * Commits View > Pipelines Tab > Pipelines Table. - * - * Renders Pipelines table in pipelines tab in the commits show view. - * - * Uses `pipelines-table-component` to render Pipelines table with an API call. - * Endpoint is provided in HTML and passed as scope. - * We need a store to make the request and store the received environemnts. - * - * Necessary SVG in the table are provided as props. This should be refactored - * as soon as we have Webpack and can load them directly into JS files. - */ -(() => { - window.gl = window.gl || {}; - gl.Commits = gl.Commits || {}; - - if (gl.Commits.PipelinesTableView) { - gl.Commits.PipelinesTableView.$destroy(true); - } - - gl.Commits.PipelinesTableView = new Vue({ - - el: document.querySelector('#commit-pipeline-table-view'), - - /** - * Accesses the DOM to provide the needed data. - * Returns the necessary props to render `pipelines-table-component` component. - * - * @return {Object} Props for `pipelines-table-component` - */ - data() { - const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; - - return { - scope: pipelinesTableData.pipelinesData, - store: new CommitsPipelineStore(), - service: new PipelinesService(), - svgs: pipelinesTableData, - }; - }, - - components: { - 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, - }, - - template: ` - - `, - }); -}); diff --git a/app/assets/javascripts/commit/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines_service.js.es6 deleted file mode 100644 index 7d773e0d361..00000000000 --- a/app/assets/javascripts/commit/pipelines_service.js.es6 +++ /dev/null @@ -1,77 +0,0 @@ -/* globals Vue */ -/* eslint-disable no-unused-vars, no-param-reassign */ - -/** - * Pipelines service. - * - * Used to fetch the data used to render the pipelines table. - * Used Vue.Resource - */ - -window.gl = window.gl || {}; -gl.pipelines = gl.pipelines || {}; - -class PipelinesService { - constructor(root) { - Vue.http.options.root = root; - - this.pipelines = Vue.resource(root); - - Vue.http.interceptors.push((request, next) => { - // needed in order to not break the tests. - if ($.rails) { - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); - } - next(); - }); - } - - /** - * Given the root param provided when the class is initialized, will - * make a GET request. - * - * @return {Promise} - */ - all() { - return this.pipelines.get(); - } -} - -gl.pipelines.PipelinesService = PipelinesService; - -// const pageValues = (headers) => { -// const normalized = gl.utils.normalizeHeaders(headers); -// -// const paginationInfo = { -// perPage: +normalized['X-PER-PAGE'], -// page: +normalized['X-PAGE'], -// total: +normalized['X-TOTAL'], -// totalPages: +normalized['X-TOTAL-PAGES'], -// nextPage: +normalized['X-NEXT-PAGE'], -// previousPage: +normalized['X-PREV-PAGE'], -// }; -// -// return paginationInfo; -// }; - -// gl.PipelineStore = class { -// fetchDataLoop(Vue, pageNum, url, apiScope) { -// const goFetch = () => -// this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) -// .then((response) => { -// const pageInfo = pageValues(response.headers); -// this.pageInfo = Object.assign({}, this.pageInfo, pageInfo); -// -// const res = JSON.parse(response.body); -// this.count = Object.assign({}, this.count, res.count); -// this.pipelines = Object.assign([], this.pipelines, res); -// -// this.pageRequest = false; -// }, () => { -// this.pageRequest = false; -// return new Flash('Something went wrong on our end.'); -// }); -// -// goFetch(); -// } -// }; diff --git a/app/assets/javascripts/commit/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines_store.js.es6 deleted file mode 100644 index bc748bece5d..00000000000 --- a/app/assets/javascripts/commit/pipelines_store.js.es6 +++ /dev/null @@ -1,30 +0,0 @@ -/* global gl, Flash */ -/* eslint-disable no-param-reassign, no-underscore-dangle */ -/*= require vue_realtime_listener/index.js */ - -/** - * Pipelines' Store for commits view. - * - * Used to store the Pipelines rendered in the commit view in the pipelines table. - * - * TODO: take care of timeago instances in here - */ - -(() => { - const CommitPipelineStore = { - state: {}, - - create() { - this.state.pipelines = []; - - return this; - }, - - storePipelines(pipelines = []) { - this.state.pipelines = pipelines; - return pipelines; - }, - }; - - return CommitPipelineStore; -})(); diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index f4937a03f03..09bd4288b9c 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -4,7 +4,7 @@ = render 'ci_menu' - content_for :page_specific_javascripts do - = page_specific_javascript_tag("commit/pipelines_bundle.js") + = page_specific_javascript_tag("commit/pipelines/pipelines_bundle.js") #commit-pipeline-table-view{ data: { pipelines_data: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)}} .pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), diff --git a/config/application.rb b/config/application.rb index 88c5e27d17d..281a660ddee 100644 --- a/config/application.rb +++ b/config/application.rb @@ -105,7 +105,7 @@ module Gitlab config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js" config.assets.precompile << "boards/test_utils/simulate_drag.js" config.assets.precompile << "environments/environments_bundle.js" - config.assets.precompile << "commit/pipelines_bundle.js" + config.assets.precompile << "commit/pipelines/pipelines_bundle.js" config.assets.precompile << "blob_edit/blob_edit_bundle.js" config.assets.precompile << "snippet/snippet_bundle.js" config.assets.precompile << "terminal/terminal_bundle.js" diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6 deleted file mode 100644 index caf84ec63e2..00000000000 --- a/spec/javascripts/vue_common_components/commit_spec.js.es6 +++ /dev/null @@ -1,131 +0,0 @@ -//= require vue_shared/components/commit - -describe('Commit component', () => { - let props; - let component; - - it('should render a code-fork icon if it does not represent a tag', () => { - setFixtures('
'); - component = new window.gl.CommitComponent({ - el: document.querySelector('.test-commit-container'), - propsData: { - tag: false, - commitRef: { - name: 'master', - ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', - }, - commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', - shortSha: 'b7836edd', - title: 'Commit message', - author: { - avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', - web_url: 'https://gitlab.com/jschatz1', - username: 'jschatz1', - }, - }, - }); - - expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork'); - }); - - describe('Given all the props', () => { - beforeEach(() => { - setFixtures('
'); - - props = { - tag: true, - commitRef: { - name: 'master', - ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', - }, - commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', - shortSha: 'b7836edd', - title: 'Commit message', - author: { - avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', - web_url: 'https://gitlab.com/jschatz1', - username: 'jschatz1', - }, - commitIconSvg: '', - }; - - component = new window.gl.CommitComponent({ - el: document.querySelector('.test-commit-container'), - propsData: props, - }); - }); - - it('should render a tag icon if it represents a tag', () => { - expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag'); - }); - - it('should render a link to the ref url', () => { - expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commitRef.ref_url); - }); - - it('should render the ref name', () => { - expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commitRef.name); - }); - - it('should render the commit short sha with a link to the commit url', () => { - expect(component.$el.querySelector('.commit-id').getAttribute('href')).toEqual(props.commitUrl); - expect(component.$el.querySelector('.commit-id').textContent).toContain(props.shortSha); - }); - - it('should render the given commitIconSvg', () => { - expect(component.$el.querySelector('.js-commit-icon').children).toContain('svg'); - }); - - describe('Given commit title and author props', () => { - it('should render a link to the author profile', () => { - expect( - component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'), - ).toEqual(props.author.web_url); - }); - - it('Should render the author avatar with title and alt attributes', () => { - expect( - component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'), - ).toContain(props.author.username); - expect( - component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'), - ).toContain(`${props.author.username}'s avatar`); - }); - }); - - it('should render the commit title', () => { - expect( - component.$el.querySelector('a.commit-row-message').getAttribute('href'), - ).toEqual(props.commitUrl); - expect( - component.$el.querySelector('a.commit-row-message').textContent, - ).toContain(props.title); - }); - }); - - describe('When commit title is not provided', () => { - it('should render default message', () => { - setFixtures('
'); - props = { - tag: false, - commitRef: { - name: 'master', - ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', - }, - commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', - shortSha: 'b7836edd', - title: null, - author: {}, - }; - - component = new window.gl.CommitComponent({ - el: document.querySelector('.test-commit-container'), - propsData: props, - }); - - expect( - component.$el.querySelector('.commit-title span').textContent, - ).toContain('Cant find HEAD commit for this branch'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/commit_spec.js.es6 b/spec/javascripts/vue_shared/components/commit_spec.js.es6 new file mode 100644 index 00000000000..caf84ec63e2 --- /dev/null +++ b/spec/javascripts/vue_shared/components/commit_spec.js.es6 @@ -0,0 +1,131 @@ +//= require vue_shared/components/commit + +describe('Commit component', () => { + let props; + let component; + + it('should render a code-fork icon if it does not represent a tag', () => { + setFixtures('
'); + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: 'Commit message', + author: { + avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', + web_url: 'https://gitlab.com/jschatz1', + username: 'jschatz1', + }, + }, + }); + + expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork'); + }); + + describe('Given all the props', () => { + beforeEach(() => { + setFixtures('
'); + + props = { + tag: true, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: 'Commit message', + author: { + avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', + web_url: 'https://gitlab.com/jschatz1', + username: 'jschatz1', + }, + commitIconSvg: '', + }; + + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: props, + }); + }); + + it('should render a tag icon if it represents a tag', () => { + expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag'); + }); + + it('should render a link to the ref url', () => { + expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commitRef.ref_url); + }); + + it('should render the ref name', () => { + expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commitRef.name); + }); + + it('should render the commit short sha with a link to the commit url', () => { + expect(component.$el.querySelector('.commit-id').getAttribute('href')).toEqual(props.commitUrl); + expect(component.$el.querySelector('.commit-id').textContent).toContain(props.shortSha); + }); + + it('should render the given commitIconSvg', () => { + expect(component.$el.querySelector('.js-commit-icon').children).toContain('svg'); + }); + + describe('Given commit title and author props', () => { + it('should render a link to the author profile', () => { + expect( + component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'), + ).toEqual(props.author.web_url); + }); + + it('Should render the author avatar with title and alt attributes', () => { + expect( + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'), + ).toContain(props.author.username); + expect( + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'), + ).toContain(`${props.author.username}'s avatar`); + }); + }); + + it('should render the commit title', () => { + expect( + component.$el.querySelector('a.commit-row-message').getAttribute('href'), + ).toEqual(props.commitUrl); + expect( + component.$el.querySelector('a.commit-row-message').textContent, + ).toContain(props.title); + }); + }); + + describe('When commit title is not provided', () => { + it('should render default message', () => { + setFixtures('
'); + props = { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: null, + author: {}, + }; + + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: props, + }); + + expect( + component.$el.querySelector('.commit-title span').textContent, + ).toContain('Cant find HEAD commit for this branch'); + }); + }); +}); -- cgit v1.2.1 From 7ef21460d1ad47c1e140b5cf2977ebc90f8c6dd1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 28 Jan 2017 20:06:15 +0000 Subject: Transform vue_pipelines index into a non-dependent table component. --- .../commit/pipelines/pipelines_bundle.js.es6 | 85 +++++-- .../commit/pipelines/pipelines_service.js.es6 | 45 +--- .../commit/pipelines/pipelines_store.js.es6 | 12 +- .../javascripts/vue_pipelines_index/stage.js.es6 | 1 + .../vue_shared/components/pipelines_table.js.es6 | 277 +++------------------ .../components/pipelines_table_row.js.es6 | 192 ++++++++++++++ app/views/projects/commit/pipelines.html.haml | 6 +- 7 files changed, 306 insertions(+), 312 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 index d2547f0b4a8..d42f2d15f19 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -1,11 +1,10 @@ /* eslint-disable no-new */ -/* global Vue, VueResource */ +/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ +//= require vue +//= require_tree . //= require vue //= require vue-resource -//= require ./pipelines_store -//= require ./pipelines_service -//= require vue_shared/components/commit //= require vue_shared/vue_resource_interceptor //= require vue_shared/components/pipelines_table @@ -21,18 +20,23 @@ * Necessary SVG in the table are provided as props. This should be refactored * as soon as we have Webpack and can load them directly into JS files. */ -(() => { +$(() => { window.gl = window.gl || {}; - gl.Commits = gl.Commits || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; - if (gl.Commits.PipelinesTableView) { - gl.Commits.PipelinesTableView.$destroy(true); + if (gl.commits.PipelinesTableView) { + gl.commits.PipelinesTableView.$destroy(true); } - gl.Commits.PipelinesTableView = new Vue({ + gl.commits.pipelines.PipelinesTableView = new Vue({ el: document.querySelector('#commit-pipeline-table-view'), + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + /** * Accesses the DOM to provide the needed data. * Returns the necessary props to render `pipelines-table-component` component. @@ -41,21 +45,70 @@ */ data() { const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + const svgsData = document.querySelector('.pipeline-svgs').dataset; + const store = gl.commits.pipelines.PipelinesStore.create(); + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = Object.keys(svgsData).reduce((acc, element) => { + acc[element] = svgsData[element]; + return acc; + }, {}); return { - scope: pipelinesTableData.pipelinesData, - store: new CommitsPipelineStore(), - service: new PipelinesService(), - svgs: pipelinesTableData, + endpoint: pipelinesTableData.pipelinesData, + svgs: svgsObject, + store, + state: store.state, + isLoading: false, }; }, - components: { - 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + /** + * When the component is created the service to fetch the data will be + * initialized with the correct endpoint. + * + * A request to fetch the pipelines will be made. + * In case of a successfull response we will store the data in the provided + * store, in case of a failed response we need to warn the user. + * + */ + created() { + gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); + + this.isLoading = true; + + return gl.pipelines.pipelinesService.all() + .then(response => response.json()) + .then((json) => { + this.store.store(json); + this.isLoading = false; + }).catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the pipelines.', 'alert'); + }); }, template: ` - +
+
+ +
+ +
+

+ You don't have any pipelines. +

+ Put get started with pipelines button here!!! +
+ +
+ + +
+
`, }); }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 index 7d773e0d361..1e6aa73d9cf 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 @@ -5,12 +5,8 @@ * Pipelines service. * * Used to fetch the data used to render the pipelines table. - * Used Vue.Resource + * Uses Vue.Resource */ - -window.gl = window.gl || {}; -gl.pipelines = gl.pipelines || {}; - class PipelinesService { constructor(root) { Vue.http.options.root = root; @@ -36,42 +32,3 @@ class PipelinesService { return this.pipelines.get(); } } - -gl.pipelines.PipelinesService = PipelinesService; - -// const pageValues = (headers) => { -// const normalized = gl.utils.normalizeHeaders(headers); -// -// const paginationInfo = { -// perPage: +normalized['X-PER-PAGE'], -// page: +normalized['X-PAGE'], -// total: +normalized['X-TOTAL'], -// totalPages: +normalized['X-TOTAL-PAGES'], -// nextPage: +normalized['X-NEXT-PAGE'], -// previousPage: +normalized['X-PREV-PAGE'], -// }; -// -// return paginationInfo; -// }; - -// gl.PipelineStore = class { -// fetchDataLoop(Vue, pageNum, url, apiScope) { -// const goFetch = () => -// this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`) -// .then((response) => { -// const pageInfo = pageValues(response.headers); -// this.pageInfo = Object.assign({}, this.pageInfo, pageInfo); -// -// const res = JSON.parse(response.body); -// this.count = Object.assign({}, this.count, res.count); -// this.pipelines = Object.assign([], this.pipelines, res); -// -// this.pageRequest = false; -// }, () => { -// this.pageRequest = false; -// return new Flash('Something went wrong on our end.'); -// }); -// -// goFetch(); -// } -// }; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 index bc748bece5d..5c2e1b33cd1 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 @@ -6,12 +6,14 @@ * Pipelines' Store for commits view. * * Used to store the Pipelines rendered in the commit view in the pipelines table. - * - * TODO: take care of timeago instances in here */ (() => { - const CommitPipelineStore = { + window.gl = window.gl || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; + + gl.commits.pipelines.PipelinesStore = { state: {}, create() { @@ -20,11 +22,9 @@ return this; }, - storePipelines(pipelines = []) { + store(pipelines = []) { this.state.pipelines = pipelines; return pipelines; }, }; - - return CommitPipelineStore; })(); diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 496df9aaced..572644c8e6e 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -14,6 +14,7 @@ type: Object, required: true, }, + //FIXME: DOMStringMap is non standard, let's use a plain object. svgs: { type: DOMStringMap, required: true, diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index f602a0c44c2..e606632306f 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -1,14 +1,14 @@ -/* eslint-disable no-param-reassign, no-new */ +/* eslint-disable no-param-reassign */ /* global Vue */ -/* global PipelinesService */ -/* global Flash */ -//= require vue_pipelines_index/status.js.es6 -//= require vue_pipelines_index/pipeline_url.js.es6 -//= require vue_pipelines_index/stage.js.es6 -//= require vue_pipelines_index/pipeline_actions.js.es6 -//= require vue_pipelines_index/time_ago.js.es6 -//= require vue_pipelines_index/pipelines.js.es6 +//=require ./pipelines_table_row + +/** + * Pipelines Table Component + * + * Given an array of pipelines, renders a table. + * + */ (() => { window.gl = window.gl || {}; @@ -17,31 +17,10 @@ gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', { props: { - - /** - * Object used to store the Pipelines to render. - * It's passed as a prop to allow different stores to use this Component. - * Different API calls can result in different responses, using a custom - * store allows us to use the same pipeline component. - * - * Note: All provided stores need to have a `storePipelines` method. - * Find a better way to do this. - */ - store: { - type: Object, - required: true, - default: () => ({}), - }, - - /** - * Will be used to fetch the needed data. - * This component is used in different and therefore different API calls - * to different endpoints will be made. To guarantee this is a reusable - * component, the endpoint must be provided. - */ - endpoint: { - type: String, + pipelines: { + type: Array, required: true, + default: [], }, /** @@ -55,219 +34,31 @@ }, components: { - 'commit-component': gl.CommitComponent, - runningPipeline: gl.VueRunningPipeline, - pipelineActions: gl.VuePipelineActions, - 'vue-stage': gl.VueStage, - pipelineUrl: gl.VuePipelineUrl, - pipelineHead: gl.VuePipelineHead, - statusScope: gl.VueStatusScope, + 'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent, }, - data() { - return { - state: this.store.state, - isLoading: false, - }; - }, - - computed: { - /** - * If provided, returns the commit tag. - * - * @returns {Object|Undefined} - */ - commitAuthor() { - if (this.pipeline && - this.pipeline.commit && - this.pipeline.commit.author) { - return this.pipeline.commit.author; - } - - return undefined; - }, - - /** - * If provided, returns the commit tag. - * - * @returns {String|Undefined} - */ - commitTag() { - if (this.model.last_deployment && - this.model.last_deployment.tag) { - return this.model.last_deployment.tag; - } - return undefined; - }, - - /** - * If provided, returns the commit ref. - * - * @returns {Object|Undefined} - */ - commitRef() { - if (this.pipeline.ref) { - return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { - if (prop === 'url') { - accumulator.path = this.pipeline.ref[prop]; - } else { - accumulator[prop] = this.pipeline.ref[prop]; - } - return accumulator; - }, {}); - } - - return undefined; - }, - - /** - * If provided, returns the commit url. - * - * @returns {String|Undefined} - */ - commitUrl() { - if (this.pipeline.commit && - this.pipeline.commit.commit_path) { - return this.pipeline.commit.commit_path; - } - return undefined; - }, - - /** - * If provided, returns the commit short sha. - * - * @returns {String|Undefined} - */ - commitShortSha() { - if (this.pipeline.commit && - this.pipeline.commit.short_id) { - return this.pipeline.commit.short_id; - } - return undefined; - }, - - /** - * If provided, returns the commit title. - * - * @returns {String|Undefined} - */ - commitTitle() { - if (this.pipeline.commit && - this.pipeline.commit.title) { - return this.pipeline.commit.title; - } - return undefined; - }, - - /** - * Figure this out! - */ - author(pipeline) { - if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; - if (pipeline.commit.author) return pipeline.commit.author; - return { - avatar_url: pipeline.commit.author_gravatar_url, - web_url: `mailto:${pipeline.commit.author_email}`, - username: pipeline.commit.author_name, - }; - }, - - /** - * Figure this out - */ - match(string) { - return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); - }, - }, - - /** - * When the component is created the service to fetch the data will be - * initialized with the correct endpoint. - * - * A request to fetch the pipelines will be made. - * In case of a successfull response we will store the data in the provided - * store, in case of a failed response we need to warn the user. - * - */ - created() { - gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); - - this.isLoading = true; - - return gl.pipelines.pipelinesService.all() - .then(resp => resp.json()) - .then((json) => { - this.store.storePipelines(json); - this.isLoading = false; - }).catch(() => { - this.isLoading = false; - new Flash('An error occurred while fetching the pipelines.', 'alert'); - }); - }, - // this need to be reusable between the 3 tables :/ template: ` -
-
- -
- - -
-

- You don't have any pipelines. -

- Put get started with pipelines button here!!! -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
StatusPipelineCommitStages
- - - - -
-
-
+ + + + + + + + + + + + + + +
StatusPipelineCommitStages
`, }); })(); diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 new file mode 100644 index 00000000000..1e55cce1c41 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -0,0 +1,192 @@ +/* eslint-disable no-param-reassign */ +/* global Vue */ + +//= require vue_pipelines_index/status +//= require vue_pipelines_index/pipeline_url +//= require vue_pipelines_index/stage +//= require vue_shared/components/commit +//= require vue_pipelines_index/pipeline_actions +//= require vue_pipelines_index/time_ago +(() => { + window.gl = window.gl || {}; + gl.pipelines = gl.pipelines || {}; + + gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', { + + props: { + pipeline: { + type: Object, + required: true, + default: () => ({}), + }, + + /** + * Remove this. Find a better way to do this. don't want to provide this 3 times. + */ + svgs: { + type: Object, + required: true, + default: () => ({}), + }, + }, + + components: { + 'commit-component': gl.CommitComponent, + runningPipeline: gl.VueRunningPipeline, + pipelineActions: gl.VuePipelineActions, + 'vue-stage': gl.VueStage, + pipelineUrl: gl.VuePipelineUrl, + pipelineHead: gl.VuePipelineHead, + statusScope: gl.VueStatusScope, + }, + + computed: { + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * @returns {Object|Undefined} + */ + commitAuthor() { + if (this.pipeline && + this.pipeline.commit && + this.pipeline.commit.author) { + return this.pipeline.commit.author; + } + + return undefined; + }, + + /** + * If provided, returns the commit tag. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTag() { + // if (this.model.last_deployment && + // this.model.last_deployment.tag) { + // return this.model.last_deployment.tag; + // } + return undefined; + }, + + /** + * If provided, returns the commit ref. + * Needed to render the commit component column. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.pipeline.ref) { + return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { + if (prop === 'url') { + accumulator.path = this.pipeline.ref[prop]; + } else { + accumulator[prop] = this.pipeline.ref[prop]; + } + return accumulator; + }, {}); + } + + return undefined; + }, + + /** + * If provided, returns the commit url. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitUrl() { + if (this.pipeline.commit && + this.pipeline.commit.commit_path) { + return this.pipeline.commit.commit_path; + } + return undefined; + }, + + /** + * If provided, returns the commit short sha. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if (this.pipeline.commit && + this.pipeline.commit.short_id) { + return this.pipeline.commit.short_id; + } + return undefined; + }, + + /** + * If provided, returns the commit title. + * Needed to render the commit component column. + * + * @returns {String|Undefined} + */ + commitTitle() { + if (this.pipeline.commit && + this.pipeline.commit.title) { + return this.pipeline.commit.title; + } + return undefined; + }, + + /** + * Figure this out! + * Needed to render the commit component column. + */ + author(pipeline) { + if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; + if (pipeline.commit.author) return pipeline.commit.author; + return { + avatar_url: pipeline.commit.author_gravatar_url, + web_url: `mailto:${pipeline.commit.author_email}`, + username: pipeline.commit.author_name, + }; + }, + }, + + methods: { + match(string) { + return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); + }, + }, + + template: ` + + + + + + + + + + + + + + + + + + + + `, + }); +})(); diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index 09bd4288b9c..f62fbe4d9cd 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -3,9 +3,6 @@ = render 'commit_box' = render 'ci_menu' -- content_for :page_specific_javascripts do - = page_specific_javascript_tag("commit/pipelines/pipelines_bundle.js") - #commit-pipeline-table-view{ data: { pipelines_data: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)}} .pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), "icon_status_canceled" => custom_icon("icon_status_canceled"), @@ -28,3 +25,6 @@ "icon_timer" => custom_icon("icon_timer"), "icon_status_manual" => custom_icon("icon_status_manual"), } } + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('commit/pipelines/pipelines_bundle.js') -- cgit v1.2.1 From 2c2da2c07b775f1677456376d311560f1e43226f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 28 Jan 2017 21:26:04 +0000 Subject: Use new vue js pipelines table to render in merge request view Remove duplicate data-toggle attributes. Reuse the same pipeline table Remove unneeded required resources Remove unused file; Fix mr pipelines loading Updates documentation --- .../commit/pipelines/pipelines_bundle.js.es6 | 17 +++-- .../commit/pipelines/pipelines_store.js.es6 | 4 - app/assets/javascripts/dispatcher.js.es6 | 5 -- app/assets/javascripts/merge_request_tabs.js.es6 | 24 ------ .../javascripts/vue_pipelines_index/index.js.es6 | 43 +++++------ .../vue_pipelines_index/pipeline_actions.js.es6 | 12 +-- .../vue_pipelines_index/pipelines.js.es6 | 87 +++------------------- .../javascripts/vue_pipelines_index/stage.js.es6 | 3 +- .../javascripts/vue_pipelines_index/stages.js.es6 | 21 ------ .../vue_shared/components/pipelines_table.js.es6 | 2 +- .../components/pipelines_table_row.js.es6 | 55 +++++++++----- .../projects/merge_requests_controller.rb | 11 +-- app/views/projects/commit/_pipelines_list.haml | 25 +++++++ app/views/projects/commit/pipelines.html.haml | 27 +------ .../projects/merge_requests/_new_submit.html.haml | 6 +- app/views/projects/merge_requests/_show.html.haml | 3 +- .../merge_requests/show/_pipelines.html.haml | 2 +- 17 files changed, 124 insertions(+), 223 deletions(-) delete mode 100644 app/assets/javascripts/vue_pipelines_index/stages.js.es6 diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 index d42f2d15f19..a06aad17824 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -1,4 +1,4 @@ -/* eslint-disable no-new */ +/* eslint-disable no-new, no-param-reassign */ /* global Vue, CommitsPipelineStore, PipelinesService, Flash */ //= require vue @@ -10,8 +10,10 @@ /** * Commits View > Pipelines Tab > Pipelines Table. + * Merge Request View > Pipelines Tab > Pipelines Table. * * Renders Pipelines table in pipelines tab in the commits show view. + * Renders Pipelines table in pipelines tab in the merge request show view. * * Uses `pipelines-table-component` to render Pipelines table with an API call. * Endpoint is provided in HTML and passed as scope. @@ -20,6 +22,7 @@ * Necessary SVG in the table are provided as props. This should be refactored * as soon as we have Webpack and can load them directly into JS files. */ + $(() => { window.gl = window.gl || {}; gl.commits = gl.commits || {}; @@ -55,11 +58,12 @@ $(() => { }, {}); return { - endpoint: pipelinesTableData.pipelinesData, + endpoint: pipelinesTableData.endpoint, svgs: svgsObject, store, state: store.state, isLoading: false, + error: false, }; }, @@ -82,7 +86,9 @@ $(() => { .then((json) => { this.store.store(json); this.isLoading = false; + this.error = false; }).catch(() => { + this.error = true; this.isLoading = false; new Flash('An error occurred while fetching the pipelines.', 'alert'); }); @@ -95,14 +101,15 @@ $(() => {
+ v-if="!isLoading && !error && state.pipelines.length === 0">

You don't have any pipelines.

- Put get started with pipelines button here!!!
-
+
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 index 5c2e1b33cd1..b7d8e97fed3 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 @@ -1,7 +1,3 @@ -/* global gl, Flash */ -/* eslint-disable no-param-reassign, no-underscore-dangle */ -/*= require vue_realtime_listener/index.js */ - /** * Pipelines' Store for commits view. * diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index edec21e3b63..70f467d608f 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -159,11 +159,6 @@ new ZenMode(); shortcut_handler = new ShortcutsNavigation(); break; - case 'projects:commit:pipelines': - new gl.MiniPipelineGraph({ - container: '.js-pipeline-table', - }); - break; case 'projects:commits:show': case 'projects:activity': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6 index 4c8c28af755..dabba9e1fa9 100644 --- a/app/assets/javascripts/merge_request_tabs.js.es6 +++ b/app/assets/javascripts/merge_request_tabs.js.es6 @@ -61,7 +61,6 @@ constructor({ action, setUrl, stubLocation } = {}) { this.diffsLoaded = false; - this.pipelinesLoaded = false; this.commitsLoaded = false; this.fixedLayoutPref = null; @@ -116,10 +115,6 @@ $.scrollTo('.merge-request-details .merge-request-tabs', { offset: -navBarHeight, }); - } else if (action === 'pipelines') { - this.loadPipelines($target.attr('href')); - this.expandView(); - this.resetViewContainer(); } else { this.expandView(); this.resetViewContainer(); @@ -243,25 +238,6 @@ }); } - loadPipelines(source) { - if (this.pipelinesLoaded) { - return; - } - this.ajaxGet({ - url: `${source}.json`, - success: (data) => { - $('#pipelines').html(data.html); - gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); - this.pipelinesLoaded = true; - this.scrollToElement('#pipelines'); - - new gl.MiniPipelineGraph({ - container: '.js-pipeline-table', - }); - }, - }); - } - // Show or hide the loading spinner // // status - Boolean, true to show, false to hide diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6 index 9ca7b1a746c..e5359ba5398 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6 @@ -1,31 +1,32 @@ +/* eslint-disable no-param-reassign */ /* global Vue, VueResource, gl */ -/*= require vue_shared/components/commit */ -/*= require vue_pagination/index */ + +//= require vue /*= require vue-resource /*= require vue_shared/vue_resource_interceptor */ -/*= require ./status.js.es6 */ -/*= require ./store.js.es6 */ -/*= require ./pipeline_url.js.es6 */ -/*= require ./stage.js.es6 */ -/*= require ./stages.js.es6 */ -/*= require ./pipeline_actions.js.es6 */ -/*= require ./time_ago.js.es6 */ /*= require ./pipelines.js.es6 */ -(() => { - const project = document.querySelector('.pipelines'); - const entry = document.querySelector('.vue-pipelines-index'); - const svgs = document.querySelector('.pipeline-svgs'); - +$(() => { Vue.use(VueResource); - if (!entry) return null; return new Vue({ - el: entry, - data: { - scope: project.dataset.url, - store: new gl.PipelineStore(), - svgs: svgs.dataset, + el: document.querySelector('.vue-pipelines-index'), + + data() { + const project = document.querySelector('.pipelines'); + const svgs = document.querySelector('.pipeline-svgs').dataset; + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = Object.keys(svgs).reduce((acc, element) => { + acc[element] = svgs[element]; + return acc; + }, {}); + + return { + scope: project.dataset.url, + store: new gl.PipelineStore(), + svgs: svgsObject, + }; }, components: { 'vue-pipelines': gl.VuePipelines, @@ -39,4 +40,4 @@ `, }); -})(); +}); diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index a7176e27ea1..9b4897b1a9e 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -25,7 +25,6 @@
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index b2ed05503c9..34d93ce1b7f 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -1,19 +1,18 @@ /* global Vue, Turbolinks, gl */ /* eslint-disable no-param-reassign */ +//= require vue_pagination/index +//= require ./store.js.es6 +//= require vue_shared/components/pipelines_table + ((gl) => { gl.VuePipelines = Vue.extend({ + components: { - runningPipeline: gl.VueRunningPipeline, - pipelineActions: gl.VuePipelineActions, - stages: gl.VueStages, - commit: gl.CommitComponent, - pipelineUrl: gl.VuePipelineUrl, - pipelineHead: gl.VuePipelineHead, glPagination: gl.VueGlPagination, - statusScope: gl.VueStatusScope, - timeAgo: gl.VueTimeAgo, + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, }, + data() { return { pipelines: [], @@ -38,31 +37,6 @@ change(pagenum, apiScope) { Turbolinks.visit(`?scope=${apiScope}&p=${pagenum}`); }, - author(pipeline) { - if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; - if (pipeline.commit.author) return pipeline.commit.author; - return { - avatar_url: pipeline.commit.author_gravatar_url, - web_url: `mailto:${pipeline.commit.author_email}`, - username: pipeline.commit.author_name, - }; - }, - ref(pipeline) { - const { ref } = pipeline; - return { name: ref.name, tag: ref.tag, ref_url: ref.path }; - }, - commitTitle(pipeline) { - return pipeline.commit ? pipeline.commit.title : ''; - }, - commitSha(pipeline) { - return pipeline.commit ? pipeline.commit.short_id : ''; - }, - commitUrl(pipeline) { - return pipeline.commit ? pipeline.commit.commit_path : ''; - }, - match(string) { - return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); - }, }, template: `
@@ -70,49 +44,10 @@
- - - - - - - - - - - - - - - - - - - - - - - -
StatusPipelineCommitStages
- - -
+ +
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 index 572644c8e6e..8cc417a9966 100644 --- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6 @@ -14,9 +14,8 @@ type: Object, required: true, }, - //FIXME: DOMStringMap is non standard, let's use a plain object. svgs: { - type: DOMStringMap, + type: Object, required: true, }, match: { diff --git a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.js.es6 deleted file mode 100644 index cb176b3f0c6..00000000000 --- a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 +++ /dev/null @@ -1,21 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign */ - -((gl) => { - gl.VueStages = Vue.extend({ - components: { - 'vue-stage': gl.VueStage, - }, - props: ['pipeline', 'svgs', 'match'], - template: ` - - - - `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index e606632306f..4b6bba461d7 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -1,7 +1,7 @@ /* eslint-disable no-param-reassign */ /* global Vue */ -//=require ./pipelines_table_row +//= require ./pipelines_table_row /** * Pipelines Table Component diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 index 1e55cce1c41..c0ff0c90e4e 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -38,6 +38,7 @@ pipelineUrl: gl.VuePipelineUrl, pipelineHead: gl.VuePipelineHead, statusScope: gl.VueStatusScope, + 'time-ago': gl.VueTimeAgo, }, computed: { @@ -45,18 +46,50 @@ * If provided, returns the commit tag. * Needed to render the commit component column. * + * TODO: Document this logic, need to ask @grzesiek and @selfup + * * @returns {Object|Undefined} */ commitAuthor() { + if (!this.pipeline.commit) { + return { avatar_url: '', web_url: '', username: '' }; + } + if (this.pipeline && this.pipeline.commit && this.pipeline.commit.author) { return this.pipeline.commit.author; } + if (this.pipeline && + this.pipeline.commit && + this.pipeline.commit.author_gravatar_url && + this.pipeline.commit.author_name && + this.pipeline.commit.author_email) { + return { + avatar_url: this.pipeline.commit.author_gravatar_url, + web_url: `mailto:${this.pipeline.commit.author_email}`, + username: this.pipeline.commit.author_name, + }; + } + return undefined; }, + /** + * Figure this out! + * Needed to render the commit component column. + */ + author(pipeline) { + if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; + if (pipeline.commit.author) return pipeline.commit.author; + return { + avatar_url: pipeline.commit.author_gravatar_url, + web_url: `mailto:${pipeline.commit.author_email}`, + username: pipeline.commit.author_name, + }; + }, + /** * If provided, returns the commit tag. * Needed to render the commit component column. @@ -64,10 +97,10 @@ * @returns {String|Undefined} */ commitTag() { - // if (this.model.last_deployment && - // this.model.last_deployment.tag) { - // return this.model.last_deployment.tag; - // } + if (this.pipeline.ref && + this.pipeline.ref.tag) { + return this.pipeline.ref.tag; + } return undefined; }, @@ -133,20 +166,6 @@ } return undefined; }, - - /** - * Figure this out! - * Needed to render the commit component column. - */ - author(pipeline) { - if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; - if (pipeline.commit.author) return pipeline.commit.author; - return { - avatar_url: pipeline.commit.author_gravatar_url, - web_url: `mailto:${pipeline.commit.author_email}`, - username: pipeline.commit.author_name, - }; - }, }, methods: { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 6eb542e4bd8..deb084c2e91 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -216,13 +216,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end format.json do - render json: { - html: view_to_html_string('projects/merge_requests/show/_pipelines'), - pipelines: PipelineSerializer - .new(project: @project, user: @current_user) - .with_pagination(request, response) - .represent(@pipelines) - } + render json: PipelineSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .represent(@pipelines) end end end diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index e69de29bb2d..bfe5eb18ad1 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -0,0 +1,25 @@ +#commit-pipeline-table-view{ data: { endpoint: endpoint } } +.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), + "icon_status_canceled" => custom_icon("icon_status_canceled"), + "icon_status_running" => custom_icon("icon_status_running"), + "icon_status_skipped" => custom_icon("icon_status_skipped"), + "icon_status_created" => custom_icon("icon_status_created"), + "icon_status_pending" => custom_icon("icon_status_pending"), + "icon_status_success" => custom_icon("icon_status_success"), + "icon_status_failed" => custom_icon("icon_status_failed"), + "icon_status_warning" => custom_icon("icon_status_warning"), + "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), + "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), + "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), + "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), + "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), + "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), + "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), + "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), + "icon_play" => custom_icon("icon_play"), + "icon_timer" => custom_icon("icon_timer"), + "icon_status_manual" => custom_icon("icon_status_manual"), +} } + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('commit/pipelines/pipelines_bundle.js') diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index f62fbe4d9cd..ac93eac41ac 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -2,29 +2,4 @@ = render 'commit_box' = render 'ci_menu' - -#commit-pipeline-table-view{ data: { pipelines_data: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)}} -.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"), - "icon_status_canceled" => custom_icon("icon_status_canceled"), - "icon_status_running" => custom_icon("icon_status_running"), - "icon_status_skipped" => custom_icon("icon_status_skipped"), - "icon_status_created" => custom_icon("icon_status_created"), - "icon_status_pending" => custom_icon("icon_status_pending"), - "icon_status_success" => custom_icon("icon_status_success"), - "icon_status_failed" => custom_icon("icon_status_failed"), - "icon_status_warning" => custom_icon("icon_status_warning"), - "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"), - "stage_icon_status_running" => custom_icon("icon_status_running_borderless"), - "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"), - "stage_icon_status_created" => custom_icon("icon_status_created_borderless"), - "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"), - "stage_icon_status_success" => custom_icon("icon_status_success_borderless"), - "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"), - "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"), - "icon_play" => custom_icon("icon_play"), - "icon_timer" => custom_icon("icon_timer"), - "icon_status_manual" => custom_icon("icon_status_manual"), -} } - -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('commit/pipelines/pipelines_bundle.js') += render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index d3c013b3f21..c1f48837e0e 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -44,9 +44,9 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane -# This tab is always loaded via AJAX - - if @pipelines.any? - #pipelines.pipelines.tab-pane - = render "projects/merge_requests/show/pipelines" + #pipelines.pipelines.tab-pane + //TODO: This needs to make a new request every time is opened! + = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9585a9a3ad4..8dfe967a937 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -94,7 +94,8 @@ #commits.commits.tab-pane -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - -# This tab is always loaded via AJAX + //TODO: This needs to make a new request every time is opened! + = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) #diffs.diffs.tab-pane -# This tab is always loaded via AJAX diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index afe3f3430c6..cbe534abedb 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1 +1 @@ -= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true += render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) -- cgit v1.2.1 From 184f60a06f828ccbc9264d40e6daa48d60dca629 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sun, 29 Jan 2017 15:30:04 +0000 Subject: Moves pagination to shared folder Document and remove unused code Declare components in a consistent way; Use " instead of ' to improve consistency; Update documentation; Fix commit author verification to match the use cases; Adds tests for the added components Fix paths in pagination spec Adds tests to pipelines table used in merge requests and commits Use same resource interceptor Fix eslint error --- .../commit/pipelines/pipelines_bundle.js.es6 | 100 +----------- .../commit/pipelines/pipelines_service.js.es6 | 5 + .../commit/pipelines/pipelines_table.js.es6 | 104 +++++++++++++ .../environments/environments_bundle.js.es6 | 2 +- .../environments/vue_resource_interceptor.js.es6 | 12 -- app/assets/javascripts/vue_pagination/index.js.es6 | 148 ------------------ .../vue_pipelines_index/pipelines.js.es6 | 4 +- .../vue_pipelines_index/time_ago.js.es6 | 2 + .../vue_shared/components/pipelines_table.js.es6 | 24 ++- .../components/pipelines_table_row.js.es6 | 102 ++++++++----- .../vue_shared/components/table_pagination.js.es6 | 148 ++++++++++++++++++ .../vue_shared/vue_resource_interceptor.js.es6 | 11 +- app/controllers/projects/commit_controller.rb | 1 - .../projects/merge_requests_controller.rb | 1 - .../projects/merge_requests/_new_submit.html.haml | 6 +- app/views/projects/merge_requests/_show.html.haml | 1 - spec/javascripts/commit/pipelines/mock_data.js.es6 | 90 +++++++++++ .../commit/pipelines/pipelines_spec.js.es6 | 107 +++++++++++++ .../commit/pipelines/pipelines_store_spec.js.es6 | 31 ++++ .../javascripts/fixtures/pipelines_table.html.haml | 2 + .../vue_pagination/pagination_spec.js.es6 | 167 -------------------- .../components/pipelines_table_row_spec.js.es6 | 90 +++++++++++ .../components/pipelines_table_spec.js.es6 | 67 ++++++++ .../components/table_pagination_spec.js.es6 | 168 +++++++++++++++++++++ 24 files changed, 905 insertions(+), 488 deletions(-) create mode 100644 app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 delete mode 100644 app/assets/javascripts/environments/vue_resource_interceptor.js.es6 delete mode 100644 app/assets/javascripts/vue_pagination/index.js.es6 create mode 100644 app/assets/javascripts/vue_shared/components/table_pagination.js.es6 create mode 100644 spec/javascripts/commit/pipelines/mock_data.js.es6 create mode 100644 spec/javascripts/commit/pipelines/pipelines_spec.js.es6 create mode 100644 spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 create mode 100644 spec/javascripts/fixtures/pipelines_table.html.haml delete mode 100644 spec/javascripts/vue_pagination/pagination_spec.js.es6 create mode 100644 spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 create mode 100644 spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 create mode 100644 spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 index a06aad17824..b21d13842a4 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -3,10 +3,6 @@ //= require vue //= require_tree . -//= require vue -//= require vue-resource -//= require vue_shared/vue_resource_interceptor -//= require vue_shared/components/pipelines_table /** * Commits View > Pipelines Tab > Pipelines Table. @@ -14,13 +10,6 @@ * * Renders Pipelines table in pipelines tab in the commits show view. * Renders Pipelines table in pipelines tab in the merge request show view. - * - * Uses `pipelines-table-component` to render Pipelines table with an API call. - * Endpoint is provided in HTML and passed as scope. - * We need a store to make the request and store the received environemnts. - * - * Necessary SVG in the table are provided as props. This should be refactored - * as soon as we have Webpack and can load them directly into JS files. */ $(() => { @@ -28,94 +17,11 @@ $(() => { gl.commits = gl.commits || {}; gl.commits.pipelines = gl.commits.pipelines || {}; - if (gl.commits.PipelinesTableView) { - gl.commits.PipelinesTableView.$destroy(true); + if (gl.commits.PipelinesTableBundle) { + gl.commits.PipelinesTableBundle.$destroy(true); } - gl.commits.pipelines.PipelinesTableView = new Vue({ - + gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({ el: document.querySelector('#commit-pipeline-table-view'), - - components: { - 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, - }, - - /** - * Accesses the DOM to provide the needed data. - * Returns the necessary props to render `pipelines-table-component` component. - * - * @return {Object} Props for `pipelines-table-component` - */ - data() { - const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; - const svgsData = document.querySelector('.pipeline-svgs').dataset; - const store = gl.commits.pipelines.PipelinesStore.create(); - - // Transform svgs DOMStringMap to a plain Object. - const svgsObject = Object.keys(svgsData).reduce((acc, element) => { - acc[element] = svgsData[element]; - return acc; - }, {}); - - return { - endpoint: pipelinesTableData.endpoint, - svgs: svgsObject, - store, - state: store.state, - isLoading: false, - error: false, - }; - }, - - /** - * When the component is created the service to fetch the data will be - * initialized with the correct endpoint. - * - * A request to fetch the pipelines will be made. - * In case of a successfull response we will store the data in the provided - * store, in case of a failed response we need to warn the user. - * - */ - created() { - gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); - - this.isLoading = true; - - return gl.pipelines.pipelinesService.all() - .then(response => response.json()) - .then((json) => { - this.store.store(json); - this.isLoading = false; - this.error = false; - }).catch(() => { - this.error = true; - this.isLoading = false; - new Flash('An error occurred while fetching the pipelines.', 'alert'); - }); - }, - - template: ` -
-
- -
- -
-

- You don't have any pipelines. -

-
- -
- - -
-
- `, }); }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 index 1e6aa73d9cf..f4ed986b0c5 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 @@ -32,3 +32,8 @@ class PipelinesService { return this.pipelines.get(); } } + +window.gl = window.gl || {}; +gl.commits = gl.commits || {}; +gl.commits.pipelines = gl.commits.pipelines || {}; +gl.commits.pipelines.PipelinesService = PipelinesService; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 new file mode 100644 index 00000000000..df7a6455eed --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 @@ -0,0 +1,104 @@ +/* eslint-disable no-new, no-param-reassign */ +/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ + +//= require vue +//= require vue-resource +//= require vue_shared/vue_resource_interceptor +//= require vue_shared/components/pipelines_table + +/** + * + * Uses `pipelines-table-component` to render Pipelines table with an API call. + * Endpoint is provided in HTML and passed as `endpoint`. + * We need a store to store the received environemnts. + * We need a service to communicate with the server. + * + * Necessary SVG in the table are provided as props. This should be refactored + * as soon as we have Webpack and can load them directly into JS files. + */ + +(() => { + window.gl = window.gl || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; + + gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', { + + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + /** + * Accesses the DOM to provide the needed data. + * Returns the necessary props to render `pipelines-table-component` component. + * + * @return {Object} + */ + data() { + const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + const svgsData = document.querySelector('.pipeline-svgs').dataset; + const store = gl.commits.pipelines.PipelinesStore.create(); + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = Object.keys(svgsData).reduce((acc, element) => { + acc[element] = svgsData[element]; + return acc; + }, {}); + + return { + endpoint: pipelinesTableData.endpoint, + svgs: svgsObject, + store, + state: store.state, + isLoading: false, + }; + }, + + /** + * When the component is created the service to fetch the data will be + * initialized with the correct endpoint. + * + * A request to fetch the pipelines will be made. + * In case of a successfull response we will store the data in the provided + * store, in case of a failed response we need to warn the user. + * + */ + created() { + gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); + + this.isLoading = true; + return gl.pipelines.pipelinesService.all() + .then(response => response.json()) + .then((json) => { + this.store.store(json); + this.isLoading = false; + }).catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the pipelines.', 'alert'); + }); + }, + + template: ` +
+
+ +
+ +
+

+ No pipelines to show +

+
+ +
+ + +
+
+ `, + }); +})(); diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 3b003f6f661..cd205617a97 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,7 +1,7 @@ //= require vue //= require_tree ./stores/ //= require ./components/environment -//= require ./vue_resource_interceptor +//= require vue_shared/vue_resource_interceptor $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 b/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 deleted file mode 100644 index 406bdbc1c7d..00000000000 --- a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -/* global Vue */ -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next((response) => { - if (typeof response.data === 'string') { - response.data = JSON.parse(response.data); // eslint-disable-line - } - - Vue.activeResources--; // eslint-disable-line - }); -}); diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6 deleted file mode 100644 index 605824fa939..00000000000 --- a/app/assets/javascripts/vue_pagination/index.js.es6 +++ /dev/null @@ -1,148 +0,0 @@ -/* global Vue, gl */ -/* eslint-disable no-param-reassign, no-plusplus */ - -((gl) => { - const PAGINATION_UI_BUTTON_LIMIT = 4; - const UI_LIMIT = 6; - const SPREAD = '...'; - const PREV = 'Prev'; - const NEXT = 'Next'; - const FIRST = '<< First'; - const LAST = 'Last >>'; - - gl.VueGlPagination = Vue.extend({ - props: { - - /** - This function will take the information given by the pagination component - And make a new Turbolinks call - - Here is an example `change` method: - - change(pagenum, apiScope) { - Turbolinks.visit(`?scope=${apiScope}&p=${pagenum}`); - }, - */ - - change: { - type: Function, - required: true, - }, - - /** - pageInfo will come from the headers of the API call - in the `.then` clause of the VueResource API call - there should be a function that contructs the pageInfo for this component - - This is an example: - - const pageInfo = headers => ({ - perPage: +headers['X-Per-Page'], - page: +headers['X-Page'], - total: +headers['X-Total'], - totalPages: +headers['X-Total-Pages'], - nextPage: +headers['X-Next-Page'], - previousPage: +headers['X-Prev-Page'], - }); - */ - - pageInfo: { - type: Object, - required: true, - }, - }, - methods: { - changePage(e) { - let apiScope = gl.utils.getParameterByName('scope'); - - if (!apiScope) apiScope = 'all'; - - const text = e.target.innerText; - const { totalPages, nextPage, previousPage } = this.pageInfo; - - switch (text) { - case SPREAD: - break; - case LAST: - this.change(totalPages, apiScope); - break; - case NEXT: - this.change(nextPage, apiScope); - break; - case PREV: - this.change(previousPage, apiScope); - break; - case FIRST: - this.change(1, apiScope); - break; - default: - this.change(+text, apiScope); - break; - } - }, - }, - computed: { - prev() { - return this.pageInfo.previousPage; - }, - next() { - return this.pageInfo.nextPage; - }, - getItems() { - const total = this.pageInfo.totalPages; - const page = this.pageInfo.page; - const items = []; - - if (page > 1) items.push({ title: FIRST }); - - if (page > 1) { - items.push({ title: PREV, prev: true }); - } else { - items.push({ title: PREV, disabled: true, prev: true }); - } - - if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); - - const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); - const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); - - for (let i = start; i <= end; i++) { - const isActive = i === page; - items.push({ title: i, active: isActive, page: true }); - } - - if (total - page > PAGINATION_UI_BUTTON_LIMIT) { - items.push({ title: SPREAD, separator: true, page: true }); - } - - if (page === total) { - items.push({ title: NEXT, disabled: true, next: true }); - } else if (total - page >= 1) { - items.push({ title: NEXT, next: true }); - } - - if (total - page >= 1) items.push({ title: LAST, last: true }); - - return items; - }, - }, - template: ` -
- -
- `, - }); -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 34d93ce1b7f..c1daf816060 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -1,7 +1,7 @@ /* global Vue, Turbolinks, gl */ /* eslint-disable no-param-reassign */ -//= require vue_pagination/index +//= require vue_shared/components/table_pagination //= require ./store.js.es6 //= require vue_shared/components/pipelines_table @@ -9,7 +9,7 @@ gl.VuePipelines = Vue.extend({ components: { - glPagination: gl.VueGlPagination, + 'gl-pagination': gl.VueGlPagination, 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, }, diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 655110feba1..61417b28630 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -1,6 +1,8 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ +//= require lib/utils/datetime_utility + ((gl) => { gl.VueTimeAgo = Vue.extend({ data() { diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index 4b6bba461d7..9bc1ea65e53 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -4,10 +4,9 @@ //= require ./pipelines_table_row /** - * Pipelines Table Component - * - * Given an array of pipelines, renders a table. + * Pipelines Table Component. * + * Given an array of objects, renders a table. */ (() => { @@ -20,11 +19,11 @@ pipelines: { type: Array, required: true, - default: [], + default: () => ([]), }, /** - * Remove this. Find a better way to do this. don't want to provide this 3 times. + * TODO: Remove this when we have webpack. */ svgs: { type: Object, @@ -41,19 +40,18 @@ - - - - - - + + + + + + diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 index c0ff0c90e4e..375516e3804 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -7,6 +7,12 @@ //= require vue_shared/components/commit //= require vue_pipelines_index/pipeline_actions //= require vue_pipelines_index/time_ago + +/** + * Pipeline table row. + * + * Given the received object renders a table row in the pipelines' table. + */ (() => { window.gl = window.gl || {}; gl.pipelines = gl.pipelines || {}; @@ -21,7 +27,7 @@ }, /** - * Remove this. Find a better way to do this. don't want to provide this 3 times. + * TODO: Remove this when we have webpack; */ svgs: { type: Object, @@ -32,12 +38,10 @@ components: { 'commit-component': gl.CommitComponent, - runningPipeline: gl.VueRunningPipeline, - pipelineActions: gl.VuePipelineActions, - 'vue-stage': gl.VueStage, - pipelineUrl: gl.VuePipelineUrl, - pipelineHead: gl.VuePipelineHead, - statusScope: gl.VueStatusScope, + 'pipeline-actions': gl.VuePipelineActions, + 'dropdown-stage': gl.VueStage, + 'pipeline-url': gl.VuePipelineUrl, + 'status-scope': gl.VueStatusScope, 'time-ago': gl.VueTimeAgo, }, @@ -46,48 +50,48 @@ * If provided, returns the commit tag. * Needed to render the commit component column. * - * TODO: Document this logic, need to ask @grzesiek and @selfup + * This field needs a lot of verification, because of different possible cases: + * + * 1. person who is an author of a commit might be a GitLab user + * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar + * 3. If GitLab user does not have avatar he/she might have a Gravatar + * 4. If committer is not a GitLab User he/she can have a Gravatar + * 5. We do not have consistent API object in this case + * 6. We should improve API and the code * * @returns {Object|Undefined} */ commitAuthor() { - if (!this.pipeline.commit) { - return { avatar_url: '', web_url: '', username: '' }; - } + let commitAuthorInformation; + // 1. person who is an author of a commit might be a GitLab user if (this.pipeline && this.pipeline.commit && this.pipeline.commit.author) { - return this.pipeline.commit.author; + // 2. if person who is an author of a commit is a GitLab user + // he/she can have a GitLab avatar + if (this.pipeline.commit.author.avatar_url) { + commitAuthorInformation = this.pipeline.commit.author; + + // 3. If GitLab user does not have avatar he/she might have a Gravatar + } else if (this.pipeline.commit.author_gravatar_url) { + commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { + avatar_url: this.pipeline.commit.author_gravatar_url, + }); + } } + // 4. If committer is not a GitLab User he/she can have a Gravatar if (this.pipeline && - this.pipeline.commit && - this.pipeline.commit.author_gravatar_url && - this.pipeline.commit.author_name && - this.pipeline.commit.author_email) { - return { + this.pipeline.commit) { + commitAuthorInformation = { avatar_url: this.pipeline.commit.author_gravatar_url, web_url: `mailto:${this.pipeline.commit.author_email}`, username: this.pipeline.commit.author_name, }; } - return undefined; - }, - - /** - * Figure this out! - * Needed to render the commit component column. - */ - author(pipeline) { - if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; - if (pipeline.commit.author) return pipeline.commit.author; - return { - avatar_url: pipeline.commit.author_gravatar_url, - web_url: `mailto:${pipeline.commit.author_email}`, - username: pipeline.commit.author_name, - }; + return commitAuthorInformation; }, /** @@ -108,6 +112,9 @@ * If provided, returns the commit ref. * Needed to render the commit component column. * + * Matched `url` prop sent in the API to `path` prop needed + * in the commit component. + * * @returns {Object|Undefined} */ commitRef() { @@ -169,6 +176,17 @@ }, methods: { + /** + * FIXME: This should not be in this component but in the components that + * need this function. + * + * Used to render SVGs in the following components: + * - status-scope + * - dropdown-stage + * + * @param {String} string + * @return {String} + */ match(string) { return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); }, @@ -177,12 +195,12 @@ template: ` - + - + - + `, }); diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 new file mode 100644 index 00000000000..605824fa939 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 @@ -0,0 +1,148 @@ +/* global Vue, gl */ +/* eslint-disable no-param-reassign, no-plusplus */ + +((gl) => { + const PAGINATION_UI_BUTTON_LIMIT = 4; + const UI_LIMIT = 6; + const SPREAD = '...'; + const PREV = 'Prev'; + const NEXT = 'Next'; + const FIRST = '<< First'; + const LAST = 'Last >>'; + + gl.VueGlPagination = Vue.extend({ + props: { + + /** + This function will take the information given by the pagination component + And make a new Turbolinks call + + Here is an example `change` method: + + change(pagenum, apiScope) { + Turbolinks.visit(`?scope=${apiScope}&p=${pagenum}`); + }, + */ + + change: { + type: Function, + required: true, + }, + + /** + pageInfo will come from the headers of the API call + in the `.then` clause of the VueResource API call + there should be a function that contructs the pageInfo for this component + + This is an example: + + const pageInfo = headers => ({ + perPage: +headers['X-Per-Page'], + page: +headers['X-Page'], + total: +headers['X-Total'], + totalPages: +headers['X-Total-Pages'], + nextPage: +headers['X-Next-Page'], + previousPage: +headers['X-Prev-Page'], + }); + */ + + pageInfo: { + type: Object, + required: true, + }, + }, + methods: { + changePage(e) { + let apiScope = gl.utils.getParameterByName('scope'); + + if (!apiScope) apiScope = 'all'; + + const text = e.target.innerText; + const { totalPages, nextPage, previousPage } = this.pageInfo; + + switch (text) { + case SPREAD: + break; + case LAST: + this.change(totalPages, apiScope); + break; + case NEXT: + this.change(nextPage, apiScope); + break; + case PREV: + this.change(previousPage, apiScope); + break; + case FIRST: + this.change(1, apiScope); + break; + default: + this.change(+text, apiScope); + break; + } + }, + }, + computed: { + prev() { + return this.pageInfo.previousPage; + }, + next() { + return this.pageInfo.nextPage; + }, + getItems() { + const total = this.pageInfo.totalPages; + const page = this.pageInfo.page; + const items = []; + + if (page > 1) items.push({ title: FIRST }); + + if (page > 1) { + items.push({ title: PREV, prev: true }); + } else { + items.push({ title: PREV, disabled: true, prev: true }); + } + + if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); + + const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); + const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); + + for (let i = start; i <= end; i++) { + const isActive = i === page; + items.push({ title: i, active: isActive, page: true }); + } + + if (total - page > PAGINATION_UI_BUTTON_LIMIT) { + items.push({ title: SPREAD, separator: true, page: true }); + } + + if (page === total) { + items.push({ title: NEXT, disabled: true, next: true }); + } else if (total - page >= 1) { + items.push({ title: NEXT, next: true }); + } + + if (total - page >= 1) items.push({ title: LAST, last: true }); + + return items; + }, + }, + template: ` +
+ +
+ `, + }); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 index 54c2b4ad369..d627fa2b88a 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -1,10 +1,15 @@ -/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, +no-param-reassign, no-plusplus */ /* global Vue */ Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - next(function (response) { - Vue.activeResources -= 1; + next((response) => { + if (typeof response.data === 'string') { + response.data = JSON.parse(response.data); + } + + Vue.activeResources--; }); }); diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index b5a7078a3a1..f880a9862c6 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController format.json do render json: PipelineSerializer .new(project: @project, user: @current_user) - .with_pagination(request, response) .represent(@pipelines) end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index deb084c2e91..68f6208c2be 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -218,7 +218,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.json do render json: PipelineSerializer .new(project: @project, user: @current_user) - .with_pagination(request, response) .represent(@pipelines) end end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index c1f48837e0e..e00ae629e4b 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -44,9 +44,9 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane -# This tab is always loaded via AJAX - #pipelines.pipelines.tab-pane - //TODO: This needs to make a new request every time is opened! - = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) + - if @pipelines.any? + #pipelines.pipelines.tab-pane + = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 8dfe967a937..f131836058b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -94,7 +94,6 @@ #commits.commits.tab-pane -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - //TODO: This needs to make a new request every time is opened! = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) #diffs.diffs.tab-pane -# This tab is always loaded via AJAX diff --git a/spec/javascripts/commit/pipelines/mock_data.js.es6 b/spec/javascripts/commit/pipelines/mock_data.js.es6 new file mode 100644 index 00000000000..5f0f26a013c --- /dev/null +++ b/spec/javascripts/commit/pipelines/mock_data.js.es6 @@ -0,0 +1,90 @@ +/* eslint-disable no-unused-vars */ +const pipeline = { + id: 73, + user: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + web_url: 'http://localhost:3000/root', + }, + path: '/root/review-app/pipelines/73', + details: { + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/73', + }, + duration: null, + finished_at: '2017-01-25T00:00:17.130Z', + stages: [{ + name: 'build', + title: 'build: failed', + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/73#build', + }, + path: '/root/review-app/pipelines/73#build', + dropdown_path: '/root/review-app/pipelines/73/stage.json?stage=build', + }], + artifacts: [], + manual_actions: [ + { + name: 'stop_review', + path: '/root/review-app/builds/1463/play', + }, + { + name: 'name', + path: '/root/review-app/builds/1490/play', + }, + ], + }, + flags: { + latest: true, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: false, + }, + ref: + { + name: 'master', + path: '/root/review-app/tree/master', + tag: false, + branch: true, + }, + commit: { + id: 'fbd79f04fa98717641deaaeb092a4d417237c2e4', + short_id: 'fbd79f04', + title: 'Update .gitlab-ci.yml', + author_name: 'Administrator', + author_email: 'admin@example.com', + created_at: '2017-01-16T12:13:57.000-05:00', + committer_name: 'Administrator', + committer_email: 'admin@example.com', + message: 'Update .gitlab-ci.yml', + author: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + commit_url: 'http://localhost:3000/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4', + commit_path: '/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4', + }, + retry_path: '/root/review-app/pipelines/73/retry', + created_at: '2017-01-16T17:13:59.800Z', + updated_at: '2017-01-25T00:00:17.132Z', +}; diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 new file mode 100644 index 00000000000..3bcc0d1eb18 --- /dev/null +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 @@ -0,0 +1,107 @@ +/* global pipeline, Vue */ + +//= require vue +//= require vue-resource +//= require flash +//= require commit/pipelines/pipelines_store +//= require commit/pipelines/pipelines_service +//= require commit/pipelines/pipelines_table +//= require vue_shared/vue_resource_interceptor +//= require ./mock_data + +describe('Pipelines table in Commits and Merge requests', () => { + preloadFixtures('pipelines_table'); + + beforeEach(() => { + loadFixtures('pipelines_table'); + }); + + describe('successfull request', () => { + describe('without pipelines', () => { + const pipelinesEmptyResponse = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesEmptyResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesEmptyResponse, + ); + }); + + it('should render the empty state', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + done(); + }, 1); + }); + }); + + describe('with pipelines', () => { + const pipelinesResponse = (request, next) => { + next(request.respondWith(JSON.stringify([pipeline]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesResponse, + ); + }); + + it('should render a table with the received pipelines', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1); + done(); + }, 0); + }); + }); + }); + + describe('unsuccessfull request', () => { + const pipelinesErrorResponse = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 500, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesErrorResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesErrorResponse, + ); + }); + + it('should render empty state', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 new file mode 100644 index 00000000000..46a7df3bb21 --- /dev/null +++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 @@ -0,0 +1,31 @@ +//= require vue +//= require commit/pipelines/pipelines_store + +describe('Store', () => { + const store = gl.commits.pipelines.PipelinesStore; + + beforeEach(() => { + store.create(); + }); + + it('should start with a blank state', () => { + expect(store.state.pipelines.length).toBe(0); + }); + + it('should store an array of pipelines', () => { + const pipelines = [ + { + id: '1', + name: 'pipeline', + }, + { + id: '2', + name: 'pipeline_2', + }, + ]; + + store.store(pipelines); + + expect(store.state.pipelines.length).toBe(pipelines.length); + }); +}); diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml new file mode 100644 index 00000000000..fbe4a434f76 --- /dev/null +++ b/spec/javascripts/fixtures/pipelines_table.html.haml @@ -0,0 +1,2 @@ +#commit-pipeline-table-view{ data: { endpoint: "endpoint" } } +.pipeline-svgs{ data: { "commit_icon_svg": "svg"} } diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6 deleted file mode 100644 index efb11211ce2..00000000000 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ /dev/null @@ -1,167 +0,0 @@ -//= require vue -//= require lib/utils/common_utils -//= require vue_pagination/index - -describe('Pagination component', () => { - let component; - - const changeChanges = { - one: '', - two: '', - }; - - const change = (one, two) => { - changeChanges.one = one; - changeChanges.two = two; - }; - - it('should render and start at page 1', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 2, - previousPage: '', - }, - change, - }, - }); - - expect(component.$el.classList).toContain('gl-pagination'); - - component.changePage({ target: { innerText: '1' } }); - - expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); - }); - - it('should go to the previous page', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 3, - previousPage: 1, - }, - change, - }, - }); - - component.changePage({ target: { innerText: 'Prev' } }); - - expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); - }); - - it('should go to the next page', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 5, - previousPage: 3, - }, - change, - }, - }); - - component.changePage({ target: { innerText: 'Next' } }); - - expect(changeChanges.one).toEqual(5); - expect(changeChanges.two).toEqual('all'); - }); - - it('should go to the last page', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 5, - previousPage: 3, - }, - change, - }, - }); - - component.changePage({ target: { innerText: 'Last >>' } }); - - expect(changeChanges.one).toEqual(10); - expect(changeChanges.two).toEqual('all'); - }); - - it('should go to the first page', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 5, - previousPage: 3, - }, - change, - }, - }); - - component.changePage({ target: { innerText: '<< First' } }); - - expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); - }); - - it('should do nothing', () => { - setFixtures('
'); - - component = new window.gl.VueGlPagination({ - el: document.querySelector('.test-pagination-container'), - propsData: { - pageInfo: { - totalPages: 10, - nextPage: 2, - previousPage: '', - }, - change, - }, - }); - - component.changePage({ target: { innerText: '...' } }); - - expect(changeChanges.one).toEqual(1); - expect(changeChanges.two).toEqual('all'); - }); -}); - -describe('paramHelper', () => { - it('can parse url parameters correctly', () => { - window.history.pushState({}, null, '?scope=all&p=2'); - - const scope = gl.utils.getParameterByName('scope'); - const p = gl.utils.getParameterByName('p'); - - expect(scope).toEqual('all'); - expect(p).toEqual('2'); - }); - - it('returns null if param not in url', () => { - window.history.pushState({}, null, '?p=2'); - - const scope = gl.utils.getParameterByName('scope'); - const p = gl.utils.getParameterByName('p'); - - expect(scope).toEqual(null); - expect(p).toEqual('2'); - }); -}); diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 new file mode 100644 index 00000000000..6825de069e4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 @@ -0,0 +1,90 @@ +/* global pipeline */ + +//= require vue +//= require vue_shared/components/pipelines_table_row +//= require commit/pipelines/mock_data + +describe('Pipelines Table Row', () => { + let component; + preloadFixtures('static/environments/element.html.raw'); + + beforeEach(() => { + loadFixtures('static/environments/element.html.raw'); + + component = new gl.pipelines.PipelinesTableRowComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipeline, + svgs: {}, + }, + }); + }); + + it('should render a table row', () => { + expect(component.$el).toEqual('TR'); + }); + + describe('status column', () => { + it('should render a pipeline link', () => { + expect( + component.$el.querySelector('td.commit-link a').getAttribute('href'), + ).toEqual(pipeline.path); + }); + + it('should render status text', () => { + expect( + component.$el.querySelector('td.commit-link a').textContent, + ).toContain(pipeline.details.status.text); + }); + }); + + describe('information column', () => { + it('should render a pipeline link', () => { + expect( + component.$el.querySelector('td:nth-child(2) a').getAttribute('href'), + ).toEqual(pipeline.path); + }); + + it('should render pipeline ID', () => { + expect( + component.$el.querySelector('td:nth-child(2) a > span').textContent, + ).toEqual(`#${pipeline.id}`); + }); + + describe('when a user is provided', () => { + it('should render user information', () => { + expect( + component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'), + ).toEqual(pipeline.user.web_url); + + expect( + component.$el.querySelector('td:nth-child(2) img').getAttribute('title'), + ).toEqual(pipeline.user.name); + }); + }); + }); + + describe('commit column', () => { + it('should render link to commit', () => { + expect( + component.$el.querySelector('td:nth-child(3) .commit-id').getAttribute('href'), + ).toEqual(pipeline.commit.commit_path); + }); + }); + + describe('stages column', () => { + it('should render an icon for each stage', () => { + expect( + component.$el.querySelectorAll('td:nth-child(4) .js-builds-dropdown-button').length, + ).toEqual(pipeline.details.stages.length); + }); + }); + + describe('actions column', () => { + it('should render the provided actions', () => { + expect( + component.$el.querySelectorAll('td:nth-child(6) ul li').length, + ).toEqual(pipeline.details.manual_actions.length); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 new file mode 100644 index 00000000000..cb1006d44dc --- /dev/null +++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 @@ -0,0 +1,67 @@ +/* global pipeline */ + +//= require vue +//= require vue_shared/components/pipelines_table +//= require commit/pipelines/mock_data +//= require lib/utils/datetime_utility + +describe('Pipelines Table', () => { + preloadFixtures('static/environments/element.html.raw'); + + beforeEach(() => { + loadFixtures('static/environments/element.html.raw'); + }); + + describe('table', () => { + let component; + beforeEach(() => { + component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [], + svgs: {}, + }, + }); + }); + + it('should render a table', () => { + expect(component.$el).toEqual('TABLE'); + }); + + it('should render table head with correct columns', () => { + expect(component.$el.querySelector('th.js-pipeline-status').textContent).toEqual('Status'); + expect(component.$el.querySelector('th.js-pipeline-info').textContent).toEqual('Pipeline'); + expect(component.$el.querySelector('th.js-pipeline-commit').textContent).toEqual('Commit'); + expect(component.$el.querySelector('th.js-pipeline-stages').textContent).toEqual('Stages'); + expect(component.$el.querySelector('th.js-pipeline-date').textContent).toEqual(''); + expect(component.$el.querySelector('th.js-pipeline-actions').textContent).toEqual(''); + }); + }); + + describe('without data', () => { + it('should render an empty table', () => { + const component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [], + svgs: {}, + }, + }); + expect(component.$el.querySelectorAll('tbody tr').length).toEqual(0); + }); + }); + + describe('with data', () => { + it('should render rows', () => { + const component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [pipeline], + svgs: {}, + }, + }); + + expect(component.$el.querySelectorAll('tbody tr').length).toEqual(1); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 new file mode 100644 index 00000000000..6a0fec43d2e --- /dev/null +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 @@ -0,0 +1,168 @@ +//= require vue +//= require lib/utils/common_utils +//= require vue_shared/components/table_pagination +/* global fixture, gl */ + +describe('Pagination component', () => { + let component; + + const changeChanges = { + one: '', + two: '', + }; + + const change = (one, two) => { + changeChanges.one = one; + changeChanges.two = two; + }; + + it('should render and start at page 1', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 2, + previousPage: '', + }, + change, + }, + }); + + expect(component.$el.classList).toContain('gl-pagination'); + + component.changePage({ target: { innerText: '1' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); + + it('should go to the previous page', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 3, + previousPage: 1, + }, + change, + }, + }); + + component.changePage({ target: { innerText: 'Prev' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); + + it('should go to the next page', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changePage({ target: { innerText: 'Next' } }); + + expect(changeChanges.one).toEqual(5); + expect(changeChanges.two).toEqual('all'); + }); + + it('should go to the last page', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changePage({ target: { innerText: 'Last >>' } }); + + expect(changeChanges.one).toEqual(10); + expect(changeChanges.two).toEqual('all'); + }); + + it('should go to the first page', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 5, + previousPage: 3, + }, + change, + }, + }); + + component.changePage({ target: { innerText: '<< First' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); + + it('should do nothing', () => { + setFixtures('
'); + + component = new window.gl.VueGlPagination({ + el: document.querySelector('.test-pagination-container'), + propsData: { + pageInfo: { + totalPages: 10, + nextPage: 2, + previousPage: '', + }, + change, + }, + }); + + component.changePage({ target: { innerText: '...' } }); + + expect(changeChanges.one).toEqual(1); + expect(changeChanges.two).toEqual('all'); + }); +}); + +describe('paramHelper', () => { + it('can parse url parameters correctly', () => { + window.history.pushState({}, null, '?scope=all&p=2'); + + const scope = gl.utils.getParameterByName('scope'); + const p = gl.utils.getParameterByName('p'); + + expect(scope).toEqual('all'); + expect(p).toEqual('2'); + }); + + it('returns null if param not in url', () => { + window.history.pushState({}, null, '?p=2'); + + const scope = gl.utils.getParameterByName('scope'); + const p = gl.utils.getParameterByName('p'); + + expect(scope).toEqual(null); + expect(p).toEqual('2'); + }); +}); -- cgit v1.2.1 From 45966b0abc70986f8dbd1694f8cef23546c81385 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 1 Feb 2017 19:17:58 +0100 Subject: Fix syntax error in the new merge request view --- app/views/projects/merge_requests/_new_submit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index e00ae629e4b..38259faf62f 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -46,7 +46,7 @@ -# This tab is always loaded via AJAX - if @pipelines.any? #pipelines.pipelines.tab-pane - = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) + = render "projects/merge_requests/show/pipelines", endpoint: link_to(url_for(params)) .mr-loading-status = spinner -- cgit v1.2.1 From 921141aebdf70161ecd3b2eb9038d271f5a3331c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 1 Feb 2017 19:20:08 +0100 Subject: Serialize pipelines in the new merge request action --- app/controllers/projects/merge_requests_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 68f6208c2be..38a1946a71e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -224,7 +224,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def new - define_new_vars + respond_to do |format| + format.html { define_new_vars } + format.json do + render json: { pipelines: PipelineSerializer + .new(project: @project, user: @current_user) + .represent(@pipelines) } + end + end end def new_diffs -- cgit v1.2.1 From 562b5015edaecb09d1237cba7ed820b95ec425f7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 1 Feb 2017 20:06:11 +0100 Subject: Add basic specs for new merge requests pipelines API --- .../projects/merge_requests_controller_spec.rb | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index e019541e74f..e100047579d 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -22,23 +22,35 @@ describe Projects::MergeRequestsController do render_views let(:fork_project) { create(:forked_project_with_submodules) } + before { fork_project.team << [user, :master] } - before do - fork_project.team << [user, :master] + context 'when rendering HTML response' do + it 'renders new merge request widget template' do + submit_new_merge_request + + expect(response).to be_success + end end - it 'renders it' do - get :new, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project.to_param, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - } + context 'when rendering JSON response' do + it 'renders JSON including serialized pipelines' do + submit_new_merge_request(format: :json) - expect(response).to be_success + expect(json_response).to have_key('pipelines') + expect(response).to be_ok + end end end + + def submit_new_merge_request(format: :html) + get :new, + namespace_id: fork_project.namespace.to_param, + project_id: fork_project.to_param, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' }, + format: format + end end shared_examples "loads labels" do |action| -- cgit v1.2.1 From afa929143251e0c0558657899132fa11823a2e57 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 1 Feb 2017 19:53:03 +0000 Subject: Adds changelog entry --- changelogs/unreleased/fe-commit-mr-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fe-commit-mr-pipelines.yml diff --git a/changelogs/unreleased/fe-commit-mr-pipelines.yml b/changelogs/unreleased/fe-commit-mr-pipelines.yml new file mode 100644 index 00000000000..b5cc6bbf8b6 --- /dev/null +++ b/changelogs/unreleased/fe-commit-mr-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: Use vue.js Pipelines table in commit and merge request view +merge_request: 8844 +author: -- cgit v1.2.1 From 035cb734d27cb6df56803d10be408c6e0cf764f0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 2 Feb 2017 19:43:22 +0000 Subject: Add time ago auto-update to the 2 newest tables --- .../commit/pipelines/pipelines_store.js.es6 | 28 ++++++++++++++++++++++ .../commit/pipelines/pipelines_table.js.es6 | 7 ++++-- .../vue_pipelines_index/pipeline_actions.js.es6 | 4 ++-- .../javascripts/vue_pipelines_index/store.js.es6 | 8 ++++--- app/views/projects/pipelines/index.html.haml | 3 ++- .../projects/merge_requests_controller_spec.rb | 9 +------ 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 index b7d8e97fed3..fe90e7bac0a 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle*/ /** * Pipelines' Store for commits view. * @@ -20,7 +21,34 @@ store(pipelines = []) { this.state.pipelines = pipelines; + return pipelines; }, + + /** + * Once the data is received we will start the time ago loops. + * + * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we + * update the time to show how long as passed. + * + */ + startTimeAgoLoops() { + const startTimeLoops = () => { + this.timeLoopInterval = setInterval(() => { + this.$children[0].$children.reduce((acc, component) => { + const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0]; + acc.push(timeAgoComponent); + return acc; + }, []).forEach(e => e.changeTime()); + }, 10000); + }; + + startTimeLoops(); + + const removeIntervals = () => clearInterval(this.timeLoopInterval); + const startIntervals = () => startTimeLoops(); + + gl.VueRealtimeListener(removeIntervals, startIntervals); + }, }; })(); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 index df7a6455eed..18d57333f61 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 @@ -5,6 +5,7 @@ //= require vue-resource //= require vue_shared/vue_resource_interceptor //= require vue_shared/components/pipelines_table +//= require vue_realtime_listener/index /** * @@ -71,10 +72,12 @@ .then(response => response.json()) .then((json) => { this.store.store(json); + this.store.startTimeAgoLoops.call(this, Vue); this.isLoading = false; - }).catch(() => { + }) + .catch(() => { this.isLoading = false; - new Flash('An error occurred while fetching the pipelines.', 'alert'); + new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert'); }); }, diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 index 9b4897b1a9e..e8f91227345 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 @@ -70,7 +70,7 @@
- +
+ + + + + `, + }); +})(); diff --git a/app/assets/javascripts/boards/components/modal/header.js.es6 b/app/assets/javascripts/boards/components/modal/header.js.es6 index dbbcd73f1fe..87c407f5fec 100644 --- a/app/assets/javascripts/boards/components/modal/header.js.es6 +++ b/app/assets/javascripts/boards/components/modal/header.js.es6 @@ -1,5 +1,6 @@ /* global Vue */ //= require ./tabs +//= require ./filters (() => { const ModalStore = gl.issueBoards.ModalStore; @@ -49,6 +50,7 @@
StatusPipelineCommitStagesStatusPipelineCommitStages
-