diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e0ae85b18..a35497a0582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,6 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. -## 13.0.5 (2020-06-04) - -### Fixed (4 changes) - -- Fix NoMethodError by using the correct method to report exceptions to Sentry. !33260 -- Fix bug in snippets updating only file_name or content. !33375 -- Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace. !33497 -- Fix linking alerts to created issues for the Generic alerts intergration. !33647 - -### Other (1 change) - -- Update GitLab Workhorse to v8.31.2. !33818 - - ## 13.0.4 (2020-06-03) ### Security (1 change) diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 966e6d42b80..6ae4167d5ba 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -109,9 +109,10 @@ export function mergeUrlParams(params, url) { * * @param {string[]} params - the query param names to remove * @param {string} [url=windowLocation().href] - url from which the query param will be removed + * @param {boolean} skipEncoding - set to true when the url does not require encoding * @returns {string} A copy of the original url but without the query param */ -export function removeParams(params, url = window.location.href) { +export function removeParams(params, url = window.location.href, skipEncoding = false) { const [rootAndQuery, fragment] = url.split('#'); const [root, query] = rootAndQuery.split('?'); @@ -119,12 +120,13 @@ export function removeParams(params, url = window.location.href) { return url; } - const encodedParams = params.map(param => encodeURIComponent(param)); + const removableParams = skipEncoding ? params : params.map(param => encodeURIComponent(param)); + const updatedQuery = query .split('&') .filter(paramPair => { const [foundParam] = paramPair.split('='); - return encodedParams.indexOf(foundParam) < 0; + return removableParams.indexOf(foundParam) < 0; }) .join('&'); diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index 87e7c648a2b..0eb277a24fe 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -140,7 +140,7 @@ module AlertManagement end def register_new_event! - increment!(:events, 1) + increment!(:events) end private diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 1144628a64a..58adfd5f70b 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -28,6 +28,7 @@ class Milestone < ApplicationRecord scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) } scope :reorder_by_due_date_asc, -> { reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) } + scope :with_api_entity_associations, -> { preload(project: [:project_feature, :route, namespace: :route]) } validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 54bcec32095..4c497cc304c 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -205,7 +205,7 @@ class Wiki end def wiki_base_path - Gitlab.config.gitlab.relative_url_root + web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '') + web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '') end private diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb index af28f1354b3..157cfc995f7 100644 --- a/app/services/alert_management/process_prometheus_alert_service.rb +++ b/app/services/alert_management/process_prometheus_alert_service.rb @@ -29,6 +29,7 @@ module AlertManagement def process_firing_alert_management_alert if am_alert.present? + am_alert.register_new_event! reset_alert_management_alert_status else create_alert_management_alert diff --git a/changelogs/unreleased/212882-add-cpu-mem-charts.yml b/changelogs/unreleased/212882-add-cpu-mem-charts.yml new file mode 100644 index 00000000000..52e645cead7 --- /dev/null +++ b/changelogs/unreleased/212882-add-cpu-mem-charts.yml @@ -0,0 +1,5 @@ +--- +title: Add CPU, memory usage charts to self monitoring default dashboard +merge_request: 33532 +author: +type: changed diff --git a/changelogs/unreleased/218823-wiki-base-path-relative-url-root.yml b/changelogs/unreleased/218823-wiki-base-path-relative-url-root.yml new file mode 100644 index 00000000000..f48f7d11cca --- /dev/null +++ b/changelogs/unreleased/218823-wiki-base-path-relative-url-root.yml @@ -0,0 +1,5 @@ +--- +title: Fix relative URL root in wiki_base_path +merge_request: 33841 +author: +type: fixed diff --git a/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml b/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml new file mode 100644 index 00000000000..4a4e268171e --- /dev/null +++ b/changelogs/unreleased/219582-fix-ambiguous_string_concat_on_cleanup_projects_with_missing_names.yml @@ -0,0 +1,5 @@ +--- +title: Fix ambiguous string concatenation on CleanupProjectsWithMissingNamespace +merge_request: 33497 +author: +type: fixed diff --git a/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml b/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml new file mode 100644 index 00000000000..91e3117bffa --- /dev/null +++ b/changelogs/unreleased/ci-rust-cargo-test-workspace-option.yml @@ -0,0 +1,5 @@ +--- +title: "Rust CI template: Replace --all with --workspace on cargo test." +merge_request: 33517 +author: Markus Becker +type: fixed diff --git a/changelogs/unreleased/fix-invalid-error-tracking-method.yml b/changelogs/unreleased/fix-invalid-error-tracking-method.yml new file mode 100644 index 00000000000..90a49dcd8bd --- /dev/null +++ b/changelogs/unreleased/fix-invalid-error-tracking-method.yml @@ -0,0 +1,5 @@ +--- +title: Fix NoMethodError by using the correct method to report exceptions to Sentry +merge_request: 33260 +author: +type: fixed diff --git a/changelogs/unreleased/fix-pagination-link.yml b/changelogs/unreleased/fix-pagination-link.yml new file mode 100644 index 00000000000..d2c1fc1eb94 --- /dev/null +++ b/changelogs/unreleased/fix-pagination-link.yml @@ -0,0 +1,5 @@ +--- +title: Fix pagination link header +merge_request: 33714 +author: Max Wittig +type: fixed diff --git a/changelogs/unreleased/fj-fix-single-param-update.yml b/changelogs/unreleased/fj-fix-single-param-update.yml new file mode 100644 index 00000000000..8d5f9dd80d4 --- /dev/null +++ b/changelogs/unreleased/fj-fix-single-param-update.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug in snippets updating only file_name or content +merge_request: 33375 +author: +type: fixed diff --git a/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml b/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml new file mode 100644 index 00000000000..d040ee3fccd --- /dev/null +++ b/changelogs/unreleased/fj-fix-snippet-import-when-fails.yml @@ -0,0 +1,5 @@ +--- +title: Remove non migrated snippets from failed imports +merge_request: 33621 +author: +type: fixed diff --git a/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml b/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml new file mode 100644 index 00000000000..29ccc347d09 --- /dev/null +++ b/changelogs/unreleased/increase-events-count-for-prometheus-alerts.yml @@ -0,0 +1,5 @@ +--- +title: Increase events count for Prometheus alerts +merge_request: 33706 +author: +type: added diff --git a/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml b/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml new file mode 100644 index 00000000000..a4837dd7df8 --- /dev/null +++ b/changelogs/unreleased/pl-alert-management-fix-multiple-issue-creation.yml @@ -0,0 +1,5 @@ +--- +title: Fix linking alerts to created issues for the Generic alerts intergration +merge_request: 33647 +author: +type: fixed diff --git a/config/prometheus/self_monitoring_default.yml b/config/prometheus/self_monitoring_default.yml index dc2361fb3bc..53b47274ecd 100644 --- a/config/prometheus/self_monitoring_default.yml +++ b/config/prometheus/self_monitoring_default.yml @@ -1,6 +1,27 @@ dashboard: 'Default dashboard' priority: 1 panel_groups: + +- group: 'Resource usage' + panels: + - title: "Memory usage" + type: "line-chart" + y_label: "% memory used" + metrics: + - id: node_memory_usage_percentage + query_range: '(1 - (node_memory_MemAvailable_bytes or node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes + node_memory_Slab_bytes) / node_memory_MemTotal_bytes) * 100' + unit: "%" + label: instance + + - title: "CPU usage" + type: "line-chart" + y_label: "% CPU used" + metrics: + - id: node_cpu_usage_percentage + query_range: '(avg without (mode,cpu) (1 - irate(node_cpu_seconds_total{mode="idle"}[5m]))) * 100' + unit: "%" + label: instance + - group: Web Service panels: - title: Web Service - Error Ratio diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md index 40110399178..d05fb803c5c 100644 --- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md +++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md @@ -21,10 +21,12 @@ All administrators at the time of creation of the project and group will be adde as maintainers of the group and project, and as an admin, you'll be able to add new members to the group in order to give them maintainer access to the project. -This project is used to self monitor your GitLab instance. Metrics are not yet -fully integrated, and the dashboard does not aggregate any data on Omnibus installations. GitLab plans -to provide integrated self-monitoring metrics in a future release. You can -currently use the project to configure your own [custom metrics](../../../user/project/integrations/prometheus.md#adding-custom-metrics) using +This project is used to self monitor your GitLab instance. The metrics dashboard +of the project shows some basic resource usage charts, such as CPU and memory usage +of each server in [Omnibus GitLab](https://docs.gitlab.com/omnibus/) installations. + +You can also use the project to configure your own +[custom metrics](../../../user/project/integrations/prometheus.md#adding-custom-metrics) using metrics exposed by the [GitLab exporter](../prometheus/gitlab_metrics.md#metrics-available). ## Creating the self monitoring project diff --git a/doc/api/README.md b/doc/api/README.md index 6afed960583..6cbb99a76cb 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -417,10 +417,14 @@ The response header includes a link to the next page. For example: HTTP/1.1 200 OK ... Links: ; rel="next" +Link: ; rel="next" Status: 200 OK ... ``` +CAUTION: **Deprecation:** +The `Links` Header will be removed in GitLab 14.0 to be aligned with the [W3C specification](https://www.w3.org/wiki/LinkHeader) + The link to the next page contains an additional filter `id_after=42` which excludes records we have retrieved already. Note the type of filter depends on the `order_by` option used and we may have more than one additional filter. diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index 4531370bc87..12f785a3e3d 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -366,7 +366,9 @@ Example response: "revision": "33d33a096a", "package_files_count": 10, "package_files_checksummed_count": 10, - "package_files_checksum_failed_count": 0 + "package_files_checksum_failed_count": 0, + "package_files_synced_count": 10, + "package_files_failed_count": 5 }, { "geo_node_id": 2, @@ -437,7 +439,9 @@ Example response: "revision": "33d33a096a", "package_files_count": 10, "package_files_checksummed_count": 10, - "package_files_checksum_failed_count": 0 + "package_files_checksum_failed_count": 0, + "package_files_synced_count": 10, + "package_files_failed_count": 5 } ] ``` diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 9d719639b82..dc9ab8674d2 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -12725,7 +12725,7 @@ type Vulnerability { """ Filter issue links by link type """ - linkType: [VulnerabilityIssueLinkType!] + linkType: VulnerabilityIssueLinkType ): VulnerabilityIssueLinkConnection! """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index a349d89296c..73be5c414c6 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -37400,17 +37400,9 @@ "name": "linkType", "description": "Filter issue links by link type", "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "VulnerabilityIssueLinkType", - "ofType": null - } - } + "kind": "ENUM", + "name": "VulnerabilityIssueLinkType", + "ofType": null }, "defaultValue": null }, diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md index 920fd14f95e..7a4ca989fb6 100644 --- a/doc/ci/merge_request_pipelines/index.md +++ b/doc/ci/merge_request_pipelines/index.md @@ -146,7 +146,7 @@ test: Instead, you can use the [`$CI_COMMIT_REF_NAME` predefined environment -variable](../variables/predefined_variables.md#variables-reference) in +variable](../variables/predefined_variables.md) in combination with [`only:variables`](../yaml/README.md#onlyvariablesexceptvariables) to accomplish this behavior: diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 50bd7f23dee..c6ac87ef888 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -23,7 +23,7 @@ The following methods of authentication are supported: - [Trigger token](#trigger-token) - [CI job token](#ci-job-token) -If using the `$CI_PIPELINE_SOURCE` [predefined environment variable](../variables/predefined_variables.md#variables-reference) +If using the `$CI_PIPELINE_SOURCE` [predefined environment variable](../variables/predefined_variables.md) to limit which jobs run in a pipeline, the value could be either `pipeline` or `trigger`, depending on which trigger method is used. diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index c26a9b3eae2..715aa3a2b72 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -10,8 +10,6 @@ type: reference For an introduction on this subject, read through the [getting started with environment variables](README.md) document. -## Overview - Some of the predefined environment variables are available only if a minimum version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the table below to find the version of Runner required. @@ -22,7 +20,8 @@ Starting with GitLab 9.0, we have deprecated some variables. Read the strongly advised to use the new variables as we will remove the old ones in future GitLab releases.** -## Variables reference +You can add a command to your `.gitlab-ci.yml` file to +[output the values of all variables available for a job](README.md#list-all-environment-variables). | Variable | GitLab | Runner | Description | |-----------------------------------------------|--------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 7f92a8a42f7..413e701013b 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1428,7 +1428,7 @@ This means the `only:changes` policy is useful for pipelines where: - `$CI_PIPELINE_SOURCE == 'external_pull_request_event'` If there is no Git push event, such as for pipelines with -[sources other than the three above](../variables/predefined_variables.md#variables-reference), +[sources other than the three above](../variables/predefined_variables.md), `changes` can't determine if a given file is new or old, and will always return true. diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md index 6630d230089..bbfd3e13ec3 100644 --- a/doc/development/integrations/secure.md +++ b/doc/development/integrations/secure.md @@ -91,7 +91,7 @@ disable running the custom scanner. GitLab also defines a `CI_PROJECT_REPOSITORY_LANGUAGES` variable, which provides the list of languages in the repository. Depending on this value, your scanner may or may not do something different. Language detection currently relies on the [`linguist`](https://github.com/github/linguist) Ruby gem. -See [GitLab CI/CD predefined variables](../../ci/variables/predefined_variables.md#variables-reference). +See [GitLab CI/CD predefined variables](../../ci/variables/predefined_variables.md). #### Policy checking example diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md index 1e285acb4f1..8ac3d266b95 100644 --- a/doc/user/compliance/license_compliance/index.md +++ b/doc/user/compliance/license_compliance/index.md @@ -385,6 +385,79 @@ You can supply a custom root certificate to complete TLS verification by using t specifying a `ca` setting in a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification) file. +### Configuring Conan projects + +You can configure [Conan](https://conan.io/) projects by adding a `.conan` directory to your +project root. The project root serves as the [`CONAN_USER_HOME`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-user-home). + +Consult the [Conan](https://docs.conan.io/en/latest/reference/config_files/conan.conf.html#conan-conf) +documentation for a list of settings that you can apply. + +The `license_scanning` job runs in a [Debian 10](https://www.debian.org/releases/buster/) Docker +image. The supplied image ships with some build tools such as [CMake](https://cmake.org/) and [GCC](https://gcc.gnu.org/). +However, not all project types are supported by default. To install additional tools needed to +compile dependencies, use a [`before_script`](../../../ci/yaml/README.md#before_script-and-after_script) +to install the necessary build tools using the [`apt`](https://wiki.debian.org/PackageManagementTools) +package manager. For a comprehensive list, consult [the Conan documentation](https://docs.conan.io/en/latest/introduction.html#all-platforms-all-build-systems-and-compilers). + +The default [Conan](https://conan.io/) configuration sets [`CONAN_LOGIN_USERNAME`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-login-username-conan-login-username-remote-name) +to `ci_user`, and binds [`CONAN_PASSWORD`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-password-conan-password-remote-name) +to the [`CI_JOB_TOKEN`](../../../ci/variables/predefined_variables.md) +for the running job. This allows Conan projects to fetch packages from a [GitLab Conan Repository](../../packages/conan_repository/#fetching-conan-package-information-from-the-gitlab-package-registry) +if a GitLab remote is specified in the `.conan/remotes.json` file. + +To override the default credentials specify a [`CONAN_LOGIN_USERNAME_{REMOTE_NAME}`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-login-username-conan-login-username-remote-name) +matching the name of the remote specified in the `.conan/remotes.json` file. + +NOTE: **Note:** +[MSBuild](https://github.com/mono/msbuild#microsoftbuild-msbuild) projects aren't supported. The +`license_scanning` image ships with [Mono](https://www.mono-project.com/) and [MSBuild](https://github.com/mono/msbuild#microsoftbuild-msbuild). +Additional setup may be required to build packages for this project configuration. + +#### Using private Conan registries + +By default, [Conan](https://conan.io/) uses the `conan-center` remote. For example: + +```json +{ + "remotes": [ + { + "name": "conan-center", + "url": "https://conan.bintray.com", + "verify_ssl": true + } + ] +} +``` + +To fetch dependencies from an alternate remote, specify that remote in a `.conan/remotes.json`. For +example: + +```json +{ + "remotes": [ + { + "name": "gitlab", + "url": "https://gitlab.com/api/v4/packages/conan", + "verify_ssl": true + } + ] +} +``` + +If credentials are required to authenticate then you can configure a [protected variable](../../../ci/variables/README.md#protect-a-custom-variable) +following the naming convention described in the [`CONAN_LOGIN_USERNAME` documentation](https://docs.conan.io/en/latest/reference/env_vars.html#conan-login-username-conan-login-username-remote-name). + +#### Custom root certificates for Conan + +You can provide custom certificates by adding a `.conan/cacert.pem` file to the project root and +setting [`CA_CERT_PATH`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-cacert-path) +to `.conan/cacert.pem`. + +If you specify the `ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables), this +variable's X.509 certificates are installed in the Docker image's default trust store and Conan is +configured to use this as the default `CA_CERT_PATH`. + ### Migration from `license_management` to `license_scanning` In GitLab 12.8 a new name for `license_management` job was introduced. This change was made to improve clarity around the purpose of the scan, which is to scan and collect the types of licenses present in a projects dependencies. @@ -487,9 +560,13 @@ license_scanning: The License Compliance job should now use local copies of the License Compliance analyzers to scan your code and generate security reports, without requiring internet access. -Additional configuration may be needed for connecting to [private Maven repositories](#using-private-maven-repos), +Additional configuration may be needed for connecting to [private Bower registries](#using-private-bower-registries), -[private NPM registries](#using-private-npm-registries), [private Yarn registries](#using-private-yarn-registries), and [private Python repositories](#using-private-python-repos). +[private Conan registries](#using-private-bower-registries), +[private Maven repositories](#using-private-maven-repos), +[private NPM registries](#using-private-npm-registries), +[private Python repositories](#using-private-python-repos), +and [private Yarn registries](#using-private-yarn-registries). Exact name matches are required for [project policies](#project-policies-for-license-compliance) when running in an offline environment ([see related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212388)). diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 7a3e882ceb7..10c63f0dfa1 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -131,6 +131,7 @@ The following table depicts the various user permission levels in a project. | Enable/disable tag protections | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ | | Edit project badges | | | | ✓ | ✓ | +| Share (invite) projects with groups | | | | ✓ (*8*) | ✓ (*8*)| | Add deploy keys to project | | | | ✓ | ✓ | | Configure project hooks | | | | ✓ | ✓ | | Manage Runners | | | | ✓ | ✓ | @@ -175,6 +176,7 @@ The following table depicts the various user permission levels in a project. 1. If the [branch is protected](./project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings), this depends on the access Developers and Maintainers are given. 1. Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see repository information like tags and commits. 1. Actions are limited only to records owned (referenced) by user. +1. When [Share Group Lock](./group/index.md#share-with-group-lock) is enabled the project can't be shared with other groups. It does not affect group with group sharing. ## Project features permissions @@ -242,6 +244,7 @@ group. | Publish [packages](packages/index.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ | | View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ | | Create project in group | | | ✓ (3) | ✓ (3) | ✓ (3) | +| Share (invite) groups with groups | | | | | ✓ | | Create/edit/delete group milestones | | | ✓ | ✓ | ✓ | | Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ | diff --git a/lib/api/search.rb b/lib/api/search.rb index 1eed92e8dec..ac00d3682a0 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -23,7 +23,8 @@ module API SCOPE_PRELOAD_METHOD = { merge_requests: :with_api_entity_associations, projects: :with_api_entity_associations, - issues: :with_api_entity_associations + issues: :with_api_entity_associations, + milestones: :with_api_entity_associations }.freeze def search(additional_params = {}) diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml index a25dc38e4e7..f35470367cc 100644 --- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml @@ -20,4 +20,4 @@ image: "rust:latest" test:cargo: script: - rustc --version && cargo --version # Print version info for debugging - - cargo test --all --verbose + - cargo test --workspace --verbose diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index d5816100023..7b8689069d8 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -24,6 +24,11 @@ module Gitlab raise Projects::ImportService::Error.new(shared.errors.to_sentence) end rescue => e + # If some exception was raised could mean that the SnippetsRepoRestorer + # was not called. This would leave us with snippets without a repository. + # This is a state we don't want them to be, so we better delete them. + remove_non_migrated_snippets + raise Projects::ImportService::Error.new(e.message) ensure remove_base_tmp_dir @@ -153,6 +158,14 @@ module Gitlab def remove_base_tmp_dir FileUtils.rm_rf(@shared.base_path) end + + def remove_non_migrated_snippets + project + .snippets + .left_joins(:snippet_repository) + .where(snippet_repositories: { snippet_id: nil }) + .delete_all + end end end end diff --git a/lib/gitlab/import_export/snippet_repo_restorer.rb b/lib/gitlab/import_export/snippet_repo_restorer.rb index 3cf2fb1e79c..334d13a13ae 100644 --- a/lib/gitlab/import_export/snippet_repo_restorer.rb +++ b/lib/gitlab/import_export/snippet_repo_restorer.rb @@ -5,6 +5,8 @@ module Gitlab class SnippetRepoRestorer < RepoRestorer attr_reader :snippet + SnippetRepositoryError = Class.new(StandardError) + def initialize(snippet:, user:, shared:, path_to_bundle:) @snippet = snippet @user = user @@ -35,6 +37,10 @@ module Gitlab def create_repository_from_db Gitlab::BackgroundMigration::BackfillSnippetRepositories.new.perform_by_ids([snippet.id]) + + unless snippet.reset.snippet_repository + raise SnippetRepositoryError, _("Error creating repository for snippet with id %{snippet_id}") % { snippet_id: snippet.id } + end end end end diff --git a/lib/gitlab/import_export/snippets_repo_restorer.rb b/lib/gitlab/import_export/snippets_repo_restorer.rb index baeb08214a0..5ab28f8dd83 100644 --- a/lib/gitlab/import_export/snippets_repo_restorer.rb +++ b/lib/gitlab/import_export/snippets_repo_restorer.rb @@ -10,13 +10,13 @@ module Gitlab end def restore - @project.snippets.find_each.all? do |snippet| + @project.snippets.find_each.map do |snippet| Gitlab::ImportExport::SnippetRepoRestorer.new(snippet: snippet, user: @user, shared: @shared, path_to_bundle: snippet_repo_bundle_path(snippet)) .restore - end + end.all?(true) end private diff --git a/lib/gitlab/looping_batcher.rb b/lib/gitlab/looping_batcher.rb deleted file mode 100644 index adf0aeda506..00000000000 --- a/lib/gitlab/looping_batcher.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - # Returns an ID range within a table so it can be iterated over. Repeats from - # the beginning after it reaches the end. - # - # Used by Geo in particular to iterate over a replicable and its registry - # table. - # - # Tracks a cursor for each table, by "key". If the table is smaller than - # batch_size, then a range for the whole table is returned on every call. - class LoopingBatcher - # @param [Class] model_class the class of the table to iterate on - # @param [String] key to identify the cursor. Note, cursor is already unique - # per table. - # @param [Integer] batch_size to limit the number of records in a batch - def initialize(model_class, key:, batch_size: 1000) - @model_class = model_class - @key = key - @batch_size = batch_size - end - - # @return [Range] a range of IDs. `nil` if 0 records at or after the cursor. - def next_range! - return unless @model_class.any? - - batch_first_id = cursor_id - - batch_last_id = get_batch_last_id(batch_first_id) - return unless batch_last_id - - batch_first_id..batch_last_id - end - - private - - # @private - # - # Get the last ID of the batch. Increment the cursor or reset it if at end. - # - # @param [Integer] batch_first_id the first ID of the batch - # @return [Integer] batch_last_id the last ID of the batch (not the table) - def get_batch_last_id(batch_first_id) - batch_last_id, more_rows = run_query(@model_class.table_name, @model_class.primary_key, batch_first_id, @batch_size) - - if more_rows - increment_batch(batch_last_id) - else - reset if batch_first_id > 1 - end - - batch_last_id - end - - def run_query(table, primary_key, batch_first_id, batch_size) - sql = <<~SQL - SELECT MAX(batch.id) AS batch_last_id, - EXISTS ( - SELECT #{primary_key} - FROM #{table} - WHERE #{primary_key} > MAX(batch.id) - ) AS more_rows - FROM ( - SELECT #{primary_key} - FROM #{table} - WHERE #{primary_key} >= #{batch_first_id} - ORDER BY #{primary_key} - LIMIT #{batch_size}) AS batch; - SQL - - result = ActiveRecord::Base.connection.exec_query(sql).first - - [result["batch_last_id"], result["more_rows"]] - end - - def reset - set_cursor_id(1) - end - - def increment_batch(batch_last_id) - set_cursor_id(batch_last_id + 1) - end - - # @private - # - # @return [Integer] the cursor ID, or 1 if it is not set - def cursor_id - Rails.cache.fetch("#{cache_key}:cursor_id") || 1 - end - - def set_cursor_id(id) - Rails.cache.write("#{cache_key}:cursor_id", id) - end - - def cache_key - @cache_key ||= "#{self.class.name.parameterize}:#{@model_class.name.parameterize}:#{@key}:cursor_id" - end - end -end diff --git a/lib/gitlab/pagination/keyset/request_context.rb b/lib/gitlab/pagination/keyset/request_context.rb index 8c8138b3076..070fa844347 100644 --- a/lib/gitlab/pagination/keyset/request_context.rb +++ b/lib/gitlab/pagination/keyset/request_context.rb @@ -24,7 +24,9 @@ module Gitlab end def apply_headers(next_page) - request.header('Links', pagination_links(next_page)) + link = pagination_links(next_page) + request.header('Links', link) + request.header('Link', link) end private diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8d44576513e..8b15fead5bb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,6 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-06-01 14:24-0400\n" -"PO-Revision-Date: 2020-06-01 14:24-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -8736,6 +8734,9 @@ msgstr "" msgid "Error creating new iteration" msgstr "" +msgid "Error creating repository for snippet with id %{snippet_id}" +msgstr "" + msgid "Error creating the snippet" msgstr "" diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 52671689448..b2c24284eb9 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -63,7 +63,7 @@ describe Banzai::Pipeline::WikiPipeline do 'when GitLab is hosted at a relative URL' => '/nested/relative/gitlab' }.each do |test_name, relative_url_root| context test_name do before do - allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root) + allow(Rails.application.routes).to receive(:default_url_options).and_return(script_name: relative_url_root) end describe "linking to pages within the wiki" do diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb index b8c34a461ef..494f7e3a00d 100644 --- a/spec/lib/gitlab/import_export/importer_spec.rb +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -97,6 +97,49 @@ describe Gitlab::ImportExport::Importer do end end + context 'when import fails' do + let(:error_message) { 'foo' } + + shared_examples 'removes any non migrated snippet' do + specify do + create_list(:project_snippet, 2, project: project) + snippet_with_repo = create(:project_snippet, :repository, project: project) + + expect { importer.execute }.to change(Snippet, :count).by(-2).and(raise_error(Projects::ImportService::Error)) + + expect(snippet_with_repo.reload).to be_present + end + end + + context 'when there is a graceful error' do + before do + allow_next_instance_of(Gitlab::ImportExport::AvatarRestorer) do |instance| + allow(instance).to receive(:avatar_export_file).and_raise(StandardError, error_message) + end + end + + it 'raises and exception' do + expect { importer.execute }.to raise_error(Projects::ImportService::Error, error_message) + end + + it_behaves_like 'removes any non migrated snippet' + end + + context 'when an unexpected exception is raised' do + before do + allow_next_instance_of(Gitlab::ImportExport::AvatarRestorer) do |instance| + allow(instance).to receive(:restore).and_raise(StandardError, error_message) + end + end + + it 'captures it and raises the Projects::ImportService::Error exception' do + expect { importer.execute }.to raise_error(Projects::ImportService::Error, error_message) + end + + it_behaves_like 'removes any non migrated snippet' + end + end + context 'when project successfully restored' do context "with a project in a user's namespace" do let!(:existing_project) { create(:project, namespace: user.namespace) } diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb index 737c3e97bb1..779b65e33d8 100644 --- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb @@ -34,6 +34,15 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do expect(blob.data).to eq(snippet.content) end end + + context 'when the repository creation fails' do + it 'returns false' do + allow_any_instance_of(Gitlab::BackgroundMigration::BackfillSnippetRepositories).to receive(:perform_by_ids).and_return(nil) + + expect(restorer.restore).to be false + expect(shared.errors.first).to match(/Error creating repository for snippet/) + end + end end context 'when the snippet does not have a bundle file path' do diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb index f0582a322ef..fdae259c2f1 100644 --- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb @@ -86,13 +86,14 @@ describe Gitlab::ImportExport::SnippetsRepoRestorer do it_behaves_like 'imports snippet repositories' end - context 'when one snippet cannot be saved' do - it 'returns false and do not process other snippets' do + context 'when any of the snippet repositories cannot be created' do + it 'continues processing other snippets and returns false' do allow(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet1)).and_return(service) allow(service).to receive(:restore).and_return(false) - expect(Gitlab::ImportExport::SnippetRepoRestorer).not_to receive(:new).with(hash_including(snippet: snippet2)) - expect(restorer.restore).to be_falsey + expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet2)).and_call_original + + expect(restorer.restore).to be false end end diff --git a/spec/lib/gitlab/looping_batcher_spec.rb b/spec/lib/gitlab/looping_batcher_spec.rb deleted file mode 100644 index b03e969c1e7..00000000000 --- a/spec/lib/gitlab/looping_batcher_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::LoopingBatcher, :use_clean_rails_memory_store_caching do - describe '#next_range!' do - let(:model_class) { LfsObject } - let(:key) { 'looping_batcher_spec' } - let(:batch_size) { 2 } - - subject { described_class.new(model_class, key: key, batch_size: batch_size).next_range! } - - context 'when there are no records' do - it { is_expected.to be_nil } - end - - context 'when there are records' do - let!(:records) { create_list(model_class.underscore, 3) } - - context 'when it has never been called before' do - it { is_expected.to be_a Range } - - it 'starts from the beginning' do - expect(subject.first).to eq(1) - end - - it 'ends at a full batch' do - expect(subject.last).to eq(records.second.id) - end - - context 'when the batch size is greater than the number of records' do - let(:batch_size) { 5 } - - it 'ends at the last ID' do - expect(subject.last).to eq(records.last.id) - end - end - end - - context 'when it was called before' do - context 'when the previous batch included the end of the table' do - before do - described_class.new(model_class, key: key, batch_size: model_class.count).next_range! - end - - it 'starts from the beginning' do - expect(subject).to eq(1..records.second.id) - end - end - - context 'when the previous batch did not include the end of the table' do - before do - described_class.new(model_class, key: key, batch_size: model_class.count - 1).next_range! - end - - it 'starts after the previous batch' do - expect(subject).to eq(records.last.id..records.last.id) - end - end - - context 'if cache is cleared' do - it 'starts from the beginning' do - Rails.cache.clear - - expect(subject).to eq(1..records.second.id) - end - end - end - end - end -end diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb index 6cd5ccc3c19..d6d5340f38b 100644 --- a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb +++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb @@ -60,9 +60,7 @@ describe Gitlab::Pagination::Keyset::RequestContext do it 'sets Links header with same host/path as the original request' do orig_uri = URI.parse(request_context.request.url) - expect(request_context).to receive(:header) do |name, header| - expect(name).to eq('Links') - + expect(request_context).to receive(:header).twice do |name, header| first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures uri = URI.parse(first_link) @@ -77,9 +75,7 @@ describe Gitlab::Pagination::Keyset::RequestContext do it 'sets Links header with a link to the next page' do orig_uri = URI.parse(request_context.request.url) - expect(request_context).to receive(:header) do |name, header| - expect(name).to eq('Links') - + expect(request_context).to receive(:header).twice do |name, header| first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures query = CGI.parse(URI.parse(first_link).query) @@ -97,9 +93,7 @@ describe Gitlab::Pagination::Keyset::RequestContext do it 'sets Links header with a link to the next page' do orig_uri = URI.parse(request_context.request.url) - expect(request_context).to receive(:header) do |name, header| - expect(name).to eq('Links') - + expect(request_context).to receive(:header).twice do |name, header| first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures query = CGI.parse(URI.parse(first_link).query) diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb index ea58f2f4337..5f27ed350b3 100644 --- a/spec/models/alert_management/alert_spec.rb +++ b/spec/models/alert_management/alert_spec.rb @@ -325,7 +325,7 @@ describe AlertManagement::Alert do let(:alert) { create(:alert_management_alert) } it 'increments the events count by 1' do - expect { subject }.to change { alert.events}.by(1) + expect { subject }.to change { alert.events }.by(1) end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 8a1c68916e4..6f6737864b6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -597,6 +597,10 @@ describe API::Projects do expect(response.header).to include('Links') expect(response.header['Links']).to include('pagination=keyset') expect(response.header['Links']).to include("id_after=#{public_project.id}") + + expect(response.header).to include('Link') + expect(response.header['Link']).to include('pagination=keyset') + expect(response.header['Link']).to include("id_after=#{public_project.id}") end it 'contains only the first project with per_page = 1' do @@ -613,12 +617,17 @@ describe API::Projects do expect(response.header).to include('Links') expect(response.header['Links']).to include('pagination=keyset') expect(response.header['Links']).to include("id_after=#{project3.id}") + + expect(response.header).to include('Link') + expect(response.header['Link']).to include('pagination=keyset') + expect(response.header['Link']).to include("id_after=#{project3.id}") end it 'does not include a next link when the page does not have any records' do get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id)) expect(response.header).not_to include('Links') + expect(response.header).not_to include('Link') end it 'returns an empty array when the page does not have any records' do @@ -644,6 +653,10 @@ describe API::Projects do expect(response.header).to include('Links') expect(response.header['Links']).to include('pagination=keyset') expect(response.header['Links']).to include("id_before=#{project3.id}") + + expect(response.header).to include('Link') + expect(response.header['Link']).to include('pagination=keyset') + expect(response.header['Link']).to include("id_before=#{project3.id}") end it 'contains only the last project with per_page = 1' do @@ -672,6 +685,11 @@ describe API::Projects do match[1] end + link = response.header['Link'] + url = link&.match(/<[^>]+(\/projects\?[^>]+)>; rel="next"/) do |match| + match[1] + end + ids += Gitlab::Json.parse(response.body).map { |p| p['id'] } end diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb index 73f9f103902..32fde5b3167 100644 --- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb +++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do let_it_be(:project) { create(:project) } describe '#execute' do - subject { described_class.new(project, nil, payload).execute } + subject(:execute) { described_class.new(project, nil, payload).execute } context 'when alert payload is valid' do let(:parsed_alert) { Gitlab::Alerting::Alert.new(project: project, payload: payload) } @@ -37,9 +37,13 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do context 'when alert with the same fingerprint already exists' do let!(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: parsed_alert.gitlab_fingerprint) } + it 'increases alert events count' do + expect { execute }.to change { alert.reload.events }.by(1) + end + context 'when status can be changed' do it 'changes status to triggered' do - expect { subject }.to change { alert.reload.triggered? }.to(true) + expect { execute }.to change { alert.reload.triggered? }.to(true) end end @@ -56,7 +60,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do alert_id: alert.id ) - subject + execute end end @@ -66,7 +70,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do context 'when alert does not exist' do context 'when alert can be created' do it 'creates a new alert' do - expect { subject }.to change { AlertManagement::Alert.where(project: project).count }.by(1) + expect { execute }.to change { AlertManagement::Alert.where(project: project).count }.by(1) end end @@ -85,7 +89,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do alert_errors: { hosts: ['hosts array is over 255 chars'] } ) - subject + execute end end @@ -99,7 +103,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do context 'when status can be changed' do it 'resolves an existing alert' do - expect { subject }.to change { alert.reload.resolved? }.to(true) + expect { execute }.to change { alert.reload.resolved? }.to(true) end end @@ -116,7 +120,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do alert_id: alert.id ) - subject + execute end end @@ -128,8 +132,8 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do let(:payload) { {} } it 'responds with bad_request' do - expect(subject).to be_error - expect(subject.http_status).to eq(:bad_request) + expect(execute).to be_error + expect(execute.http_status).to eq(:bad_request) end end end diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 84569e95e11..a881d5f036c 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -32,6 +32,13 @@ RSpec.shared_examples 'wiki model' do it 'returns the wiki base path' do expect(subject.wiki_base_path).to eq("#{wiki_container.web_url(only_path: true)}/-/wikis") end + + it 'includes the relative URL root' do + allow(Rails.application.routes).to receive(:default_url_options).and_return(script_name: '/root') + + expect(subject.wiki_base_path).to start_with('/root/') + expect(subject.wiki_base_path).not_to start_with('/root/root') + end end describe '#wiki' do