Commit c2584ae4 authored by Stan Hu's avatar Stan Hu

Merge branch 'ce-to-ee-2018-10-06' into 'master'

CE upstream - 2018-10-06 00:21 UTC

Closes #2708, #2706, and #7545

See merge request gitlab-org/gitlab-ee!7825
parents dce97a1c c0199c8d
......@@ -16,3 +16,5 @@ db/ @abrandl @NikolayS
/ee/lib/gitlab/code_owners/ @reprazent
/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/auth/ldap/ @dblessing @mkozono
/lib/gitlab/ci/templates/ @nolith @zj
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
......@@ -576,6 +576,15 @@ entry.
- Moves help_popover component to a common location.
## 11.1.8 (2018-10-05)
### Security (3 changes)
- Filter user sensitive data from discussions JSON. !2539
- Properly filter private references from system notes.
- Markdown API no longer displays confidential title references unless authorized.
## 11.1.7 (2018-09-26)
### Security (6 changes)
......
import Visibility from 'visibilityjs';
import Vue from 'vue';
import initDismissableCallout from '~/dismissable_callout';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
......@@ -62,7 +62,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
initDismissableCallout('.js-cluster-security-warning');
Clusters.initDismissableCallout();
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications();
......@@ -105,6 +105,12 @@ export default class Clusters {
});
}
static initDismissableCallout() {
const callout = document.querySelector('.js-cluster-security-warning');
if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
}
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
......
import createFlash from '~/flash';
import { __ } from '~/locale';
import setupToggleButtons from '~/toggle_buttons';
import initDismissableCallout from '~/dismissable_callout';
import PersistentUserCallout from '../persistent_user_callout';
import ClustersService from './services/clusters_service';
export default () => {
const clusterList = document.querySelector('.js-clusters-list');
initDismissableCallout('.gcp-signup-offer');
const callout = document.querySelector('.gcp-signup-offer');
if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
// The empty state won't have a clusterList
if (clusterList) {
......
......@@ -28,7 +28,7 @@ export default {
return diffModes[diffModeKey] || diffModes.replaced;
},
isTextFile() {
return this.diffFile.text;
return this.diffFile.viewer.name === 'text';
},
},
};
......
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import Flash from '~/flash';
export default function initDismissableCallout(alertSelector) {
const alertEl = document.querySelector(alertSelector);
if (!alertEl) {
return;
}
const closeButtonEl = alertEl.getElementsByClassName('close')[0];
const { dismissEndpoint, featureId } = closeButtonEl.dataset;
closeButtonEl.addEventListener('click', () => {
axios
.post(dismissEndpoint, {
feature_name: featureId,
})
.then(() => {
$(alertEl).alert('close');
})
.catch(() => {
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
});
});
}
import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
......@@ -12,7 +12,9 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
initDismissableCallout('.gcp-signup-offer');
const callout = document.querySelector('.gcp-signup-offer');
if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
initGkeDropdowns();
}
......
......@@ -57,6 +57,21 @@
required: false,
default: '',
},
pagesAvailable: {
type: Boolean,
required: false,
default: false,
},
pagesAccessControlEnabled: {
type: Boolean,
required: false,
default: false,
},
pagesHelpPath: {
type: String,
required: false,
default: '',
},
packagesHelpPath: {
type: String,
required: false,
......@@ -74,6 +89,7 @@
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
packagesEnabled: true,
......@@ -101,6 +117,13 @@
);
},
pagesFeatureAccessLevelOptions() {
if (this.visibilityLevel !== visibilityOptions.PUBLIC) {
return this.featureAccessLevelOptions.concat([[30, 'Everyone']]);
}
return this.featureAccessLevelOptions;
},
repositoryEnabled() {
return this.repositoryAccessLevel > 0;
},
......@@ -120,6 +143,10 @@
this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
if (this.pagesAccessLevel === 20) {
// When from Internal->Private narrow access for only members
this.pagesAccessLevel = 10;
}
this.highlightChanges();
} else if (oldValue === visibilityOptions.PRIVATE) {
// if changing away from private, make enabled features more permissive
......@@ -129,6 +156,7 @@
if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20;
this.highlightChanges();
}
},
......@@ -348,6 +376,18 @@
name="project[project_feature_attributes][snippets_access_level]"
/>
</project-setting-row>
<project-setting-row
v-if="pagesAvailable && pagesAccessControlEnabled"
:help-path="pagesHelpPath"
label="Pages"
help-text="Static website for the project."
>
<project-feature-setting
v-model="pagesAccessLevel"
:options="pagesFeatureAccessLevelOptions"
name="project[project_feature_attributes][pages_access_level]"
/>
</project-setting-row>
</div>
</div>
</template>
// if the "projects dashboard" is a user's default dashboard, when they visit the
// instance root index, the dashboard will be served by the root controller instead
// of a dashboard controller. The root index redirects for all other default dashboards.
import '../dashboard/projects/index';
import axios from './lib/utils/axios_utils';
import { __ } from './locale';
import Flash from './flash';
export default class PersistentUserCallout {
constructor(container) {
const { dismissEndpoint, featureId } = container.dataset;
this.container = container;
this.dismissEndpoint = dismissEndpoint;
this.featureId = featureId;
this.init();
}
init() {
const closeButton = this.container.querySelector('.js-close');
closeButton.addEventListener('click', event => this.dismiss(event));
}
dismiss(event) {
event.preventDefault();
axios
.post(this.dismissEndpoint, {
feature_name: this.featureId,
})
.then(() => {
this.container.remove();
})
.catch(() => {
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
});
}
}
......@@ -1000,6 +1000,7 @@
}
.tree-list-holder {
position: -webkit-sticky;
position: sticky;
top: 100px;
max-height: calc(100vh - 100px);
......
......@@ -211,7 +211,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
environments =
begin
@merge_request.environments_for(current_user).map do |environment|
project = environment.project
project = environment.project
deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
stop_url =
......@@ -221,7 +221,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_project_environment_deployment_path(environment.project, environment, deployment)
metrics_project_environment_deployment_path(project, environment, deployment)
end
metrics_monitoring_url =
......
......@@ -16,10 +16,10 @@ module Projects
@new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
if @new_deploy_token.persisted?
flash[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
end
redirect_to action: :show
render_show
end
private
......
......@@ -368,6 +368,7 @@ class ProjectsController < Projects::ApplicationController
repository_access_level
snippets_access_level
wiki_access_level
pages_access_level
]
]
end
......
......@@ -363,6 +363,7 @@ class IssuableFinder
def use_cte_for_search?
return false unless search
return false unless Gitlab::Database.postgresql?
return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
params[:use_cte_for_search]
end
......
......@@ -122,9 +122,13 @@ class IssuesFinder < IssuableFinder
return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
@user_can_see_all_confidential_issues =
project? &&
project &&
project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
if project? && project
project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL
elsif group
group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL
else
false
end
end
def user_cannot_see_confidential_issues?
......
......@@ -16,7 +16,7 @@ module Types
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages
:create_pages, :destroy_pages, :read_pages_content
end
end
end
......@@ -21,6 +21,29 @@ module DashboardHelper
links.any? { |link| dashboard_nav_link?(link) }
end
def controller_action_to_child_dashboards(controller = controller_name, action = action_name)
case "#{controller}##{action}"
when 'projects#index', 'root#index', 'projects#starred', 'projects#trending'
%w(projects stars)
when 'dashboard#activity'
%w(starred_project_activity project_activity)
when 'groups#index'
%w(groups)
when 'todos#index'
%w(todos)
when 'dashboard#issues'
%w(issues)
when 'dashboard#merge_requests'
%w(merge_requests)
else
[]
end
end
def user_default_dashboard?(user = current_user)
controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard }
end
private
def get_dashboard_nav_links
......
......@@ -456,6 +456,7 @@ module ProjectsHelper
buildsAccessLevel: feature.builds_access_level,
wikiAccessLevel: feature.wiki_access_level,
snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled
}
......@@ -470,7 +471,10 @@ module ProjectsHelper
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
pagesHelpPath: help_page_path('user/project/pages/index.md')
}
end
......
......@@ -792,6 +792,9 @@ module Ci
variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: gitlab_version_info.major.to_s)
variables.append(key: 'CI_SERVER_VERSION_MINOR', value: gitlab_version_info.minor.to_s)
variables.append(key: 'CI_SERVER_VERSION_PATCH', value: gitlab_version_info.patch.to_s)
variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision)
variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage)
......@@ -806,6 +809,10 @@ module Ci
end
end
def gitlab_version_info
@gitlab_version_info ||= Gitlab::VersionInfo.parse(Gitlab::VERSION)
end
def legacy_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_BUILD_REF', value: sha)
......
......@@ -45,12 +45,12 @@ class Note < ActiveRecord::Base
# Banzai::ObjectRenderer.
attr_accessor :redacted_note_html
# Number of user visible references as generated by Banzai::ObjectRenderer
attr_accessor :user_visible_reference_count
# Total of all references as generated by Banzai::ObjectRenderer
attr_accessor :total_reference_count
# Number of user visible references as generated by Banzai::ObjectRenderer
attr_accessor :user_visible_reference_count
# Attribute used to store the attributes that have been changed by quick actions.
attr_accessor :commands_changes
......
......@@ -58,8 +58,8 @@ class Project < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :issues_enabled?, to: :project_feature,
allow_nil: true
:merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
to: :project_feature, allow_nil: true
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
......@@ -361,7 +361,7 @@ class Project < ActiveRecord::Base
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] })
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] })
}
# Picks a feature where the level is exactly that given.
......@@ -423,15 +423,15 @@ class Project < ActiveRecord::Base
end
end
# project features may be "disabled", "internal" or "enabled". If "internal",
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user.
# the feature is either public, enabled, or internal with permission for the user.
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [nil, ProjectFeature::ENABLED]
visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
if user&.admin?
with_feature_enabled(feature)
......@@ -1097,31 +1097,13 @@ class Project < ActiveRecord::Base
end
def find_or_initialize_services(exceptions: [])
services_templates = Service.where(template: true)
available_services_names = Service.available_services_names - exceptions
available_services = available_services_names.map do |service_name|
service = find_service(services, service_name)
if service
service
else
# We should check if template for the service exists
template = find_service(services_templates, service_name)
if template.nil?
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
public_send("build_#{service_name}_service") # rubocop:disable GitlabSecurity/PublicSend
else
Service.build_from_template(id, template)
end
end
find_or_initialize_service(service_name)
end
available_services.reject do |service|
disabled_services.include?(service.to_param)
end
available_services.compact
end
def disabled_services
......@@ -1129,7 +1111,20 @@ class Project < ActiveRecord::Base
end
def find_or_initialize_service(name)
find_or_initialize_services.find { |service| service.to_param == name }
return if disabled_services.include?(name)
service = find_service(services, name)
return service if service
# We should check if template for the service exists
template = find_service(services_templates, name)
if template
Service.build_from_template(id, template)
else
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend
end
end
# rubocop: disable CodeReuse/ServiceClass
......@@ -2289,4 +2284,8 @@ class Project < ActiveRecord::Base
check_access.call
end
end
def services_templates
@services_templates ||= Service.where(template: true)
end
end
......@@ -5,7 +5,8 @@ class ProjectAutoDevops < ActiveRecord::Base
enum deploy_strategy: {
continuous: 0,
manual: 1
manual: 1,
timed_incremental: 2
}
scope :enabled, -> { where(enabled: true) }
......@@ -30,10 +31,7 @@ class ProjectAutoDevops < ActiveRecord::Base
value: domain.presence || instance_domain)
end
if manual?
variables.append(key: 'STAGING_ENABLED', value: '1')
variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1')
end
variables.concat(deployment_strategy_default_variables)
end
end
......@@ -51,4 +49,16 @@ class ProjectAutoDevops < ActiveRecord::Base
!project.public? &&
!project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present?
end
def deployment_strategy_default_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
if manual?
variables.append(key: 'STAGING_ENABLED', value: '1')
variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1') # deprecated
variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual')
elsif timed_incremental?
variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed')
end
end
end
end
......@@ -13,14 +13,16 @@ class ProjectFeature < ActiveRecord::Base
# Disabled: not enabled for anyone
# Private: enabled only for team members
# Enabled: enabled for everyone able to access the project
# Public: enabled for everyone (only allowed for pages)
#
# Permission levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
PUBLIC = 30
FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze
FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
class << self
def access_level_attribute(feature)
......@@ -46,6 +48,7 @@ class ProjectFeature < ActiveRecord::Base
validates :project, presence: true
validate :repository_children_level
validate :allowed_access_levels
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
......@@ -87,6 +90,16 @@ class ProjectFeature < ActiveRecord::Base
issues_access_level > DISABLED
end
def pages_enabled?
pages_access_level > DISABLED
end
def public_pages?
return true unless Gitlab.config.pages.access_control
pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public?
end
private
# Validates builds and merge requests access level
......@@ -101,6 +114,17 @@ class ProjectFeature < ActiveRecord::Base
%i(merge_requests_access_level builds_access_level).each(&validator)
end
# Validates access level for other than pages cannot be PUBLIC
def allowed_access_levels
validator = lambda do |field|
level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > ProjectFeature::ENABLED
self.errors.add(field, "cannot have public visibility level") if not_allowed
end
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
end
def get_permission(user, level)
case level
when DISABLED
......@@ -109,6 +133,8 @@ class ProjectFeature < ActiveRecord::Base
user && (project.team.member?(user) || user.full_private_access?)
when ENABLED
true
when PUBLIC
true
else
true
end
......
......@@ -149,7 +149,7 @@ class HipchatService < Service
context.merge!(options)
html = Banzai.post_process(Banzai.render(text, context), context)
html = Banzai.render_and_post_process(text, context)
sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt])
sanitized_html.truncate(200, separator: ' ', omission: '...')
......
......@@ -6,7 +6,8 @@ class UserCallout < ActiveRecord::Base
enum feature_name: {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
cluster_security_warning: 3
cluster_security_warning: 3,
gold_trial: 4
}
validates :user, presence: true
......
......@@ -111,6 +111,7 @@ class ProjectPolicy < BasePolicy
snippets
wiki
builds
pages
]
features.each do |f|
......@@ -168,6 +169,7 @@ class ProjectPolicy < BasePolicy
enable :upload_file
enable :read_cycle_analytics
enable :award_emoji
enable :read_pages_content
end
# These abilities are not allowed to admins that are not members of the project,
......@@ -287,6 +289,8 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:merge_request))
end
rule { pages_disabled }.prevent :read_pages_content
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin_destroy(:milestone))
......@@ -346,6 +350,7 @@ class ProjectPolicy < BasePolicy
enable :download_code
enable :download_wiki_code
enable :read_cycle_analytics
enable :read_pages_content
# NOTE: may be overridden by IssuePolicy
enable :read_issue
......
......@@ -116,6 +116,10 @@ class DiffFileEntity < Grape::Entity
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end
expose :viewer, using: DiffViewerEntity do |diff_file|
diff_file.rich_viewer || diff_file.simple_viewer
end
expose :replaced_view_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
......
# frozen_string_literal: true
class DiffViewerEntity < Grape::Entity
# Partial name refers directly to a Rails feature, let's avoid
# using this on the frontend.
expose :partial_name, as: :name
end
......@@ -27,7 +27,7 @@ class DiscussionEntity < Grape::Entity
expose :resolved?, as: :resolved
expose :resolved_by_push?, as: :resolved_by_push
expose :resolved_by
expose :resolved_by, using: NoteUserEntity
expose :resolved_at
expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion|
resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id)
......
......@@ -21,7 +21,9 @@ module Projects
def pages_config
{
domains: pages_domains_config,
https_only: project.pages_https_only?
https_only: project.pages_https_only?,
id: project.project_id,
access_control: !project.public_pages?
}
end
......@@ -31,7 +33,9 @@ module Projects
domain: domain.domain,
certificate: domain.certificate,
key: domain.key,
https_only: project.pages_https_only? && domain.https?
https_only: project.pages_https_only? && domain.https?,
id: project.project_id,
access_control: !project.public_pages?
}
end
end
......
......@@ -74,7 +74,11 @@ module Projects
system_hook_service.execute_hooks_for(project, :update)
end
update_pages_config if changing_pages_https_only?
update_pages_config if changing_pages_related_config?
end
def changing_pages_related_config?
changing_pages_https_only? || changing_pages_access_level?
end
def update_failed!
......@@ -104,6 +108,10 @@ module Projects
params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
end
def changing_pages_access_level?
params.dig(:project_feature_attributes, :pages_access_level)
end
def ensure_wiki_exists
ProjectWiki.new(project, project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError
......
......@@ -4,6 +4,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
......
......@@ -3,6 +3,9 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if params[:filter].blank? && @groups.empty?
= render 'shared/groups/empty_state'
- else
......
......@@ -4,6 +4,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
......
......@@ -2,6 +2,9 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
.nav-controls
......
......@@ -4,6 +4,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
......
......@@ -4,6 +4,9 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
%div{ class: container_class }
= render "projects/last_push"
= render 'dashboard/projects_head'
......
......@@ -2,6 +2,9 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if current_user.todos.any?
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
......
......@@ -2,6 +2,9 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if current_user
= render 'dashboard/groups_head'
- else
......
......@@ -2,6 +2,9 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if current_user
= render 'dashboard/projects_head'
- else
......
......@@ -2,6 +2,9 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if current_user
= render 'dashboard/projects_head'
- else
......
......@@ -2,6 +2,9 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
= content_for :above_breadcrumbs_content do
= render_if_exists "shared/gold_trial_callout"
- if current_user
= render 'dashboard/projects_head'
- else
......
- container = @no_breadcrumb_container ? 'container-fluid' : container_class
- hide_top_links = @hide_top_links || false
= yield :above_breadcrumbs_content
%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container
- if defined?(@left_sidebar)
......
......@@ -9,7 +9,7 @@
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
- if show_cluster_security_warning?
.js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning
%button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } &times;
.js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning{ data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } }
%button.close.js-close{ type: "button" } &times;
= s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.")
= link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' }
%button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } &times;
.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
%button.close.js-close{ type: "button" } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.append-right-8
= sprite_icon("information", size: 16)
......
- page_title _("Metrics")
.row
.row.empty-state
.col-sm-12
.svg-content
= image_tag 'illustrations/operations_metrics_empty.svg'
.row.empty-environments
.col-sm-12.text-center
%h4
= s_('Metrics|No deployed environments')
.state-description
= s_('Metrics|Check out the CI/CD documentation on deploying to an environment')
.prepend-top-10
= link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success'
.col-12
.text-content
%h4.text-center
= s_('Metrics|No deployed environments')
%p.state-description
= s_('Metrics|Check out the CI/CD documentation on deploying to an environment')
.text-center
= link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success'
......@@ -45,10 +45,17 @@
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production')
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-deploy'), target: '_blank'
.form-check
= form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input'
= form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production using timed incremental rollout')
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank'
.form-check
= form.radio_button :deploy_strategy, 'manual', class: 'form-check-input'
= form.label :deploy_strategy_manual, class: 'form-check-label' do
= s_('CICD|Automatic deployment to staging, manual deployment to production')
= link_to icon('question-circle'), help_page_path('ci/environments.md', anchor: 'manually-deploying-to-environments'), target: '_blank'
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank'
= f.submit _('Save changes'), class: "btn btn-success prepend-top-15"
---
title: Add GitLab version components to CI environment variables
merge_request: 21853
author:
type: added
---
title: Fix sorting by priority or popularity on group issues page, when also searching
issue content
merge_request: 21521
author:
type: fixed
---
title: Fix timeout when running the RemoveRestrictedTodos background migration
merge_request: 21893
author:
type: fixed
---
title: Add installation type to backup information file
merge_request: 22150
author:
type: changed
---
title: Add access control to GitLab pages and make it possible to enable/disable it in project settings
merge_request: 18589
author: Tuomo Ala-Vannesluoma
type: added
---
title: Add timed incremental rollout to Auto DevOps
merge_request: 22023
author:
type: added
---
title: Mitigate N+1 queries when parsing commit references in comments.
merge_request:
author:
type: performance
---
title: Fix LFS uploaded images not being rendered
merge_request: 22092
author:
type: fixed
---
title: 'Rails5: fix artifacts controller download spec Rails5 has params[:file_type]
as '''' if file_type is included as nil in the request'
merge_request: 22123
author: Jasper Maes
type: other
---
title: Markdown API no longer displays confidential title references unless authorized
merge_request:
author:
type: security
---
title: Properly filter private references from system notes
merge_request:
author:
type: security
---
title: Filter user sensitive data from discussions JSON
merge_request: 2536
author:
type: security
---
title: Update operations metrics empty state
merge_request: 21974
author: George Tsiolis
type: other
......@@ -231,6 +231,7 @@ production: &base
## GitLab Pages
pages:
enabled: false
access_control: false
# The location where pages are stored (default: shared/pages).
# path: shared/pages
......
......@@ -217,6 +217,7 @@ Settings.registry['path'] = Settings.absolute(Settings.registry['path
#
Settings['pages'] ||= Settingslogic.new({})
Settings.pages['enabled'] = false if Settings.pages['enabled'].nil?
Settings.pages['access_control'] = false if Settings.pages['access_control'].nil?
Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"))
Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['host'] ||= "example.com"
......
class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false)
change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED)
end
def down
remove_column :project_features, :pages_access_level
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rescheduling of the revised RemoveRestrictedTodos background migration
class RemoveRestrictedTodosWithCte < ActiveRecord::Migration
DOWNTIME = false
disable_ddl_transaction!
MIGRATION = 'RemoveRestrictedTodos'.freeze
BATCH_SIZE = 1000
DELAY_INTERVAL = 5.minutes.to_i
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
end
def up
Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)')
.each_batch(of: BATCH_SIZE) do |batch, index|
range = batch.pluck('MIN(id)', 'MAX(id)').first
BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, range)
end
end
def down
# nothing to do
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20181001172651) do
ActiveRecord::Schema.define(version: 20181002172433) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -2141,6 +2141,7 @@ ActiveRecord::Schema.define(version: 20181001172651) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
t.integer "pages_access_level", default: 20, null: false
end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
......
......@@ -96,6 +96,9 @@ future GitLab releases.**
| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
| **CI_SERVER_VERSION_MAJOR** | 11.4 | all | GitLab version major component |
| **CI_SERVER_VERSION_MINOR** | 11.4 | all | GitLab version minor component |
| **CI_SERVER_VERSION_PATCH** | 11.4 | all | GitLab version patch component |
| **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
......@@ -344,6 +347,12 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_SERVER_NAME='GitLab CI'
++ export CI_SERVER_VERSION=
++ CI_SERVER_VERSION=
++ export CI_SERVER_VERSION_MAJOR=
++ CI_SERVER_VERSION_MAJOR=
++ export CI_SERVER_VERSION_MINOR=
++ CI_SERVER_VERSION_MINOR=
++ export CI_SERVER_VERSION_PATCH=
++ CI_SERVER_VERSION_PATCH=
++ export CI_SERVER_REVISION=
++ CI_SERVER_REVISION=
++ export GITLAB_CI=true
......@@ -489,6 +498,9 @@ export CI_SERVER="yes"
export CI_SERVER_NAME="GitLab"
export CI_SERVER_REVISION="70606bf"
export CI_SERVER_VERSION="8.9.0"
export CI_SERVER_VERSION_MAJOR="8"
export CI_SERVER_VERSION_MINOR="9"
export CI_SERVER_VERSION_PATCH="0"
export GITLAB_USER_ID="42"
export GITLAB_USER_EMAIL="user@example.com"
export CI_REGISTRY_USER="gitlab-ci-token"
......
......@@ -17,8 +17,8 @@ There are two places defined variables can be used. On the:
| Definition | Can be expanded? | Expansion place | Description |
|--------------------------------------|-------------------|-----------------|--------------|
| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>**Supported:** all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>**Not suported:** variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> |
| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion **doesn't support**: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>Supported: all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>Not suported: variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> |
| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
......@@ -26,7 +26,7 @@ There are two places defined variables can be used. On the:
| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>**Not supported:**<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>Not supported:<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> |
### `config.toml` file
......@@ -55,9 +55,9 @@ since the expansion is done in GitLab before any Runner will get the job.
### GitLab Runner internal variable expansion mechanism
- **Supported:** project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and
- Supported: project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and
variables from triggers, pipeline schedules, and manual pipelines.
- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`).
- Not supported: variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`).
The Runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle
only variables defined as `$variable` and `${variable}`. What's also important, is that
......@@ -73,7 +73,7 @@ by bash/sh (leaving empty strings or some values depending whether the variables
defined or not), but will not work with Windows' cmd/PowerShell, since these shells
are using a different variables syntax.
**Supported:**
Supported:
- The `script` may use all available variables that are default for the shell (e.g., `$PATH` which
should be present in all bash/sh shells) and all variables defined by GitLab CI/CD (project/group variables,
......@@ -106,9 +106,11 @@ The following variables are known as "persisted":
They are:
- **Supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner".
- **Not supported:**
- By the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab".
- Supported for definitions where the ["Expansion place"](#gitlab-ci-yml-file) is:
- Runner.
- Script execution shell.
- Not supported:
- For definitions where the ["Expansion place"](#gitlab-ci-yml-file) is GitLab.
- In the `only` and `except` [variables expressions](README.md#variables-expressions).
## Variables with an environment scope
......
......@@ -239,14 +239,19 @@ project's **Settings > CI/CD > Auto DevOps**.
The available options are:
- **Continuous deployment to production** - enables [Auto Deploy](#auto-deploy)
by setting the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and
[`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables
to false.
- **Automatic deployment to staging, manual deployment to production** - sets the
- **Continuous deployment to production**: Enables [Auto Deploy](#auto-deploy)
with `master` branch directly deployed to production.
- **Continuous deployment to production using timed incremental rollout**: Sets the
[`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production) variable
to `timed`, and production deployment will be executed with a 5 minute delay between
each increment in rollout.
- **Automatic deployment to staging, manual deployment to production**: Sets the
[`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and
[`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables
to true, and the user is responsible for manually deploying to staging and production.
[`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production) variables
to `1` and `manual`. This means:
- `master` branch is directly deployed to staging.
- Manual actions are provided for incremental rollout to production.
## Stages of Auto DevOps
......@@ -609,7 +614,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). |
| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. |
| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
......@@ -730,9 +735,8 @@ to use an incremental rollout to replace just a few pods with the latest code.
This will allow you to first check how the app is behaving, and later manually
increasing the rollout up to 100%.
If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set
`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the
standard `production` job, 4 different
If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead
of the standard `production` job, 4 different
[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph)
will be created:
......@@ -756,21 +760,45 @@ environment page.
Below, you can see how the pipeline will look if the rollout or staging
variables are defined.
- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`**
Without `INCREMENTAL_ROLLOUT_MODE` and without `STAGING_ENABLED`:
![Staging and rollout disabled](img/rollout_staging_disabled.png)
Without `INCREMENTAL_ROLLOUT_MODE` and with `STAGING_ENABLED`:
![Staging and rollout disabled](img/rollout_staging_disabled.png)
![Staging enabled](img/staging_enabled.png)
- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`**
With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and without `STAGING_ENABLED`:
![Staging enabled](img/staging_enabled.png)
![Rollout enabled](img/rollout_enabled.png)
- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`**
With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and with `STAGING_ENABLED`
![Rollout and staging enabled](img/rollout_staging_enabled.png)
CAUTION: **Caution:**
Before GitLab 11.4 this feature was enabled by the presence of the
`INCREMENTAL_ROLLOUT_ENABLED` environment variable.
This configuration is deprecated and will be removed in the future.
#### Timed incremental rollout to production **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7545) in GitLab 11.4.
TIP: **Tip:**
You can also set this inside your [project's settings](#deployment-strategy).
![Rollout enabled](img/rollout_enabled.png)
This configuration based on
[incremental rollout to production](#incremental-rollout-to-production).
- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`**
Everything behaves the same way, except:
![Rollout and staging enabled](img/rollout_staging_enabled.png)
- It's enabled by setting the `INCREMENTAL_ROLLOUT_MODE` variable to `timed`.
- Instead of the standard `production` job, the following jobs with a 5 minute delay between each are created:
1. `timed rollout 10%`
1. `timed rollout 25%`
1. `timed rollout 50%`
1. `timed rollout 100%`
## Currently supported languages
......
......@@ -116,6 +116,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
- Everyone: enabled for everyone (only available for GitLab Pages)
### Protected branches
......@@ -248,6 +249,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
- Everyone: enabled for everyone (only available for GitLab Pages)
## GitLab CI/CD permissions
......
......@@ -12,7 +12,8 @@ module API
detail "This feature was introduced in GitLab 11.0."
end
post do
context = { only_path: false }
context = { only_path: false, current_user: current_user }
context[:pipeline] = params[:gfm] ? :full : :plain_markdown
if params[:project]
project = Project.find_by_full_path(params[:project])
......@@ -24,9 +25,7 @@ module API
context[:skip_project_check] = true
end
context[:pipeline] = params[:gfm] ? :full : :plain_markdown
{ html: Banzai.render(params[:text], context) }
{ html: Banzai.render_and_post_process(params[:text], context) }
end
end
end
......
......@@ -289,6 +289,12 @@ module API
present_projects forks
end
desc 'Check pages access of this project'
get ':id/pages_access' do
authorize! :read_pages_content, user_project unless user_project.public_pages?
status 200
end
desc 'Update an existing project' do
success Entities::Project
end
......
......@@ -243,6 +243,7 @@ module Backup
backup_created_at: Time.now,
gitlab_version: Gitlab::VERSION,
tar_version: tar_version,
installation_type: Gitlab::INSTALLATION_TYPE,
skipped: ENV["SKIP"]
}
end
......
module Banzai
# if you need to render markdown, then you probably need to post_process as well,
# such as removing references that the current user doesn't have
# permission to make
def self.render_and_post_process(text, context = {})
post_process(render(text, context), context)
end
def self.render(text, context = {})
Renderer.render(text, context)
end
......
......@@ -215,7 +215,7 @@ module Banzai
#
def projects_for_nodes(nodes)
@projects_for_nodes ||=
grouped_objects_for_nodes(nodes, Project, 'data-project')
grouped_objects_for_nodes(nodes, Project.includes(:project_feature), 'data-project')
end
def can?(user, permission, subject = :global)
......
# frozen_string_literal: true
# rubocop:disable Style/Documentation
# rubocop:disable Metrics/ClassLength
module Gitlab
module BackgroundMigration
......@@ -49,11 +50,14 @@ module Gitlab
private
def remove_non_members_todos(project_id)
Todo.where(project_id: project_id)
.where('user_id NOT IN (?)', authorized_users(project_id))
.each_batch(of: 5000) do |batch|
batch.delete_all
end
if Gitlab::Database.postgresql?
batch_remove_todos_cte(project_id)
else
unauthorized_project_todos(project_id)
.each_batch(of: 5000) do |batch|
batch.delete_all
end
end
end
def remove_confidential_issue_todos(project_id)
......@@ -86,10 +90,13 @@ module Gitlab
next if target_types.empty?
Todo.where(project_id: project_id)
.where('user_id NOT IN (?)', authorized_users(project_id))
.where(target_type: target_types)
.delete_all
if Gitlab::Database.postgresql?
batch_remove_todos_cte(project_id, target_types)
else
unauthorized_project_todos(project_id)
.where(target_type: target_types)
.delete_all
end
end
end
......@@ -100,6 +107,65 @@ module Gitlab
def authorized_users(project_id)
ProjectAuthorization.select(:user_id).where(project_id: project_id)
end
def unauthorized_project_todos(project_id)
Todo.where(project_id: project_id)
.where('user_id NOT IN (?)', authorized_users(project_id))
end
def batch_remove_todos_cte(project_id, target_types = nil)
loop do
count = remove_todos_cte(project_id, target_types)
break if count == 0
end
end
def remove_todos_cte(project_id, target_types = nil)
sql = []
sql << with_all_todos_sql(project_id, target_types)
sql << as_deleted_sql
sql << "SELECT count(*) FROM deleted"
result = Todo.connection.exec_query(sql.join(' '))
result.rows[0][0].to_i
end
def with_all_todos_sql(project_id, target_types = nil)
if target_types
table = Arel::Table.new(:todos)
in_target = table[:target_type].in(target_types)
target_types_sql = " AND #{in_target.to_sql}"
end
<<-SQL
WITH all_todos AS (
SELECT id
FROM "todos"
WHERE "todos"."project_id" = #{project_id}
AND (user_id NOT IN (
SELECT "project_authorizations"."user_id"
FROM "project_authorizations"
WHERE "project_authorizations"."project_id" = #{project_id})
#{target_types_sql}
)
),
SQL
end
def as_deleted_sql
<<-SQL
deleted AS (
DELETE FROM todos
WHERE id IN (
SELECT id
FROM all_todos
LIMIT 5000
)
RETURNING id
)
SQL
end
end
end
end
......@@ -25,8 +25,9 @@
# level, or manually added below.
#
# Continuous deployment to production is enabled by default.
# If you want to deploy to staging first, or enable incremental rollouts,
# set STAGING_ENABLED or INCREMENTAL_ROLLOUT_ENABLED environment variables.
# If you want to deploy to staging first, set STAGING_ENABLED environment variable.
# If you want to enable incremental rollout, either manual or time based,
# set INCREMENTAL_ROLLOUT_TYPE environment variable to "manual" or "timed".
# If you want to use canary deployments, set CANARY_ENABLED environment variable.
#
# If Auto DevOps fails to detect the proper buildpack, or if you want to
......@@ -61,6 +62,10 @@ stages:
- staging
- canary
- production
- incremental rollout 10%
- incremental rollout 25%
- incremental rollout 50%
- incremental rollout 100%
- performance
- cleanup
......@@ -282,11 +287,6 @@ stop_review:
variables:
- $REVIEW_DISABLED
# Keys that start with a dot (.) will not be processed by GitLab CI.
# Staging and canary jobs are disabled by default, to enable them
# remove the dot (.) before the job name.
# https://docs.gitlab.com/ee/ci/yaml/README.html#hidden-keys
# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
# If you prefer to automatically deploy to staging and
......@@ -368,6 +368,7 @@ production:
- $STAGING_ENABLED
- $CANARY_ENABLED
- $INCREMENTAL_ROLLOUT_ENABLED
- $INCREMENTAL_ROLLOUT_MODE
production_manual:
<<: *production_template
......@@ -383,11 +384,11 @@ production_manual:
except:
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
- $INCREMENTAL_ROLLOUT_MODE
# This job implements incremental rollout on for every push to `master`.
.rollout: &rollout_template
stage: production
script:
- check_kube_domain
- install_dependencies
......@@ -405,52 +406,77 @@ production_manual:
artifacts:
paths: [environment_url.txt]
rollout 10%:
.manual_rollout_template: &manual_rollout_template
<<: *rollout_template
variables:
ROLLOUT_PERCENTAGE: 10
stage: production
when: manual
# This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4)
only:
refs:
- master
kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_MODE == "manual"
- $INCREMENTAL_ROLLOUT_ENABLED
except:
variables:
- $INCREMENTAL_ROLLOUT_MODE == "timed"
rollout 25%:
.timed_rollout_template: &timed_rollout_template
<<: *rollout_template
variables:
ROLLOUT_PERCENTAGE: 25
when: manual
when: delayed
start_in: 5 minutes
only:
refs:
- master
kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
- $INCREMENTAL_ROLLOUT_MODE == "timed"
timed rollout 10%:
<<: *timed_rollout_template
stage: incremental rollout 10%
variables:
ROLLOUT_PERCENTAGE: 10
timed rollout 25%:
<<: *timed_rollout_template
stage: incremental rollout 25%
variables:
ROLLOUT_PERCENTAGE: 25
timed rollout 50%:
<<: *timed_rollout_template
stage: incremental rollout 50%
variables:
ROLLOUT_PERCENTAGE: 50
timed rollout 100%:
<<: *timed_rollout_template
<<: *production_template
stage: incremental rollout 100%
variables:
ROLLOUT_PERCENTAGE: 100
rollout 10%:
<<: *manual_rollout_template
variables:
ROLLOUT_PERCENTAGE: 10
rollout 25%:
<<: *manual_rollout_template
variables:
ROLLOUT_PERCENTAGE: 25
rollout 50%:
<<: *rollout_template
<<: *manual_rollout_template
variables:
ROLLOUT_PERCENTAGE: 50
when: manual
only:
refs:
- master
kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
rollout 100%:
<<: *manual_rollout_template
<<: *production_template
when: manual
allow_failure: false
only:
refs:
- master
kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
# ---------------------------------------------------------------------------
......
......@@ -1327,6 +1327,9 @@ msgstr ""
msgid "CICD|Continuous deployment to production"
msgstr ""
msgid "CICD|Continuous deployment to production using timed incremental rollout"
msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
......
......@@ -234,8 +234,8 @@ describe GroupsController do
end
describe 'GET #issues' do
let(:issue_1) { create(:issue, project: project) }
let(:issue_2) { create(:issue, project: project) }
let(:issue_1) { create(:issue, project: project, title: 'foo') }
let(:issue_2) { create(:issue, project: project, title: 'bar') }
before do
create_list(:award_emoji, 3, awardable: issue_2)
......@@ -256,6 +256,31 @@ describe GroupsController do
expect(assigns(:issues)).to eq [issue_2, issue_1]
end
end
context 'searching' do
# Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271
before do
stub_feature_flags(use_cte_for_group_issues_search: false)
end
it 'works with popularity sort' do
get :issues, id: group.to_param, search: 'foo', sort: 'popularity'
expect(assigns(:issues)).to eq([issue_1])
end
it 'works with priority sort' do
get :issues, id: group.to_param, search: 'foo', sort: 'priority'
expect(assigns(:issues)).to eq([issue_1])
end
it 'works with label priority sort' do
get :issues, id: group.to_param, search: 'foo', sort: 'label_priority'
expect(assigns(:issues)).to eq([issue_1])
end
end
end
describe 'GET #merge_requests' do
......
......@@ -19,15 +19,17 @@ describe Projects::ArtifactsController do
end
describe 'GET download' do
subject { get :download, namespace_id: project.namespace, project_id: project, job_id: job, file_type: file_type }
def download_artifact(extra_params = {})
params = { namespace_id: project.namespace, project_id: project, job_id: job }.merge(extra_params)
context 'when no file type is supplied' do
let(:file_type) { nil }
get :download, params
end
context 'when no file type is supplied' do
it 'sends the artifacts file' do
expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original
subject
download_artifact
end
end
......@@ -36,7 +38,7 @@ describe Projects::ArtifactsController do
let(:file_type) { 'invalid' }
it 'returns 404' do
subject
download_artifact(file_type: file_type)
expect(response).to have_gitlab_http_status(404)
end
......@@ -52,7 +54,7 @@ describe Projects::ArtifactsController do
it 'sends the codequality report' do
expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original
subject
download_artifact(file_type: file_type)
end
end
end
......
......@@ -763,25 +763,35 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' do
context 'the environment is from a forked project' do
let!(:forked) { fork_project(project, user, repository: true) }
let!(:environment) { create(:environment, project: forked) }
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
let(:admin) { create(:admin) }
let!(:forked) { fork_project(project, user, repository: true) }
let!(:environment) { create(:environment, project: forked) }
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
let(:admin) { create(:admin) }
let(:merge_request) do
create(:merge_request, source_project: forked, target_project: project)
end
before do
it 'links to the environment on that project' do
get_ci_environments_status
expect(json_response.first['url']).to match /#{forked.full_path}/
end
# we're trying to reduce the overall number of queries for this method.
# set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287
it 'keeps queries in check' do
control_count = ActiveRecord::QueryRecorder.new { get_ci_environments_status }.count
expect(control_count).to be <= 137
end
def get_ci_environments_status
get :ci_environments_status,
namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project,
id: merge_request.iid, format: 'json'
end
it 'links to the environment on that project' do
expect(json_response.first['url']).to match /#{forked.full_path}/
end
end
end
......
......@@ -5,8 +5,16 @@ FactoryBot.define do
domain "example.com"
deploy_strategy :continuous
trait :manual do
deploy_strategy :manual
trait :continuous_deployment do
deploy_strategy ProjectAutoDevops.deploy_strategies[:continuous] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically
end
trait :manual_deployment do
deploy_strategy ProjectAutoDevops.deploy_strategies[:manual] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically
end
trait :timed_incremental_deployment do
deploy_strategy ProjectAutoDevops.deploy_strategies[:timed_incremental] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically
end
trait :disabled do
......
require_relative '../support/helpers/test_env'
FactoryBot.define do
PAGES_ACCESS_LEVEL_SCHEMA_VERSION = 20180423204600
# Project without repository
#
# Project does not have bare repository.
......@@ -23,6 +25,7 @@ FactoryBot.define do
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
pages_access_level ProjectFeature::ENABLED
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
......@@ -34,13 +37,20 @@ FactoryBot.define do
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
project.project_feature.update(
hash = {
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level)
repository_access_level: evaluator.repository_access_level
}
if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION
hash.store("pages_access_level", evaluator.pages_access_level)
end
project.project_feature.update(hash)
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
......@@ -287,6 +297,10 @@ FactoryBot.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
trait(:pages_public) { pages_access_level ProjectFeature::PUBLIC }
trait(:pages_enabled) { pages_access_level ProjectFeature::ENABLED }
trait(:pages_disabled) { pages_access_level ProjectFeature::DISABLED }
trait(:pages_private) { pages_access_level ProjectFeature::PRIVATE }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
......
......@@ -568,12 +568,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
it 'shows delayed job', :js do
time_diff = [0, job.scheduled_at - Time.now].max
expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This is a scheduled to run in')
expect(page).to have_content("This job will automatically run after it's timer finishes.")
expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S"))
expect(page).to have_link('Unschedule job')
end
......
{
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": ["string"] }
},
"additionalProperties": false
}
{
"type": "object",
"required": [
"id",
"state",
"avatar_url",
"path",
"name",
"username"
],
"properties": {
"id": { "type": "integer" },
"state": { "type": "string" },
"avatar_url": { "type": "string" },
"path": { "type": "string" },
"name": { "type": "string" },
"username": { "type": "string" },
"status_tooltip_html": { "$ref": "../types/nullable_string.json" }
},
"additionalProperties": false
}
......@@ -10,7 +10,7 @@ describe Types::PermissionTypes::Project do
:read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label,
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content
]
expect(described_class).to have_graphql_fields(expected_permissions)
......
......@@ -8,13 +8,12 @@ import diffFileMockData from '../mock_data/diff_file';
describe('DiffContent', () => {
const Component = Vue.extend(DiffContentComponent);
let vm;
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
beforeEach(() => {
vm = mountComponentWithStore(Component, {
store,
props: {
diffFile: getDiffFileMock(),
diffFile: JSON.parse(JSON.stringify(diffFileMockData)),
},
});
});
......@@ -43,7 +42,7 @@ describe('DiffContent', () => {
describe('Non-Text diffs', () => {
beforeEach(() => {
vm.diffFile.text = false;
vm.diffFile.viewer.name = 'image';
});
describe('image diff', () => {
......
......@@ -6,11 +6,10 @@ import diffFileMockData from '../mock_data/diff_file';
describe('DiffFile', () => {
let vm;
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
beforeEach(() => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
file: getDiffFileMock(),
file: JSON.parse(JSON.stringify(diffFileMockData)),
canCurrentUserFork: false,
}).$mount();
});
......@@ -18,7 +17,7 @@ describe('DiffFile', () => {
describe('template', () => {
it('should render component with file header, file content components', () => {
const el = vm.$el;
const { fileHash, filePath } = diffFileMockData;
const { fileHash, filePath } = vm.file;
expect(el.id).toEqual(fileHash);
expect(el.classList.contains('diff-file')).toEqual(true);
......
......@@ -23,6 +23,9 @@ export default {
aMode: '100644',
bMode: '100644',
text: true,
viewer: {
name: 'text',
},
addedLines: 2,
removedLines: 0,
diffRefs: {
......
......@@ -120,4 +120,22 @@ describe Banzai::ReferenceParser::CommitParser do
expect(subject.find_commits(project, %w{123})).to eq([])
end
end
context 'when checking commits on another projects' do
let(:control_links) do
[commit_link]
end
let(:actual_links) do
control_links + [commit_link, commit_link]
end
def commit_link
project = create(:project, :repository, :public)
Nokogiri::HTML.fragment(%Q{<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>}).children[0]
end
it_behaves_like 'no project N+1 queries'
end
end
......@@ -509,6 +509,7 @@ ProjectFeature:
- snippets_access_level
- builds_access_level
- repository_access_level
- pages_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
......
......@@ -57,6 +57,9 @@ describe 'Gitlab::VersionInfo' do
context 'parse' do
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
end
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20180423204600_add_pages_access_level_to_project_feature.rb')
describe AddPagesAccessLevelToProjectFeature, :migration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:features) { table(:project_features) }
let!(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab') }
let!(:first_project) { projects.create(name: 'gitlab1', path: 'gitlab1', namespace_id: namespace.id) }
let!(:first_project_features) { features.create(project_id: first_project.id) }
let!(:second_project) { projects.create(name: 'gitlab2', path: 'gitlab2', namespace_id: namespace.id) }
let!(:second_project_features) { features.create(project_id: second_project.id) }
it 'correctly migrate pages for old projects to be public' do
migrate!
# For old projects pages should be public
expect(first_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC
expect(second_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC
end
it 'after migration pages are enabled as default' do
migrate!
# For new project default is enabled
third_project = projects.create(name: 'gitlab3', path: 'gitlab3', namespace_id: namespace.id)
third_project_features = features.create(project_id: third_project.id)
expect(third_project_features.reload.pages_access_level).to eq ProjectFeature::ENABLED
end
end
......@@ -1854,6 +1854,7 @@ describe Ci::Build do
describe '#variables' do
let(:container_registry_enabled) { false }
let(:gitlab_version_info) { Gitlab::VersionInfo.parse(Gitlab::VERSION) }
let(:predefined_variables) do
[
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
......@@ -1871,6 +1872,9 @@ describe Ci::Build do
{ key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_VERSION_MAJOR', value: gitlab_version_info.major.to_s, public: true },
{ key: 'CI_SERVER_VERSION_MINOR', value: gitlab_version_info.minor.to_s, public: true },
{ key: 'CI_SERVER_VERSION_PATCH', value: gitlab_version_info.patch.to_s, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true },
{ key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true },
......
......@@ -65,7 +65,8 @@ describe InternalId do
context 'with an insufficient schema version' do
before do
described_class.reset_column_information
expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
# Project factory will also call the current_version
expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
end
let(:init) { double('block') }
......
......@@ -70,24 +70,31 @@ describe ProjectAutoDevops do
end
context 'when deploy_strategy is manual' do
let(:domain) { 'example.com' }
before do
auto_devops.deploy_strategy = 'manual'
let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) }
let(:expected_variables) do
[
{ key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual' },
{ key: 'STAGING_ENABLED', value: '1' },
{ key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1' }
]
end
it { expect(auto_devops.predefined_variables).to include(*expected_variables) }
end
context 'when deploy_strategy is continuous' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :continuous_deployment, project: project) }
it do
expect(auto_devops.predefined_variables.map { |var| var[:key] })
.to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
.not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
end
end
context 'when deploy_strategy is continuous' do
let(:domain) { 'example.com' }
context 'when deploy_strategy is timed_incremental' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :timed_incremental_deployment, project: project) }
before do
auto_devops.deploy_strategy = 'continuous'
end
it { expect(auto_devops.predefined_variables).to include(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') }
it do
expect(auto_devops.predefined_variables.map { |var| var[:key] })
......
......@@ -17,7 +17,7 @@ describe ProjectFeature do
end
describe '#feature_available?' do
let(:features) { %w(issues wiki builds merge_requests snippets repository) }
let(:features) { %w(issues wiki builds merge_requests snippets repository pages) }
context 'when features are disabled' do
it "returns false" do
......@@ -121,6 +121,19 @@ describe ProjectFeature do
end
end
context 'public features' do
it "does not allow public for other than pages" do
features = %w(issues wiki builds merge_requests snippets repository)
project_feature = project.project_feature
features.each do |feature|
field = "#{feature}_access_level".to_sym
project_feature.update_attribute(field, ProjectFeature::PUBLIC)
expect(project_feature.valid?).to be_falsy
end
end
end
describe '#*_enabled?' do
let(:features) { %w(wiki builds merge_requests) }
......
......@@ -4354,6 +4354,40 @@ describe Project do
end
end
describe "#find_or_initialize_services" do
subject { build(:project) }
it 'returns only enabled services' do
allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover))
allow(subject).to receive(:disabled_services).and_return(%w(prometheus))
services = subject.find_or_initialize_services
expect(services.count).to eq 1
expect(services).to include(PushoverService)
end
end
describe "#find_or_initialize_service" do
subject { build(:project) }
it 'avoids N+1 database queries' do
allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover))
control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count
allow(Service).to receive(:available_services_names).and_call_original
expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count)
end
it 'returns nil if service is disabled' do
allow(subject).to receive(:disabled_services).and_return(%w(prometheus))
expect(subject.find_or_initialize_service('prometheus')).to be_nil
end
end
def rugged_config
rugged_repo(project.repository).config
end
......
......@@ -106,6 +106,52 @@ describe API::Markdown do
.and include("#1</a>")
end
end
context 'with a public project and confidential issue' do
let(:public_project) { create(:project, :public) }
let(:confidential_issue) { create(:issue, :confidential, project: public_project, title: 'Confidential title') }
let(:text) { ":tada: Hello world! :100: #{confidential_issue.to_reference}" }
let(:params) { { text: text, gfm: true, project: public_project.full_path } }
shared_examples 'user without proper access' do
it 'does not render the title or link' do
expect(response).to have_http_status(201)
expect(json_response["html"]).not_to include('Confidential title')
expect(json_response["html"]).not_to include('<a href=')
expect(json_response["html"]).to include('Hello world!')
.and include('data-name="tada"')
.and include('data-name="100"')
.and include('#1</p>')
end
end
context 'when not logged in' do
let(:user) { }
it_behaves_like 'user without proper access'
end
context 'when logged in as user without access' do
let(:user) { create(:user) }
it_behaves_like 'user without proper access'
end
context 'when logged in as author' do
let(:user) { confidential_issue.author }
it 'renders the title or link' do
expect(response).to have_http_status(201)
expect(json_response["html"]).to include('Confidential title')
expect(json_response["html"]).to include('Hello world!')
.and include('data-name="tada"')
.and include('data-name="100"')
.and include("<a href=\"#{IssuesHelper.url_for_issue(confidential_issue.iid, public_project)}\"")
.and include("#1</a>")
end
end
end
end
end
end
......
require 'spec_helper'
describe "Internal Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
set(:group) { create(:group) }
set(:project) { create(:project, :internal, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
set(:admin) { create(:admin) }
set(:owner) { create(:user) }
set(:master) { create(:user) }
set(:developer) { create(:user) }
set(:reporter) { create(:user) }
set(:guest) { create(:user) }
set(:user) { create(:user) }
before do
allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
group.add_owner(owner)
project.add_master(master)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
describe "Project should be internal" do
describe '#internal?' do
subject { project.internal? }
it { is_expected.to be_truthy }
end
end
describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403
ProjectFeature::DISABLED | "owner" | 403
ProjectFeature::DISABLED | "master" | 403
ProjectFeature::DISABLED | "developer" | 403
ProjectFeature::DISABLED | "reporter" | 403
ProjectFeature::DISABLED | "guest" | 403
ProjectFeature::DISABLED | "user" | 403
ProjectFeature::DISABLED | nil | 404
ProjectFeature::PUBLIC | "admin" | 200
ProjectFeature::PUBLIC | "owner" | 200
ProjectFeature::PUBLIC | "master" | 200
ProjectFeature::PUBLIC | "developer" | 200
ProjectFeature::PUBLIC | "reporter" | 200
ProjectFeature::PUBLIC | "guest" | 200
ProjectFeature::PUBLIC | "user" | 200
ProjectFeature::PUBLIC | nil | 404
ProjectFeature::ENABLED | "admin" | 200
ProjectFeature::ENABLED | "owner" | 200
ProjectFeature::ENABLED | "master" | 200
ProjectFeature::ENABLED | "developer" | 200
ProjectFeature::ENABLED | "reporter" | 200
ProjectFeature::ENABLED | "guest" | 200
ProjectFeature::ENABLED | "user" | 200
ProjectFeature::ENABLED | nil | 404
ProjectFeature::PRIVATE | "admin" | 200
ProjectFeature::PRIVATE | "owner" | 200
ProjectFeature::PRIVATE | "master" | 200
ProjectFeature::PRIVATE | "developer" | 200
ProjectFeature::PRIVATE | "reporter" | 200
ProjectFeature::PRIVATE | "guest" | 200
ProjectFeature::PRIVATE | "user" | 403
ProjectFeature::PRIVATE | nil | 404
end
with_them do
before do
project.project_feature.update(pages_access_level: pages_access_level)
end
it "correct return value" do
if !with_user.nil?
user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user)
else
get api("/projects/#{project.id}/pages_access")
end
expect(response).to have_gitlab_http_status(expected_result)
end
end
end
end
end
require 'spec_helper'
describe "Private Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
set(:group) { create(:group) }
set(:project) { create(:project, :private, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
set(:admin) { create(:admin) }
set(:owner) { create(:user) }
set(:master) { create(:user) }
set(:developer) { create(:user) }
set(:reporter) { create(:user) }
set(:guest) { create(:user) }
set(:user) { create(:user) }
before do
allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
group.add_owner(owner)
project.add_master(master)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
describe "Project should be private" do
describe '#private?' do
subject { project.private? }
it { is_expected.to be_truthy }
end
end
describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403
ProjectFeature::DISABLED | "owner" | 403
ProjectFeature::DISABLED | "master" | 403
ProjectFeature::DISABLED | "developer" | 403
ProjectFeature::DISABLED | "reporter" | 403
ProjectFeature::DISABLED | "guest" | 403
ProjectFeature::DISABLED | "user" | 404
ProjectFeature::DISABLED | nil | 404
ProjectFeature::PUBLIC | "admin" | 200
ProjectFeature::PUBLIC | "owner" | 200
ProjectFeature::PUBLIC | "master" | 200
ProjectFeature::PUBLIC | "developer" | 200
ProjectFeature::PUBLIC | "reporter" | 200
ProjectFeature::PUBLIC | "guest" | 200
ProjectFeature::PUBLIC | "user" | 404
ProjectFeature::PUBLIC | nil | 404
ProjectFeature::ENABLED | "admin" | 200
ProjectFeature::ENABLED | "owner" | 200
ProjectFeature::ENABLED | "master" | 200
ProjectFeature::ENABLED | "developer" | 200
ProjectFeature::ENABLED | "reporter" | 200
ProjectFeature::ENABLED | "guest" | 200
ProjectFeature::ENABLED | "user" | 404
ProjectFeature::ENABLED | nil | 404
ProjectFeature::PRIVATE | "admin" | 200
ProjectFeature::PRIVATE | "owner" | 200
ProjectFeature::PRIVATE | "master" | 200
ProjectFeature::PRIVATE | "developer" | 200
ProjectFeature::PRIVATE | "reporter" | 200
ProjectFeature::PRIVATE | "guest" | 200
ProjectFeature::PRIVATE | "user" | 404
ProjectFeature::PRIVATE | nil | 404
end
with_them do
before do
project.project_feature.update(pages_access_level: pages_access_level)
end
it "correct return value" do
if !with_user.nil?
user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user)
else
get api("/projects/#{project.id}/pages_access")
end
expect(response).to have_gitlab_http_status(expected_result)
end
end
end
end
end
require 'spec_helper'
describe "Public Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
set(:group) { create(:group) }
set(:project) { create(:project, :public, pages_access_level: ProjectFeature::ENABLED, namespace: group) }
set(:admin) { create(:admin) }
set(:owner) { create(:user) }
set(:master) { create(:user) }
set(:developer) { create(:user) }
set(:reporter) { create(:user) }
set(:guest) { create(:user) }
set(:user) { create(:user) }
before do
allow(Gitlab.config.pages).to receive(:access_control).and_return(true)
group.add_owner(owner)
project.add_master(master)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
describe "Project should be public" do
describe '#public?' do
subject { project.public? }
it { is_expected.to be_truthy }
end
end
describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403
ProjectFeature::DISABLED | "owner" | 403
ProjectFeature::DISABLED | "master" | 403
ProjectFeature::DISABLED | "developer" | 403
ProjectFeature::DISABLED | "reporter" | 403
ProjectFeature::DISABLED | "guest" | 403
ProjectFeature::DISABLED | "user" | 403
ProjectFeature::DISABLED | nil | 403
ProjectFeature::PUBLIC | "admin" | 200
ProjectFeature::PUBLIC | "owner" | 200
ProjectFeature::PUBLIC | "master" | 200
ProjectFeature::PUBLIC | "developer" | 200
ProjectFeature::PUBLIC | "reporter" | 200
ProjectFeature::PUBLIC | "guest" | 200
ProjectFeature::PUBLIC | "user" | 200
ProjectFeature::PUBLIC | nil | 200
ProjectFeature::ENABLED | "admin" | 200
ProjectFeature::ENABLED | "owner" | 200
ProjectFeature::ENABLED | "master" | 200
ProjectFeature::ENABLED | "developer" | 200
ProjectFeature::ENABLED | "reporter" | 200
ProjectFeature::ENABLED | "guest" | 200
ProjectFeature::ENABLED | "user" | 200
ProjectFeature::ENABLED | nil | 200
ProjectFeature::PRIVATE | "admin" | 200
ProjectFeature::PRIVATE | "owner" | 200
ProjectFeature::PRIVATE | "master" | 200
ProjectFeature::PRIVATE | "developer" | 200
ProjectFeature::PRIVATE | "reporter" | 200
ProjectFeature::PRIVATE | "guest" | 200
ProjectFeature::PRIVATE | "user" | 403
ProjectFeature::PRIVATE | nil | 403
end
with_them do
before do
project.project_feature.update(pages_access_level: pages_access_level)
end
it "correct return value" do
if !with_user.nil?
user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user)
else
get api("/projects/#{project.id}/pages_access")
end
expect(response).to have_gitlab_http_status(expected_result)
end
end
end
end
end
......@@ -26,6 +26,11 @@ describe DiffFileEntity do
)
end
it 'includes viewer' do
expect(subject[:viewer].with_indifferent_access)
.to match_schema('entities/diff_viewer')
end
# Converted diff files from GitHub import does not contain blob file
# and content sha.
context 'when diff file does not have a blob and content sha' do
......
require 'spec_helper'
describe DiffViewerEntity do
include RepoHelpers
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diff_refs) { commit.diff_refs }
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
let(:viewer) { diff_file.simple_viewer }
subject { described_class.new(viewer).as_json }
it 'serializes diff file viewer' do
expect(subject.with_indifferent_access).to match_schema('entities/diff_viewer')
end
end
......@@ -36,6 +36,13 @@ describe DiscussionEntity do
)
end
it 'resolved_by matches note_user_entity schema' do
Notes::ResolveService.new(note.project, user).execute(note)
expect(subject[:resolved_by].with_indifferent_access)
.to match_schema('entities/note_user_entity')
end
context 'when is LegacyDiffDiscussion' do
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
......
......@@ -341,6 +341,27 @@ describe Projects::UpdateService, '#execute' do
call_service
end
end
context 'when updating #pages_access_level' do
subject(:call_service) do
update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE })
end
it 'updates the attribute' do
expect { call_service }
.to change { project.project_feature.pages_access_level }
.to(ProjectFeature::PRIVATE)
end
it 'calls Projects::UpdatePagesConfigurationService' do
expect(Projects::UpdatePagesConfigurationService)
.to receive(:new)
.with(project)
.and_call_original
call_service
end
end
end
describe '#run_auto_devops_pipeline?' do
......
......@@ -3,7 +3,7 @@ module ReferenceParserHelpers
Nokogiri::HTML.fragment('<a></a>').children[0]
end
shared_examples 'no N+1 queries' do
shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
......@@ -19,6 +19,10 @@ module ReferenceParserHelpers
expect(actual.count).to be <= control.count
expect(actual.cached_count).to be <= control.cached_count
end
end
shared_examples 'no N+1 queries' do
it_behaves_like 'no project N+1 queries'
it 'avoids N+1 queries in #records_for_nodes', :request_store do
context = Banzai::RenderContext.new(project, user)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment