Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-28 09:09:56 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-28 09:09:56 +0300
commit1d42c38d9b4d4ca651db435c8eb0c47bd24a25e2 (patch)
treeadd5ce3542470c7d3626c5669898ca3546f29285
parent3f5f07675c4205c28791486d66bcbe80b7e0eca4 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/models/concerns/avatarable.rb15
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/service.rb19
-rw-r--r--app/services/groups/create_service.rb13
-rw-r--r--app/services/projects/create_service.rb22
-rw-r--r--doc/administration/reference_architectures/index.md2
-rw-r--r--doc/api/api_resources.md2
-rw-r--r--doc/api/releases/index.md2
-rw-r--r--doc/ci/ci_cd_for_external_repos/bitbucket_integration.md2
-rw-r--r--doc/ci/review_apps/index.md2
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/adding_service_component.md2
-rw-r--r--doc/development/architecture.md4
-rw-r--r--doc/development/documentation/index.md6
-rw-r--r--doc/development/integrations/secure_partner_integration.md6
-rw-r--r--doc/development/new_fe_guide/development/index.md2
-rw-r--r--doc/development/testing_guide/end_to_end/feature_flags.md57
-rw-r--r--doc/gitlab-basics/README.md2
-rw-r--r--doc/gitlab-basics/create-branch.md4
-rw-r--r--doc/install/README.md2
-rw-r--r--doc/install/azure/index.md2
-rw-r--r--doc/install/openshift_and_gitlab/index.md2
-rw-r--r--doc/install/structure.md2
-rw-r--r--doc/integration/README.md2
-rw-r--r--doc/integration/jira_development_panel.md2
-rw-r--r--doc/integration/omniauth.md2
-rw-r--r--doc/intro/README.md2
-rw-r--r--doc/operations/index.md2
-rw-r--r--doc/operations/metrics/alerts.md2
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/training/index.md2
-rw-r--r--doc/user/admin_area/settings/index.md2
-rw-r--r--doc/user/analytics/value_stream_analytics.md2
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/index.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md2
-rw-r--r--doc/user/project/index.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md2
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md2
-rw-r--r--doc/user/project/settings/index.md2
-rw-r--r--lib/backup/artifacts.rb4
-rw-r--r--lib/backup/builds.rb4
-rw-r--r--lib/backup/lfs.rb4
-rw-r--r--lib/backup/pages.rb4
-rw-r--r--lib/backup/registry.rb4
-rw-r--r--lib/backup/uploads.rb4
-rw-r--r--qa/qa/runtime/feature.rb169
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb4
-rw-r--r--qa/spec/runtime/feature_spec.rb250
-rw-r--r--spec/models/service_spec.rb24
-rw-r--r--spec/services/admin/propagate_service_template_spec.rb4
-rw-r--r--spec/services/groups/create_service_spec.rb47
-rw-r--r--spec/services/projects/create_service_spec.rb2
55 files changed, 486 insertions, 256 deletions
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 0dd55ab67b5..92926620f8c 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -3,16 +3,11 @@
module Avatarable
extend ActiveSupport::Concern
- ALLOWED_IMAGE_SCALER_WIDTHS = [
- 400,
- 200,
- 64,
- 48,
- 40,
- 26,
- 20,
- 16
- ].freeze
+ USER_AVATAR_SIZES = [16, 20, 23, 24, 26, 32, 36, 38, 40, 48, 60, 64, 96, 120, 160].freeze
+ PROJECT_AVATAR_SIZES = [15, 40, 48, 64, 88].freeze
+ GROUP_AVATAR_SIZES = [15, 37, 38, 39, 40, 64, 96].freeze
+
+ ALLOWED_IMAGE_SCALER_WIDTHS = (USER_AVATAR_SIZES | PROJECT_AVATAR_SIZES | GROUP_AVATAR_SIZES).freeze
included do
prepend ShadowMethods
diff --git a/app/models/project.rb b/app/models/project.rb
index 54925ae4ed8..dacad903439 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2513,10 +2513,10 @@ class Project < ApplicationRecord
def build_from_instance_or_template(name)
instance = find_service(services_instances, name)
- return Service.build_from_integration(id, instance) if instance
+ return Service.build_from_integration(instance, project_id: id) if instance
template = find_service(services_templates, name)
- return Service.build_from_integration(id, template) if template
+ return Service.build_from_integration(template, project_id: id) if template
end
def services_templates
diff --git a/app/models/service.rb b/app/models/service.rb
index af31cf4a335..087985b192a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -215,7 +215,7 @@ class Service < ApplicationRecord
services_names.map { |service_name| "#{service_name}_service".camelize }
end
- def self.build_from_integration(project_id, integration)
+ def self.build_from_integration(integration, project_id: nil, group_id: nil)
service = integration.dup
if integration.supports_data_fields?
@@ -225,9 +225,9 @@ class Service < ApplicationRecord
service.template = false
service.instance = false
- service.group = nil
- service.inherit_from_id = integration.id if integration.instance? || integration.group
service.project_id = project_id
+ service.group_id = group_id
+ service.inherit_from_id = integration.id if integration.instance? || integration.group
service.active = false if service.invalid?
service
end
@@ -255,6 +255,19 @@ class Service < ApplicationRecord
end
private_class_method :instance_level_integration
+ def self.create_from_active_default_integrations(scope, association, with_templates: false)
+ group_ids = scope.ancestors.select(:id)
+ array = group_ids.to_sql.present? ? "array(#{group_ids.to_sql})" : 'ARRAY[]'
+
+ from_union([
+ with_templates ? active.where(template: true) : none,
+ active.where(instance: true),
+ active.where(group_id: group_ids)
+ ]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")).group_by(&:type).each do |type, records|
+ build_from_integration(records.first, association => scope.id).save!
+ end
+ end
+
def activated?
active
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index ce583095168..51dca43fa4a 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -28,9 +28,12 @@ module Groups
@group.build_chat_team(name: response['name'], team_id: response['id'])
end
- if @group.save
- @group.add_owner(current_user)
- add_settings_record
+ Group.transaction do
+ if @group.save
+ @group.add_owner(current_user)
+ @group.create_namespace_settings
+ Service.create_from_active_default_integrations(@group, :group_id) if Feature.enabled?(:group_level_integrations)
+ end
end
@group
@@ -83,10 +86,6 @@ module Groups
params[:visibility_level] = Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
-
- def add_settings_record
- @group.create_namespace_settings
- end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 727d57ef8b9..9943dc46731 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -162,7 +162,7 @@ module Projects
if @project.save
unless @project.gitlab_project_import?
- create_services_from_active_default_integrations_or_templates(@project)
+ Service.create_from_active_default_integrations(@project, :project_id, with_templates: true)
@project.create_labels
end
@@ -228,26 +228,6 @@ module Projects
private
- # rubocop: disable CodeReuse/ActiveRecord
- def create_services_from_active_default_integrations_or_templates(project)
- group_ids = project.ancestors.select(:id)
-
- Service.from_union([
- Service.active.where(instance: true),
- Service.active.where(template: true),
- Service.active.where(group_id: group_ids)
- ]).order(by_type_group_ids_and_instance(group_ids)).group_by(&:type).each do |type, records|
- Service.build_from_integration(project.id, records.first).save!
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def by_type_group_ids_and_instance(group_ids)
- array = group_ids.to_sql.present? ? "array(#{group_ids.to_sql})" : 'ARRAY[]'
-
- Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")
- end
-
def project_namespace
@project_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
end
diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md
index 3964b8daeb7..8816d0eecf4 100644
--- a/doc/administration/reference_architectures/index.md
+++ b/doc/administration/reference_architectures/index.md
@@ -105,7 +105,7 @@ is the least complex to setup. This provides a point-in-time recovery of a prede
> - Supported tiers: [GitLab Starter, Premium, and Ultimate](https://about.gitlab.com/pricing/)
This requires separating out GitLab into multiple application nodes with an added
-[load balancer](../high_availability/load_balancer.md). The load balancer will distribute traffic
+[load balancer](../load_balancer.md). The load balancer will distribute traffic
across GitLab application nodes. Meanwhile, each application node connects to a
shared file server and database systems on the back end. This way, if one of the
application servers fails, the workflow is not interrupted.
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index 898aa713331..3d448b2a65f 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -9,7 +9,7 @@ Available resources for the [GitLab API](README.md) can be grouped in the follow
See also:
- [V3 to V4](v3_to_v4.md).
-- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md).
+- Adding [deploy keys for multiple projects](deploy_keys.md#adding-deploy-keys-to-multiple-projects).
- [API Resources for various templates](#templates-api-resources).
## Project resources
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index 357f7e7a125..54dc2ecaa33 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -365,7 +365,7 @@ POST /projects/:id/releases
| `assets:links` | array of hash | no | An array of assets links. |
| `assets:links:name`| string | required by: `assets:links` | The name of the link. |
| `assets:links:url` | string | required by: `assets:links` | The URL of the link. |
-| `assets:links:filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases.md).
+| `assets:links:filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases/index.md).
| `assets:links:link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`.
| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
diff --git a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
index 74c48f087b2..f8e2a2b7eb0 100644
--- a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
@@ -9,7 +9,7 @@ type: howto
GitLab CI/CD can be used with Bitbucket Cloud by:
-1. Creating a [CI/CD project](../../user/project/ci_cd_for_external_repo.md).
+1. Creating a [CI/CD project](index.md).
1. Connecting your Git repository via URL.
To use GitLab CI/CD with a Bitbucket Cloud repository:
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index e2d5cbcbea4..4092df733c9 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -33,7 +33,7 @@ In the above example:
## How Review Apps work
A Review App is a mapping of a branch with an [environment](../environments/index.md).
-Access to the Review App is made available as a link on the [merge request](../../user/project/merge_requests.md) relevant to the branch.
+Access to the Review App is made available as a link on the [merge request](../../user/project/merge_requests/index.md) relevant to the branch.
The following is an example of a merge request with an environment set dynamically.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index c20ffd68c15..7ddaefa2af0 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1754,7 +1754,7 @@ the pipeline if the following is true:
In the example below, the `test` job will `only` be created when **all** of the following are true:
-- The pipeline has been [scheduled](../../user/project/pipelines/schedules.md) **or** runs for `master`.
+- The pipeline has been [scheduled](../pipelines/schedules.md) **or** runs for `master`.
- The `variables` keyword matches.
- The `kubernetes` service is active on the project.
diff --git a/doc/development/adding_service_component.md b/doc/development/adding_service_component.md
index 2801e27145d..bb5af286c1d 100644
--- a/doc/development/adding_service_component.md
+++ b/doc/development/adding_service_component.md
@@ -46,7 +46,7 @@ TIP: **Tip:**
**For services that depend on the existing GitLab codebase:**
-The first iteration should be opt-in, either through the `gitlab.yml` configuration or through [feature flags](feature_flags.md). For these types of services it is often necessary to [bundle the service and its dependencies with GitLab](#bundling-a-service-with-gitlab) as part of the initial integration.
+The first iteration should be opt-in, either through the `gitlab.yml` configuration or through [feature flags](feature_flags/index.md). For these types of services it is often necessary to [bundle the service and its dependencies with GitLab](#bundling-a-service-with-gitlab) as part of the initial integration.
TIP: **Tip:**
[ActionCable](https://docs.gitlab.com/omnibus/settings/actioncable.html) is an example of a service that has been added this way.
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index b94ee045a60..43582719783 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -873,7 +873,7 @@ To serve repositories over SSH there's an add-on application called GitLab Shell
### Installation folder summary
-To summarize here's the [directory structure of the `git` user home directory](../install/structure.md).
+To summarize here's the [directory structure of the `git` user home directory](../install/installation.md#gitlab-directory-structure).
### Processes
@@ -985,7 +985,7 @@ GitLab Shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
### Maintenance tasks
-[GitLab](https://gitlab.com/gitlab-org/gitlab/tree/master) provides Rake tasks with which you see version information and run a quick check on your configuration to ensure it is configured properly within the application. See [maintenance Rake tasks](../raketasks/maintenance.md).
+[GitLab](https://gitlab.com/gitlab-org/gitlab/tree/master) provides Rake tasks with which you see version information and run a quick check on your configuration to ensure it is configured properly within the application. See [maintenance Rake tasks](../administration/raketasks/maintenance.md).
In a nutshell, do the following:
```shell
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index d6f24d6374d..dc1cf3ec466 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -52,9 +52,9 @@ docs-only merge requests using the following guide:
[Contributions to GitLab docs](workflow.md) are welcome from the entire GitLab community.
-To ensure that GitLab docs are current, there are special processes and responsibilities for all [feature changes](feature-change-workflow.md), that is development work that impacts the appearance, usage, or administration of a feature.
+To ensure that GitLab docs are current, there are special processes and responsibilities for all [feature changes](workflow.md), that is development work that impacts the appearance, usage, or administration of a feature.
-However, anyone can contribute [documentation improvements](improvement-workflow.md) that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
+However, anyone can contribute [documentation improvements](workflow.md) that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
## Markdown and styles
@@ -462,7 +462,7 @@ If you want to know the in-depth details, here's what's really happening:
The following GitLab features are used among others:
- [Manual actions](../../ci/yaml/README.md#whenmanual)
-- [Multi project pipelines](../../ci/multi_project_pipeline_graphs.md)
+- [Multi project pipelines](../../ci/multi_project_pipelines.md)
- [Review Apps](../../ci/review_apps/index.md)
- [Artifacts](../../ci/yaml/README.md#artifacts)
- [Specific runner](../../ci/runners/README.md#prevent-a-specific-runner-from-being-enabled-for-other-projects)
diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md
index 36a40162184..19fd86f4bf6 100644
--- a/doc/development/integrations/secure_partner_integration.md
+++ b/doc/development/integrations/secure_partner_integration.md
@@ -36,7 +36,7 @@ best place to integrate your own product and its results into GitLab.
- Pipeline jobs serve a variety of purposes. Jobs can do scanning for and have
implications for app security, corporate policy, or compliance. When complete,
the job reports back on its status and creates a
- [job artifact](../../user/project/pipelines/job_artifacts.md) as a result.
+ [job artifact](../../ci/pipelines/job_artifacts.md) as a result.
- The [Merge Request Security Widget](../../user/project/merge_requests/testing_and_reports_in_merge_requests.md#security-reports)
displays the results of the pipeline's security checks and the developer can
review them. The developer can review both a summary and a detailed version
@@ -78,7 +78,7 @@ and complete an integration with the Secure stage.
to successfully display your own product's results with the rest of GitLab.
- See detailed [technical directions](secure.md) for this step.
- Read more about [job report artifacts](../../ci/pipelines/job_artifacts.md#artifactsreports).
- - Read about [job artifacts](../../user/project/pipelines/job_artifacts.md).
+ - Read about [job artifacts](../../ci/pipelines/job_artifacts.md).
- Your report artifact must be in one of our currently supported formats.
For more information, see the [documentation on reports](secure.md#report).
- Documentation for [SAST reports](../../user/application_security/sast/index.md#reports-json-format).
@@ -89,7 +89,7 @@ and complete an integration with the Secure stage.
and add the label `devops::secure`.
- Once the job is completed, the data can be seen:
- In the [Merge Request Security Report](../../user/project/merge_requests/testing_and_reports_in_merge_requests.md#security-reports) ([MR Security Report data flow](https://gitlab.com/snippets/1910005#merge-request-view)).
- - While [browsing a Job Artifact](../../user/project/pipelines/job_artifacts.md).
+ - While [browsing a Job Artifact](../../ci/pipelines/job_artifacts.md).
- In the [Security Dashboard](../../user/application_security/security_dashboard/index.md) ([Dashboard data flow](https://gitlab.com/snippets/1910005#project-and-group-dashboards)).
1. Optional: Provide a way to interact with results as Vulnerabilities:
- Users can interact with the findings from your artifact within their workflow. They can dismiss the findings or accept them and create a backlog issue.
diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/new_fe_guide/development/index.md
index 5dced3dc466..119dbc58012 100644
--- a/doc/development/new_fe_guide/development/index.md
+++ b/doc/development/new_fe_guide/development/index.md
@@ -12,6 +12,6 @@ Learn how to implement an accessible frontend.
Learn how to keep our frontend performant.
-## [Testing](testing.md)
+## [Testing](../../testing_guide/frontend_testing.md)
Learn how to keep our frontend tested.
diff --git a/doc/development/testing_guide/end_to_end/feature_flags.md b/doc/development/testing_guide/end_to_end/feature_flags.md
index 87a9738b313..e571774167d 100644
--- a/doc/development/testing_guide/end_to_end/feature_flags.md
+++ b/doc/development/testing_guide/end_to_end/feature_flags.md
@@ -1,29 +1,70 @@
# Testing with feature flags
-To run a specific test with a feature flag enabled you can use the `QA::Runtime::Feature` class to enable and disable feature flags ([via the API](../../../api/features.md)).
+To run a specific test with a feature flag enabled you can use the `QA::Runtime::Feature` class to
+enable and disable feature flags ([via the API](../../../api/features.md)).
-Note that administrator authorization is required to change feature flags. `QA::Runtime::Feature` will automatically authenticate as an administrator as long as you provide an appropriate access token via `GITLAB_QA_ADMIN_ACCESS_TOKEN` (recommended), or provide `GITLAB_ADMIN_USERNAME` and `GITLAB_ADMIN_PASSWORD`.
+Note that administrator authorization is required to change feature flags. `QA::Runtime::Feature`
+will automatically authenticate as an administrator as long as you provide an appropriate access
+token via `GITLAB_QA_ADMIN_ACCESS_TOKEN` (recommended), or provide `GITLAB_ADMIN_USERNAME`
+and `GITLAB_ADMIN_PASSWORD`.
-Please be sure to include the tag `:requires_admin` so that the test can be skipped in environments where admin access is not available.
+Please be sure to include the tag `:requires_admin` so that the test can be skipped in environments
+where admin access is not available.
+
+CAUTION: **Caution:**
+You are strongly advised to [enable feature flags only for a group, project, user](../../feature_flags/development.md#feature-actors),
+or [feature group](../../feature_flags/development.md#feature-groups). This makes it possible to
+test a feature in a shared environment without affecting other users.
+
+For example, the code below would enable a feature flag named `:feature_flag_name` for the project
+created by the test:
```ruby
RSpec.describe "with feature flag enabled", :requires_admin do
+ let(:project) { Resource::Project.fabricate_via_api! }
+
before do
- Runtime::Feature.enable('feature_flag_name')
+ Runtime::Feature.enable(:feature_flag_name, project: project)
end
it "feature flag test" do
- # Execute a test with a feature flag enabled
+ # Execute the test with the feature flag enabled.
+ # It will only affect the project created in this test.
end
after do
- Runtime::Feature.disable('feature_flag_name')
+ Runtime::Feature.disable(:feature_flag_name, project: project)
end
end
```
+Note that the `enable` and `disable` methods first set the flag and then check that the updated
+value is returned by the API.
+
+Similarly, you can enable a feature for a group, user, or feature group:
+
+```ruby
+group = Resource::Group.fabricate_via_api!
+Runtime::Feature.enable(:feature_flag_name, group: group)
+
+user = Resource::User.fabricate_via_api!
+Runtime::Feature.enable(:feature_flag_name, user: user)
+
+feature_group = "a_feature_group"
+Runtime::Feature.enable(:feature_flag_name, feature_group: feature_group)
+```
+
+If no scope is provided, the feature flag will be set instance-wide:
+
+```ruby
+# This will affect all users!
+Runtime::Feature.enable(:feature_flag_name)
+```
+
## Running a scenario with a feature flag enabled
-It's also possible to run an entire scenario with a feature flag enabled, without having to edit existing tests or write new ones.
+It's also possible to run an entire scenario with a feature flag enabled, without having to edit
+existing tests or write new ones.
-Please see the [QA README](https://gitlab.com/gitlab-org/gitlab/tree/master/qa#running-tests-with-a-feature-flag-enabled) for details.
+Please see the [QA README](https://gitlab.com/gitlab-org/gitlab/tree/master/qa#running-tests-with-a-feature-flag-enabled)
+for details.
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index a7f18e13f74..3850655aaed 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -31,7 +31,7 @@ The following are guides to basic GitLab functionality:
- [Add a file](add-file.md), to add new files to a project's repository.
- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue),
to start collaborating within a project.
-- [Create a merge request](add-merge-request.md), to request changes made in a branch
+- [Create a merge request](../user/project/merge_requests/creating_merge_requests.md), to request changes made in a branch
be merged into a project's repository.
- See how these features come together in the [GitLab Flow introduction video](https://youtu.be/InKNIvky2KE)
and [GitLab Flow page](../topics/gitlab_flow.md).
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
index 0ad5cb53e97..13f20869b51 100644
--- a/doc/gitlab-basics/create-branch.md
+++ b/doc/gitlab-basics/create-branch.md
@@ -9,11 +9,11 @@ type: howto
A branch is an independent line of development in a [project](../user/project/index.md).
-When you create a new branch (in your [terminal](basic-git-commands.md) or with
+When you create a new branch (in your [terminal](start-using-git.md) or with
[the web interface](../user/project/repository/web_editor.md#create-a-new-branch)),
you are creating a snapshot of a certain branch, usually the main `master` branch,
at it's current state. From there, you can start to make your own changes without
affecting the main codebase. The history of your changes will be tracked in your branch.
When your changes are ready, you then merge them into the rest of the codebase with a
-[merge request](add-merge-request.md).
+[merge request](../user/project/merge_requests/creating_merge_requests.md).
diff --git a/doc/install/README.md b/doc/install/README.md
index f1ef368685e..a39daeccb6d 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -68,7 +68,7 @@ GitLab maintains a set of official Docker images based on the Omnibus GitLab pac
If the Omnibus GitLab package is not available in your distribution, you can
install GitLab from source: Useful for unsupported systems like \*BSD. For an
-overview of the directory structure, read the [structure documentation](structure.md).
+overview of the directory structure, read the [structure documentation](installation.md#gitlab-directory-structure).
[**> Install GitLab from source.**](installation.md)
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index b6e3025a0e0..b5e72a9b84a 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -422,7 +422,7 @@ on any cloud service you choose.
## Where to next?
-Check out our other [Technical Articles](../../articles/index.md) or browse the [GitLab Documentation](../../README.md) to learn more about GitLab.
+Check out our other [Technical Articles](../../topics/index.md) or browse the [GitLab Documentation](../../README.md) to learn more about GitLab.
### Useful links
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 022e2095c68..ecce211b65e 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -372,7 +372,7 @@ running scaled to 2.
Upping the GitLab pods is actually like adding new application servers to your
cluster. You can see how that would work if you didn't use GitLab with
-OpenShift by following the [HA documentation](../../administration/high_availability/gitlab.md) for the application servers.
+OpenShift by following the [HA documentation](../../administration/reference_architectures/index.md) for the application servers.
Bare in mind that you may need more resources (CPU, RAM, disk space) when you
scale up. If a pod is in pending state for too long, you can navigate to
diff --git a/doc/install/structure.md b/doc/install/structure.md
index 87ef11c60fe..ca90a3de1b8 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -2,4 +2,4 @@
redirect_to: installation.md#gitlab-directory-structure
---
-This page was moved to [another location](#installation.md#gitlab-directory-structure).
+This page was moved to [another location](installation.md#gitlab-directory-structure).
diff --git a/doc/integration/README.md b/doc/integration/README.md
index c5c21644d1c..c22c383410f 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -26,7 +26,7 @@ GitLab can be configured to authenticate access requests with the following auth
- Enable sign in with [Bitbucket](bitbucket.md) accounts.
- Configure GitLab to sign in using [CAS](cas.md).
- Integrate with [Kerberos](kerberos.md).
-- Enable sign in via [LDAP](ldap.md).
+- Enable sign in via [LDAP](../administration/auth/ldap/index.md).
- Enable [OAuth2 provider](oauth_provider.md) application creation.
- Use [OmniAuth](omniauth.md) to enable sign in via Twitter, GitHub, GitLab.com, Google,
Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure or Authentiq ID.
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
index 9b7aa5829c1..e91b024546e 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -148,7 +148,7 @@ GitLab. No other error messages appear in any logs.
If there was an issue with SSL/TLS, this error message will be generated.
-- The [GitLab Jira integration](jira.md) requires GitLab to connect to Jira. Any
+- The [GitLab Jira integration](../user/project/integrations/jira.md) requires GitLab to connect to Jira. Any
TLS issues that arise from a private certificate authority or self-signed
certificate [are resolved on the GitLab server](https://docs.gitlab.com/omnibus/settings/ssl.html#other-certificate-authorities),
as GitLab is the TLS client.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index dd183ad9eb0..18fa1a86ca9 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -50,7 +50,7 @@ earlier version, you'll need to explicitly enable it.
- `allow_single_sign_on` allows you to specify the providers you want to allow to
automatically create an account. It defaults to `false`. If `false` users must
be created manually or they will not be able to sign in via OmniAuth.
-- `auto_link_ldap_user` can be used if you have [LDAP / ActiveDirectory](ldap.md)
+- `auto_link_ldap_user` can be used if you have [LDAP / ActiveDirectory](../administration/auth/ldap/index.md)
integration enabled. It defaults to `false`. When enabled, users automatically
created through an OmniAuth provider will have their LDAP identity created in GitLab as well.
- `block_auto_created_users` defaults to `true`. If `true` auto created users will
diff --git a/doc/intro/README.md b/doc/intro/README.md
index 4bf9c766fc4..02429f387ee 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -28,7 +28,7 @@ Create issues, labels, milestones, cast your vote, and review issues.
Create merge requests and review code.
- [Fork a project and contribute to it](../user/project/repository/forking_workflow.md)
-- [Create a new merge request](../gitlab-basics/add-merge-request.md)
+- [Create a new merge request](../user/project/merge_requests/creating_merge_requests.md)
- [Automatically close issues from merge requests](../user/project/issues/managing_issues.md#closing-issues-automatically)
- [Automatically merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md)
- [Revert any commit](../user/project/merge_requests/revert_changes.md)
diff --git a/doc/operations/index.md b/doc/operations/index.md
index 7ab34502277..b49487b60e6 100644
--- a/doc/operations/index.md
+++ b/doc/operations/index.md
@@ -35,7 +35,7 @@ using metrics and logs, and promote the critical alerts to incidents.
Are your alerts too noisy? Alerts configured on GitLab metrics can configured
and fine-tuned in GitLab immediately following a fire-fight.
-- [Manage alerts and incidents](../user/incident_management/index.md) in GitLab.
+- [Manage alerts and incidents](incident_management/index.md) in GitLab.
- [Configure alerts for metrics](metrics/alerts.md#set-up-alerts-for-prometheus-metrics) in GitLab.
- Create a [status page](incident_management/status_page.md)
to communicate efficiently to your users during an incident.
diff --git a/doc/operations/metrics/alerts.md b/doc/operations/metrics/alerts.md
index 5b880ab9746..e8f9142a36c 100644
--- a/doc/operations/metrics/alerts.md
+++ b/doc/operations/metrics/alerts.md
@@ -15,7 +15,7 @@ your team when environment performance falls outside of the boundaries you set.
## Managed Prometheus instances
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6590) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2 for [custom metrics](index.md#adding-custom-metrics), and GitLab 11.3 for [library metrics](../../user/project/integrations/prometheus_library/metrics.md).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6590) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2 for [custom metrics](index.md#adding-custom-metrics), and GitLab 11.3 for [library metrics](../../user/project/integrations/prometheus_library/index.md).
For managed Prometheus instances using auto configuration, you can
[configure alerts for metrics](index.md#adding-custom-metrics) directly in the
diff --git a/doc/university/README.md b/doc/university/README.md
index d029c91a19f..63252000dd7 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -65,7 +65,6 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
1. [Making GitLab Great for Everyone - Video](https://www.youtube.com/watch?v=GGC40y4vMx0) - Response to "Dear GitHub" letter
1. [Using Innersourcing to Improve Collaboration](https://about.gitlab.com/blog/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/)
1. [The Software Development Market and GitLab - Video](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6) - [Slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit)
-1. [The GitLab Book Club](bookclub/index.md)
1. [GitLab Resources](https://about.gitlab.com/resources/)
### 1.7 Community and Support
@@ -76,7 +75,6 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
- Getting Technical Support
- Being part of our Great Community and Contributing to GitLab
1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/blog/2016/06/08/getting-started-with-gitlab-development-kit/)
-1. [GitLab Training Workshops](training/end-user/README.md)
1. [GitLab Professional Services](https://about.gitlab.com/services/)
### 1.8 GitLab Training Material
diff --git a/doc/university/training/index.md b/doc/university/training/index.md
index 69f82392027..4c54108b4fa 100644
--- a/doc/university/training/index.md
+++ b/doc/university/training/index.md
@@ -17,7 +17,7 @@ This section contains the following topics:
- [Cherry pick](topics/cherry_picking.md).
- [Code review and collaboration with Merge Requests](topics/merge_requests.md).
- [Configure your environment](topics/env_setup.md).
-- [Explore GitLab](topics/explore_gitlab.md).
+- [Explore GitLab](../../gitlab-basics/README.md).
- [Feature branching](topics/feature_branching.md).
- [Getting started](topics/getting_started.md).
- [GitLab flow](gitlab_flow.md).
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index ba9bccbf3e7..07d0c3fbd68 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -102,7 +102,7 @@ Access the default page for admin area settings by navigating to **Admin Area >
| Option | Description |
| ------ | ----------- |
| [Email](email.md) | Various email settings. |
-| [Help page](../../../customization/help_message.md) | Help page text and support page URL. |
+| [Help page](help_page.md) | Help page text and support page URL. |
| [Pages](../../../administration/pages/index.md#custom-domain-verification) | Size and domain settings for static websites |
| [Real-time features](../../../administration/polling.md) | Change this value to influence how frequently the GitLab UI polls for updates. |
| [Gitaly timeouts](gitaly_timeouts.md) | Configure Gitaly timeouts. |
diff --git a/doc/user/analytics/value_stream_analytics.md b/doc/user/analytics/value_stream_analytics.md
index 14012d4a28d..3944366f190 100644
--- a/doc/user/analytics/value_stream_analytics.md
+++ b/doc/user/analytics/value_stream_analytics.md
@@ -123,7 +123,7 @@ How this works, behind the scenes:
we need for the stages, like issue creation date, merge request merge time,
etc.
-To sum up, anything that doesn't follow [GitLab flow](../../workflow/gitlab_flow.md) will not be tracked and the
+To sum up, anything that doesn't follow [GitLab flow](../../topics/gitlab_flow.md) will not be tracked and the
Value Stream Analytics dashboard will not present any data for:
- Merge requests that do not close an issue.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 32b76cf9280..2c31ef3bf0b 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -395,7 +395,7 @@ milestones.
Get an overview of the vulnerabilities of all the projects in a group and its subgroups.
-[Learn more about the Group Security Dashboard.](security_dashboard/index.md)
+[Learn more about the Group Security Dashboard.](../application_security/security_dashboard/index.md)
## Insights **(ULTIMATE)**
diff --git a/doc/user/index.md b/doc/user/index.md
index 16d72ede00d..dfada4b8f50 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -61,7 +61,7 @@ With GitLab Enterprise Edition, you can also:
- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Search](search/advanced_global_search.md) and [Advanced Search Syntax](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
- [Authenticate users with Kerberos](../integration/kerberos.md).
- [Mirror a repository](project/repository/repository_mirroring.md) from elsewhere on your local server.
-- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipeline_graphs.md).
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipelines.md).
- [Lock files](project/file_lock.md) to prevent conflicts.
- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](project/deploy_boards.md).
- Leverage continuous delivery method with [Canary Deployments](project/canary_deployments.md).
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 1b8c16f401c..1c0c1255ee3 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -14,7 +14,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
If you're unable to use [OAuth2](../../api/oauth2.md), you can use a personal access token to authenticate with the [GitLab API](../../api/README.md#personalproject-access-tokens).
-You can also use personal access tokens with Git to authenticate over HTTP or SSH. Personal access tokens are required when [Two-Factor Authentication (2FA)](../account/two_factor_authentication.md) is enabled. In both cases, you can authenticate with a token in place of your password.
+You can also use personal access tokens with Git to authenticate over HTTP or SSH. Personal access tokens are required when [Two-Factor Authentication (2FA)](account/two_factor_authentication.md) is enabled. In both cases, you can authenticate with a token in place of your password.
Personal access tokens expire on the date you define, at midnight UTC.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index c79f2be1d3f..e00d0187622 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -34,7 +34,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Protected tags](protected_tags.md): Control over who has
permission to create tags, and prevent accidental update or deletion
- [Repository mirroring](repository/repository_mirroring.md)
- - [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
+ - [Signing commits](repository/gpg_signed_commits/index.md): use GPG to sign your commits
- [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
- [Web IDE](web_ide/index.md)
- [CVE ID Requests](../application_security/cve_id_request.md): Request a CVE identifier to track a
@@ -98,7 +98,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Snippets](../snippets.md): store, share and collaborate on code snippets.
- [Value Stream Analytics](cycle_analytics.md): review your development lifecycle.
- [Insights](insights/index.md): configure the Insights that matter for your projects. **(ULTIMATE)**
-- [Security Dashboard](security_dashboard.md): Security Dashboard. **(ULTIMATE)**
+- [Security Dashboard](../application_security/security_dashboard/index.md): Security Dashboard. **(ULTIMATE)**
- [Syntax highlighting](highlighting.md): an alternative to customize
your code blocks, overriding GitLab's default choice of language.
- [Badges](badges.md): badges for the project overview.
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 29efe08e53d..d03241f98a9 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -12,7 +12,7 @@ GitLab has support for automatically detecting and monitoring Kubernetes metrics
## Requirements
-The [Prometheus](../prometheus.md) and [Kubernetes](../kubernetes.md)
+The [Prometheus](../prometheus.md) and [Kubernetes](../../clusters/index.md)
integration services must be enabled.
## Metrics supported
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index 4b4b430b663..597d6737a8f 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -25,7 +25,7 @@ This feature covers only certificates for **custom domains**, not the wildcard c
Before you can enable automatic provisioning of an SSL certificate for your domain, make sure you have:
-- Created a [project](../getting_started_part_two.md) in GitLab
+- Created a [project](../index.md#getting-started) in GitLab
containing your website's source code.
- Acquired a domain (`example.com`) and added a [DNS entry](index.md)
pointing it to your Pages website.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 395d4bf30c5..9902f208468 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -270,7 +270,7 @@ To restore a project marked for deletion:
Forking is a great way to [contribute to a project](../repository/forking_workflow.md)
of which you're not a member.
If you want to use the fork for yourself and don't need to send
-[merge requests](../merge_requests.md) to the upstream project,
+[merge requests](../merge_requests/index.md) to the upstream project,
you can safely remove the fork relationship.
CAUTION: **Caution:**
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index c2266f0bad6..6a45baa60ec 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Artifacts < Files
+ class Artifacts < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb
index 5e795a449de..9c3b7165de7 100644
--- a/lib/backup/builds.rb
+++ b/lib/backup/builds.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Builds < Files
+ class Builds < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb
index 0dfe56e214f..514d52d7f65 100644
--- a/lib/backup/lfs.rb
+++ b/lib/backup/lfs.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Lfs < Files
+ class Lfs < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb
index d7aab33d7cb..ae293073ba2 100644
--- a/lib/backup/pages.rb
+++ b/lib/backup/pages.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Pages < Files
+ class Pages < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb
index d16ed2facf1..9645a07dfb8 100644
--- a/lib/backup/registry.rb
+++ b/lib/backup/registry.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Registry < Files
+ class Registry < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index b6a62bc3f29..9665624f71b 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require 'backup/files'
-
module Backup
- class Uploads < Files
+ class Uploads < Backup::Files
attr_reader :progress
def initialize(progress)
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index 579c2293c51..a48bc216ac2 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -1,98 +1,125 @@
# frozen_string_literal: true
+require 'active_support/core_ext/object/blank'
+
module QA
module Runtime
- module Feature
- extend self
- extend Support::Api
+ class Feature
+ class << self
+ # Documentation: https://docs.gitlab.com/ee/api/features.html
- SetFeatureError = Class.new(RuntimeError)
- AuthorizationError = Class.new(RuntimeError)
+ include Support::Api
- def enable(key)
- QA::Runtime::Logger.info("Enabling feature: #{key}")
- set_feature(key, true)
- end
+ SetFeatureError = Class.new(RuntimeError)
+ AuthorizationError = Class.new(RuntimeError)
+ UnknownScopeError = Class.new(RuntimeError)
- def disable(key)
- QA::Runtime::Logger.info("Disabling feature: #{key}")
- set_feature(key, false)
- end
-
- def remove(key)
- request = Runtime::API::Request.new(api_client, "/features/#{key}")
- response = delete(request.url)
- unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT
- raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`."
+ def remove(key)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = delete(request.url)
+ unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT
+ raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`."
+ end
end
- end
-
- def enable_and_verify(key)
- set_and_verify(key, enable: true)
- end
- def disable_and_verify(key)
- set_and_verify(key, enable: false)
- end
+ def enable(key, **scopes)
+ set_and_verify(key, enable: true, **scopes)
+ end
- def enabled?(key)
- feature = JSON.parse(get_features).find { |flag| flag["name"] == key }
- feature && feature["state"] == "on"
- end
+ def disable(key, **scopes)
+ set_and_verify(key, enable: false, **scopes)
+ end
- def get_features
- request = Runtime::API::Request.new(api_client, "/features")
- response = get(request.url)
- response.body
- end
+ def enabled?(key, **scopes)
+ feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
+ feature && feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], scopes)
+ end
- private
+ private
- def api_client
- @api_client ||= begin
- if Runtime::Env.admin_personal_access_token
- Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token)
- else
- user = Resource::User.fabricate_via_api! do |user|
- user.username = Runtime::User.admin_username
- user.password = Runtime::User.admin_password
- end
+ def api_client
+ @api_client ||= Runtime::API::Client.as_admin
+ rescue Runtime::API::Client::AuthorizationError => e
+ raise AuthorizationError, "Administrator access is required to enable/disable feature flags. #{e.message}"
+ end
- unless user.admin?
- raise AuthorizationError, "Administrator access is required to enable/disable feature flags. User '#{user.username}' is not an administrator."
+ def enabled_scope?(gates, scopes)
+ scopes.each do |key, value|
+ case key
+ when :project, :group, :user
+ actors = gates.filter { |i| i['key'] == 'actors' }.first['value']
+ break actors.include?("#{key.to_s.capitalize}:#{value.id}")
+ when :feature_group
+ groups = gates.filter { |i| i['key'] == 'groups' }.first['value']
+ break groups.include?(value)
+ else
+ raise UnknownScopeError, "Unknown scope: #{key}"
end
-
- Runtime::API::Client.new(:gitlab, user: user)
end
end
- end
- # Change a feature flag and verify that the change was successful
- # Arguments:
- # key: The feature flag to set (as a string)
- # enable: `true` to enable the flag, `false` to disable it
- def set_and_verify(key, enable:)
- Support::Retrier.retry_on_exception(sleep_interval: 2) do
- enable ? enable(key) : disable(key)
+ def get_features
+ request = Runtime::API::Request.new(api_client, '/features')
+ response = get(request.url)
+ response.body
+ end
- is_enabled = nil
+ # Change a feature flag and verify that the change was successful
+ # Arguments:
+ # key: The feature flag to set (as a string)
+ # enable: `true` to enable the flag, `false` to disable it
+ # scopes: Any scope (user, project, group) to restrict the change to
+ def set_and_verify(key, enable:, **scopes)
+ msg = "#{enable ? 'En' : 'Dis'}abling feature: #{key}"
+ msg += " for scope \"#{scopes_to_s(scopes)}\"" if scopes.present?
+ QA::Runtime::Logger.info(msg)
- QA::Support::Waiter.wait_until(sleep_interval: 1) do
- is_enabled = enabled?(key)
- is_enabled == enable
- end
+ Support::Retrier.retry_on_exception(sleep_interval: 2) do
+ set_feature(key, enable, scopes)
+
+ is_enabled = nil
+
+ QA::Support::Waiter.wait_until(sleep_interval: 1) do
+ is_enabled = enabled?(key, scopes)
+ is_enabled == enable || !enable && scopes.present?
+ end
+
+ if is_enabled == enable
+ QA::Runtime::Logger.info("Successfully #{enable ? 'en' : 'dis'}abled and verified feature flag: #{key}")
+ else
+ raise SetFeatureError, "#{key} was not #{enable ? 'en' : 'dis'}abled!" if enable
- raise SetFeatureError, "#{key} was not #{enable ? 'enabled' : 'disabled'}!" unless is_enabled == enable
+ QA::Runtime::Logger.warn("Feature flag scope was removed but the flag is still enabled globally.")
+ end
+ end
+ end
- QA::Runtime::Logger.info("Successfully #{enable ? 'enabled' : 'disabled'} and verified feature flag: #{key}")
+ def set_feature(key, value, **scopes)
+ scopes[:project] = scopes[:project].full_path if scopes.key?(:project)
+ scopes[:group] = scopes[:group].full_path if scopes.key?(:group)
+ scopes[:user] = scopes[:user].username if scopes.key?(:user)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = post(request.url, scopes.merge({ value: value }))
+ unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
+ raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ end
end
- end
- def set_feature(key, value)
- request = Runtime::API::Request.new(api_client, "/features/#{key}")
- response = post(request.url, { value: value })
- unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
- raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ def scopes_to_s(**scopes)
+ key = scopes.each_key.first
+ s = "#{key}: "
+ case key
+ when :project, :group
+ s += scopes[key].full_path
+ when :user
+ s += scopes[key].username
+ when :feature_group
+ s += scopes[key]
+ else
+ raise UnknownScopeError, "Unknown scope: #{key}"
+ end
+
+ s
end
end
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
index 29f131ac322..c3cb503ed3f 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
@@ -17,12 +17,12 @@ module QA
end
before do
- Runtime::Feature.enable_and_verify('gitaly_distributed_reads')
+ Runtime::Feature.enable(:gitaly_distributed_reads)
praefect_manager.wait_for_replication(project.id)
end
after do
- Runtime::Feature.disable_and_verify('gitaly_distributed_reads')
+ Runtime::Feature.disable(:gitaly_distributed_reads)
end
it 'reads from each node', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/979' do
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
index 5672060a953..83945a09587 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
@@ -4,7 +4,7 @@ module QA
RSpec.describe 'Create' do
describe 'Push mirror a repository over HTTP' do
it 'configures and syncs LFS objects for a (push) mirrored repository', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do
- Runtime::Feature.enable_and_verify('push_mirror_syncs_lfs')
+ Runtime::Feature.enable(:push_mirror_syncs_lfs)
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
index c9148ae78e8..376ce51273e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
@@ -4,11 +4,11 @@ module QA
RSpec.describe 'Create', :requires_admin do
describe 'Multiple file snippet' do
before do
- Runtime::Feature.enable_and_verify('snippet_multiple_files')
+ Runtime::Feature.enable('snippet_multiple_files')
end
after do
- Runtime::Feature.disable_and_verify('snippet_multiple_files')
+ Runtime::Feature.disable('snippet_multiple_files')
end
it 'creates a personal snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/842' do
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
index d43033e3770..39c20dd3070 100644
--- a/qa/spec/runtime/feature_spec.rb
+++ b/qa/spec/runtime/feature_spec.rb
@@ -4,84 +4,212 @@ RSpec.describe QA::Runtime::Feature do
let(:api_client) { double('QA::Runtime::API::Client') }
let(:request) { Struct.new(:url).new('http://api') }
let(:response_post) { Struct.new(:code).new(201) }
- let(:response_get) { Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "on" }]') }
before do
allow(described_class).to receive(:api_client).and_return(api_client)
end
- describe '.enable' do
- it 'enables a feature flag' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features/a-flag")
- .and_return(request)
- expect(described_class)
- .to receive(:post)
- .with(request.url, { value: true })
- .and_return(response_post)
-
- subject.enable('a-flag')
- end
+ where(:feature_flag) do
+ ['a_flag', :a_flag]
end
- describe '.enable_and_verify' do
- it 'enables a feature flag' do
- allow(described_class).to receive(:get).and_return(response_get)
+ with_them do
+ shared_examples 'enables a feature flag' do
+ it 'enables a feature flag for a scope' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features/a-flag").and_return(request)
- expect(described_class).to receive(:post)
- .with(request.url, { value: true }).and_return(response_post)
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: true, scope => actor_name }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::Logger).to receive(:info).with("Enabling feature: a_flag for scope \"#{scope}: #{actor_name}\"")
+ expect(QA::Runtime::Logger).to receive(:info).with("Successfully enabled and verified feature flag: a_flag")
- subject.enable_and_verify('a-flag')
+ described_class.enable(feature_flag, scope => actor)
+ end
end
- end
- describe '.disable' do
- it 'disables a feature flag' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features/a-flag")
- .and_return(request)
- expect(described_class)
- .to receive(:post)
- .with(request.url, { value: false })
- .and_return(response_post)
-
- subject.disable('a-flag')
+ shared_examples 'disables a feature flag' do
+ it 'disables a feature flag for a scope' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]'))
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: false, scope => actor_name }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::Logger).to receive(:info).with("Disabling feature: a_flag for scope \"#{scope}: #{actor_name}\"")
+ expect(QA::Runtime::Logger).to receive(:info).with("Successfully disabled and verified feature flag: a_flag")
+
+ described_class.disable(feature_flag, scope => actor )
+ end
end
- end
- describe '.disable_and_verify' do
- it 'disables a feature flag' do
- allow(described_class).to receive(:get)
- .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "off" }]'))
+ shared_examples 'checks a feature flag' do
+ context 'when the flag is enabled for a scope' do
+ it 'returns the feature flag state' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }])))
+
+ expect(described_class.enabled?(feature_flag, scope => actor)).to be_truthy
+ end
+ end
+ end
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features/a-flag").and_return(request)
- expect(described_class).to receive(:post)
- .with(request.url, { value: false }).and_return(response_post)
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features").and_return(request)
+ describe '.enable' do
+ it 'enables a feature flag' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- subject.disable_and_verify('a-flag')
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: true }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ described_class.enable(feature_flag)
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:username).new(actor_name) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ end
+ end
end
- end
- describe '.enabled?' do
- it 'returns a feature flag state' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features")
- .and_return(request)
- expect(described_class)
- .to receive(:get)
- .and_return(response_get)
-
- expect(subject.enabled?('a-flag')).to be_truthy
+ describe '.disable' do
+ it 'disables a feature flag' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]'))
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: false }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ described_class.disable(feature_flag)
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:username).new(actor_name) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ end
+ end
+ end
+
+ describe '.enabled?' do
+ it 'returns a feature flag state' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
+
+ expect(described_class.enabled?(feature_flag)).to be_truthy
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 270) }
+ let(:gates) { %q([{"key": "actors", "value": ["Project:270"]}]) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 33) }
+ let(:gates) { %q([{"key": "actors", "value": ["Group:33"]}]) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 13) }
+ let(:gates) { %q([{"key": "actors", "value": ["User:13"]}]) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ let(:gates) { %q([{"key": "groups", "value": ["foo"]}]) }
+ end
+ end
end
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index a166e913ea4..42a12a98d63 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -320,7 +320,7 @@ RSpec.describe Service do
end
it 'sets service to inactive' do
- service = described_class.build_from_integration(project.id, integration)
+ service = described_class.build_from_integration(integration, project_id: project.id)
expect(service).to be_valid
expect(service.active).to be false
@@ -331,7 +331,7 @@ RSpec.describe Service do
let(:integration) { create(:jira_service, :instance) }
it 'sets inherit_from_id from integration' do
- service = described_class.build_from_integration(project.id, integration)
+ service = described_class.build_from_integration(integration, project_id: project.id)
expect(service.inherit_from_id).to eq(integration.id)
end
@@ -341,7 +341,7 @@ RSpec.describe Service do
let(:integration) { create(:jira_service, group: group, project: nil) }
it 'sets inherit_from_id from integration' do
- service = described_class.build_from_integration(project.id, integration)
+ service = described_class.build_from_integration(integration, project_id: project.id)
expect(service.inherit_from_id).to eq(integration.id)
end
@@ -360,8 +360,8 @@ RSpec.describe Service do
end
shared_examples 'service creation from an integration' do
- it 'creates a correct service' do
- service = described_class.build_from_integration(project.id, integration)
+ it 'creates a correct service for a project integration' do
+ service = described_class.build_from_integration(integration, project_id: project.id)
expect(service).to be_active
expect(service.url).to eq(url)
@@ -373,6 +373,20 @@ RSpec.describe Service do
expect(service.project).to eq(project)
expect(service.group).to eq(nil)
end
+
+ it 'creates a correct service for a group integration' do
+ service = described_class.build_from_integration(integration, group_id: group.id)
+
+ expect(service).to be_active
+ expect(service.url).to eq(url)
+ expect(service.api_url).to eq(api_url)
+ expect(service.username).to eq(username)
+ expect(service.password).to eq(password)
+ expect(service.template).to eq(false)
+ expect(service.instance).to eq(false)
+ expect(service.project).to eq(nil)
+ expect(service.group).to eq(group)
+ end
end
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
diff --git a/spec/services/admin/propagate_service_template_spec.rb b/spec/services/admin/propagate_service_template_spec.rb
index 15654653095..814a9db55de 100644
--- a/spec/services/admin/propagate_service_template_spec.rb
+++ b/spec/services/admin/propagate_service_template_spec.rb
@@ -61,8 +61,8 @@ RSpec.describe Admin::PropagateServiceTemplate do
}
)
- Service.build_from_integration(project.id, service_template).save!
- Service.build_from_integration(project.id, other_service).save!
+ Service.build_from_integration(service_template, project_id: project.id).save!
+ Service.build_from_integration(other_service, project_id: project.id).save!
expect { described_class.propagate(service_template) }
.not_to change { Service.count }
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index fc877f45a39..6995caa9e6e 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -138,4 +138,51 @@ RSpec.describe Groups::CreateService, '#execute' do
expect(group.namespace_settings).to be_persisted
end
end
+
+ describe 'create service for the group' do
+ let(:service) { described_class.new(user, group_params) }
+ let(:created_group) { service.execute }
+
+ context 'with an active instance-level integration' do
+ let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') }
+
+ it 'creates a service from the instance-level integration' do
+ expect(created_group.services.count).to eq(1)
+ expect(created_group.services.first.api_url).to eq(instance_integration.api_url)
+ expect(created_group.services.first.inherit_from_id).to eq(instance_integration.id)
+ end
+
+ context 'with an active group-level integration' do
+ let(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
+ let!(:group_integration) { create(:prometheus_service, group: group, project: nil, api_url: 'https://prometheus.group.com/') }
+ let(:group) do
+ create(:group).tap do |group|
+ group.add_owner(user)
+ end
+ end
+
+ it 'creates a service from the group-level integration' do
+ expect(created_group.services.count).to eq(1)
+ expect(created_group.services.first.api_url).to eq(group_integration.api_url)
+ expect(created_group.services.first.inherit_from_id).to eq(group_integration.id)
+ end
+
+ context 'with an active subgroup' do
+ let(:service) { described_class.new(user, group_params.merge(parent_id: subgroup.id)) }
+ let!(:subgroup_integration) { create(:prometheus_service, group: subgroup, project: nil, api_url: 'https://prometheus.subgroup.com/') }
+ let(:subgroup) do
+ create(:group, parent: group).tap do |subgroup|
+ subgroup.add_owner(user)
+ end
+ end
+
+ it 'creates a service from the subgroup-level integration' do
+ expect(created_group.services.count).to eq(1)
+ expect(created_group.services.first.api_url).to eq(subgroup_integration.api_url)
+ expect(created_group.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d027083d846..fde90dc8c80 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -541,7 +541,7 @@ RSpec.describe Projects::CreateService, '#execute' do
}
end
- it 'creates a service from the group-level integration' do
+ it 'creates a service from the subgroup-level integration' do
expect(project.services.count).to eq(1)
expect(project.services.first.api_url).to eq(subgroup_integration.api_url)
expect(project.services.first.inherit_from_id).to eq(subgroup_integration.id)