diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-14 18:17:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-14 18:17:06 +0300 |
commit | be320585cb65cc36aa94797ea71853c7e8d70048 (patch) | |
tree | f3adafb0a81f9fdfc7dde0352e808a1fd57557c1 | |
parent | 79a7da2537e94c34aba92ad4b9a5dc1da431b2c4 (diff) |
Add latest changes from gitlab-org/gitlab@master
35 files changed, 477 insertions, 128 deletions
diff --git a/.rubocop_todo/style/open_struct_use.yml b/.rubocop_todo/style/open_struct_use.yml index c2f0d28575e..296f54fbc84 100644 --- a/.rubocop_todo/style/open_struct_use.yml +++ b/.rubocop_todo/style/open_struct_use.yml @@ -2,10 +2,8 @@ Style/OpenStructUse: Exclude: - app/helpers/application_settings_helper.rb - - ee/spec/db/production/license_spec.rb - ee/spec/features/projects/new_project_spec.rb - ee/spec/finders/template_finder_spec.rb - - ee/spec/graphql/ee/resolvers/board_lists_resolver_spec.rb - ee/spec/helpers/ee/blob_helper_spec.rb - ee/spec/lib/gitlab/auth/group_saml/failure_handler_spec.rb - ee/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index de948ac7d19..185e018736a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -906,7 +906,7 @@ GEM orm_adapter (0.5.0) os (1.1.1) parallel (1.20.1) - parser (3.0.2.0) + parser (3.0.3.2) ast (~> 2.4.1) parslet (1.8.2) pastel (0.8.0) diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb index 2a62dd5c0e5..23b0e71d836 100644 --- a/app/finders/packages/group_packages_finder.rb +++ b/app/finders/packages/group_packages_finder.rb @@ -40,10 +40,14 @@ module Packages # access to packages is ruled by: # - project is public or the current user has access to it with at least the reporter level # - the repository feature is available to the current_user - ::Project - .in_namespace(groups) - .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER) - .with_feature_available_for_user(:repository, current_user) + if current_user.is_a?(DeployToken) + current_user.accessible_projects + else + ::Project + .in_namespace(groups) + .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER) + .with_feature_available_for_user(:repository, current_user) + end end def groups diff --git a/app/models/concerns/transactions.rb b/app/models/concerns/transactions.rb index a186ebc8475..1c9bd8274f5 100644 --- a/app/models/concerns/transactions.rb +++ b/app/models/concerns/transactions.rb @@ -8,7 +8,7 @@ module Transactions # transaction. Handles special cases when running inside a test environment, # where tests may be wrapped in transactions def inside_transaction? - base = Rails.env.test? ? @open_transactions_baseline.to_i : 0 + base = Rails.env.test? ? open_transactions_baseline.to_i : 0 connection.open_transactions > base end @@ -24,5 +24,15 @@ module Transactions def reset_open_transactions_baseline @open_transactions_baseline = 0 end + + def open_transactions_baseline + return unless Rails.env.test? + + if @open_transactions_baseline.nil? + return self == ApplicationRecord ? nil : superclass.open_transactions_baseline + end + + @open_transactions_baseline + end end end diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 5f07306aada..9fd35faf0b7 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class BuildDetailsEntity < Ci::JobEntity - expose :coverage, :erased_at, :duration + expose :coverage, :erased_at, :finished_at, :duration expose :tag_list, as: :tags expose :has_trace?, as: :has_trace expose :stage diff --git a/app/services/ci/parse_dotenv_artifact_service.rb b/app/services/ci/parse_dotenv_artifact_service.rb index 94f7496ed04..40e2cd82b4f 100644 --- a/app/services/ci/parse_dotenv_artifact_service.rb +++ b/app/services/ci/parse_dotenv_artifact_service.rb @@ -33,13 +33,13 @@ module Ci end def parse!(artifact) - variables = [] + variables = {} artifact.each_blob do |blob| blob.each_line do |line| key, value = scan_line!(line) - variables << Ci::JobVariable.new(job_id: artifact.job_id, + variables[key] = Ci::JobVariable.new(job_id: artifact.job_id, source: :dotenv, key: key, value: value) end end @@ -49,7 +49,7 @@ module Ci "Dotenv files cannot have more than #{dotenv_variable_limit} variables" end - variables + variables.values end def scan_line!(line) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e4c7907c278..5269b3b65d1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -15635,6 +15635,19 @@ Represents a link related to a vulnerability. | <a id="vulnerabilitylinkname"></a>`name` | [`String`](#string) | Name of the link. | | <a id="vulnerabilitylinkurl"></a>`url` | [`String!`](#string) | URL of the link. | +### `VulnerabilityLocationClusterImageScanning` + +Represents the location of a vulnerability found by a cluster image scan. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="vulnerabilitylocationclusterimagescanningdependency"></a>`dependency` | [`VulnerableDependency`](#vulnerabledependency) | Dependency containing the vulnerability. | +| <a id="vulnerabilitylocationclusterimagescanningimage"></a>`image` | [`String`](#string) | Name of the vulnerable container image. | +| <a id="vulnerabilitylocationclusterimagescanningkubernetesresource"></a>`kubernetesResource` | [`VulnerableKubernetesResource`](#vulnerablekubernetesresource) | Kubernetes resource which uses the vulnerable container image. | +| <a id="vulnerabilitylocationclusterimagescanningoperatingsystem"></a>`operatingSystem` | [`String`](#string) | Operating system that runs on the vulnerable container image. | + ### `VulnerabilityLocationContainerScanning` Represents the location of a vulnerability found by a container security scan. @@ -15785,6 +15798,21 @@ Represents a vulnerable dependency. Used in vulnerability location data. | <a id="vulnerabledependencypackage"></a>`package` | [`VulnerablePackage`](#vulnerablepackage) | Package associated with the vulnerable dependency. | | <a id="vulnerabledependencyversion"></a>`version` | [`String`](#string) | Version of the vulnerable dependency. | +### `VulnerableKubernetesResource` + +Represents a vulnerable Kubernetes resource. Used in vulnerability location data. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="vulnerablekubernetesresourceagent"></a>`agent` | [`ClusterAgent`](#clusteragent) | Kubernetes Agent which performed the scan. | +| <a id="vulnerablekubernetesresourceclusterid"></a>`clusterId` | [`ClustersClusterID`](#clustersclusterid) | ID of the Cluster integration which was used to perform the scan. | +| <a id="vulnerablekubernetesresourcecontainername"></a>`containerName` | [`String!`](#string) | Name of the container that had its image scanned. | +| <a id="vulnerablekubernetesresourcekind"></a>`kind` | [`String!`](#string) | Kind of the Kubernetes resource. | +| <a id="vulnerablekubernetesresourcename"></a>`name` | [`String!`](#string) | Name of the Kubernetes resource. | +| <a id="vulnerablekubernetesresourcenamespace"></a>`namespace` | [`String!`](#string) | Kubernetes namespace which the resource resides in. | + ### `VulnerablePackage` Represents a vulnerable package. Used in vulnerability dependency data. @@ -17967,6 +17995,7 @@ Represents a vulnerability location. The fields with data will depend on the vul One of: +- [`VulnerabilityLocationClusterImageScanning`](#vulnerabilitylocationclusterimagescanning) - [`VulnerabilityLocationContainerScanning`](#vulnerabilitylocationcontainerscanning) - [`VulnerabilityLocationCoverageFuzzing`](#vulnerabilitylocationcoveragefuzzing) - [`VulnerabilityLocationDast`](#vulnerabilitylocationdast) diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md index f93025a08fe..cd38cf58c71 100644 --- a/doc/ci/yaml/artifacts_reports.md +++ b/doc/ci/yaml/artifacts_reports.md @@ -169,6 +169,11 @@ The `dotenv` report collects a set of environment variables as artifacts. The collected variables are registered as runtime-created variables of the job, which you can use to [set dynamic environment URLs after a job finishes](../environments/index.md#set-dynamic-environment-urls-after-a-job-finishes). +If duplicate environment variables are present in a `dotenv` report: + +- In GitLab 14.6 and later, the last one specified is used. +- In GitLab 14.5 and earlier, an error occurs. + The exceptions to the [original dotenv rules](https://github.com/motdotla/dotenv#rules) are: - The variable key can contain only letters, digits, and underscores (`_`). diff --git a/doc/development/filtering_by_label.md b/doc/development/filtering_by_label.md index 6f9811f7e05..9e759744a1a 100644 --- a/doc/development/filtering_by_label.md +++ b/doc/development/filtering_by_label.md @@ -47,9 +47,9 @@ This is more complicated than is ideal. It makes the query construction more prone to errors (such as [issue #15557](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/15557)). -## Attempt A: WHERE EXISTS +## Attempt A: `WHERE EXISTS` -### Attempt A1: use multiple subqueries with WHERE EXISTS +### Attempt A1: use multiple subqueries with `WHERE EXISTS` In [issue #37137](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/37137) and its associated [merge request](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14022), @@ -82,7 +82,7 @@ AND (EXISTS ( While this worked without schema changes, and did improve readability somewhat, it did not improve query performance. -### Attempt A2: use label IDs in the WHERE EXISTS clause +### Attempt A2: use label IDs in the `WHERE EXISTS` clause In [merge request #34503](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34503), we followed a similar approach to A1. But this time, we did a separate query to fetch the IDs of the labels used in the filter so that we avoid the `JOIN` in the `EXISTS` clause and filter directly by diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index 89f36397039..8461aca8c8d 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -48,7 +48,7 @@ each node should have: Elasticsearch is *not* included in the Omnibus packages or when you install from source. You must [install it separately](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/install-elasticsearch.html "Elasticsearch 7.x installation documentation") and ensure you select your version. Detailed information on how to install Elasticsearch is out of the scope of this page. -You can install Elasticsearch yourself, or use a cloud hosted offering such as [Elasticsearch Service](https://www.elastic.co/elasticsearch/service)(available on AWS, GCP, or Azure) or the [Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html) +You can install Elasticsearch yourself, or use a cloud hosted offering such as [Elasticsearch Service](https://www.elastic.co/elasticsearch/service) (available on AWS, GCP, or Azure) or the [Amazon OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/gsg.html) service. You should install Elasticsearch on a separate server. Running Elasticsearch on the same server as GitLab is not recommended and can cause a degradation in GitLab instance performance. @@ -208,8 +208,8 @@ The following Elasticsearch settings are available: | `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, you should use at least 5 shards, and indexes with tens of millions of documents need to have more shards ([see below](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). | | `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value increases total disk space required by the index. | | `Limit namespaces and projects that can be indexed` | Enabling this allows you to select namespaces and projects to index. All other namespaces and projects use database search instead. If you enable this option but do not select any namespaces or projects, none are indexed. [Read more below](#limit-namespaces-and-projects). -| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) for details of AWS hosted Elasticsearch domain access policy configuration. | -| `AWS Region` | The AWS region in which your Elasticsearch service is located. | +| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details of AWS hosted OpenSearch domain access policy configuration. | +| `AWS Region` | The AWS region in which your OpenSearch Service is located. | | `AWS Access Key` | The AWS access key. | | `AWS Secret Access Key` | The AWS secret access key. | | `Maximum file size indexed` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-file-size-indexed). | @@ -890,11 +890,11 @@ There is also an easy way to check it automatically with `sudo gitlab-rake gitla This exception is seen when your Elasticsearch cluster is configured to reject requests above a certain size (10MiB in this case). This corresponds to the `http.max_content_length` setting in `elasticsearch.yml`. Increase it to a larger size and restart your Elasticsearch cluster. -AWS has [fixed limits](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html) for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of the underlying instance. +AWS has [fixed limits](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/aes-limits.html) for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of the underlying instance. ### My single node Elasticsearch cluster status never goes from `yellow` to `green` even though everything seems to be running properly -**For a single node Elasticsearch cluster the functional cluster health status will be yellow** (never green) because the primary shard is allocated but replicas cannot be as there is no other node to which Elasticsearch can assign a replica. This also applies if you are using the [Amazon Elasticsearch](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-handling-errors.html#aes-handling-errors-yellow-cluster-status) service. +**For a single node Elasticsearch cluster the functional cluster health status will be yellow** (never green) because the primary shard is allocated but replicas cannot be as there is no other node to which Elasticsearch can assign a replica. This also applies if you are using the [Amazon OpenSearch](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/aes-handling-errors.html#aes-handling-errors-yellow-cluster-status) service. WARNING: Setting the number of replicas to `0` is discouraged (this is not allowed in the GitLab Elasticsearch Integration menu). If you are planning to add more Elasticsearch nodes (for a total of more than 1 Elasticsearch) the number of replicas will need to be set to an integer value larger than `0`. Failure to do so will result in lack of redundancy (losing one node will corrupt the index). @@ -981,11 +981,11 @@ however searches will only surface results that can be viewed by the user. Advanced Search will honor all permission checks in the application by filtering out projects that a user does not have access to at search time. -### Access requirements for the self-managed AWS Elasticsearch Service +### Access requirements for the self-managed AWS OpenSearch Service -To use the self-managed AWS Elasticsearch Service with GitLab, configure your instance's domain access policies +To use the self-managed AWS OpenSearch Service with GitLab, configure your instance's domain access policies to contain the actions below. -See [Identity and Access Management in Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) for details. +See [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details. ```plaintext es:ESHttpDelete diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md index 7861258e23f..23bd140d4b7 100644 --- a/doc/user/packages/composer_repository/index.md +++ b/doc/user/packages/composer_repository/index.md @@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in GitLab 13.2. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3. > - Support for Composer 2.0 [added](https://gitlab.com/gitlab-org/gitlab/-/issues/259840) in GitLab 13.10. +> - Deploy token support [added](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) in GitLab 14.6. WARNING: The Composer package registry for GitLab is under development and isn't ready for production use due to @@ -88,13 +89,12 @@ Prerequisites: - A valid `composer.json` file. - The Packages feature is enabled in a GitLab repository. - The project ID, which is on the project's home page. -- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`. +- One of the following token types: + - A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`. + - A [deploy token](../../project/deploy_tokens/index.md) + with the scope set to `write_package_registry`. - NOTE: - [Deploy tokens](../../project/deploy_tokens/index.md) are - [not yet supported](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) for use with Composer. - -To publish the package: +To publish the package with a personal access token: - Send a `POST` request to the [Packages API](../../../api/packages.md). @@ -109,6 +109,21 @@ To publish the package: - `<tag>` is the Git tag name of the version you want to publish. To publish a branch, use `branch=<branch>` instead of `tag=<tag>`. +To publish the package with a deploy token: + +- Send a `POST` request to the [Packages API](../../../api/packages.md). + + For example, you can use `curl`: + + ```shell + curl --data tag=<tag> --header "Deploy-Token: <deploy-token>" "https://gitlab.example.com/api/v4/projects/<project_id>/packages/composer" + ``` + + - `<deploy-token>` is your deploy token + - `<project_id>` is your project ID. + - `<tag>` is the Git tag name of the version you want to publish. + To publish a branch, use `branch=<branch>` instead of `tag=<tag>`. + You can view the published package by going to **Packages & Registries > Package Registry** and selecting the **Composer** tab. @@ -159,11 +174,11 @@ Prerequisites: - A package in the Package Registry. - The group ID, which is on the group's home page. -- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to, at minimum, `read_api`. - - NOTE: - [Deploy tokens](../../project/deploy_tokens/index.md) are - [not yet supported](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) for use with Composer. +- One of the following token types: + - A [personal access token](../../../user/profile/personal_access_tokens.md) + with the scope set to, at minimum, `api`. + - A [deploy token](../../project/deploy_tokens/index.md) + with the scope set to `read_package_registry`, `write_package_registry`, or both. To install a package: @@ -213,6 +228,8 @@ To install a package: 1. Create an `auth.json` file with your GitLab credentials: + Using a personal access token: + ```shell composer config gitlab-token.<DOMAIN-NAME> <personal_access_token> ``` @@ -229,6 +246,26 @@ To install a package: } ``` + Using a deploy token: + + ```shell + composer config gitlab-token.<DOMAIN-NAME> <deploy_token_username> <deploy_token> + ``` + + Result in the `auth.json` file: + + ```json + { + ... + "gitlab-token": { + "<DOMAIN-NAME>": { + "username": "<deploy_token_username>", + "token": "<deploy_token>", + ... + } + } + ``` + You can unset this with the command: ```shell @@ -236,7 +273,8 @@ To install a package: ``` - `<DOMAIN-NAME>` is the GitLab instance URL `gitlab.com` or `gitlab.example.com`. - - `<personal_access_token>` with the scope set to `read_api`. + - `<personal_access_token>` with the scope set to `api`, or `<deploy_token>` with the scope set + to `read_package_registry` and/or `write_package_registry`. 1. If you are on a GitLab self-managed instance, add `gitlab-domains` to `composer.json`. @@ -298,10 +336,19 @@ To install a package: WARNING: Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, -consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token +consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your access token stored in a [GitLab CI/CD variable](../../../ci/variables/index.md) or in [HashiCorp Vault](../../../ci/secrets/index.md). +### Working with Deploy Tokens + +Although Composer packages are accessed at the group level, a group or project deploy token can be +used to access them: + +- A group deploy token has access to all packages published to projects in that group or its + subgroups. +- A project deploy token only has access to packages published to that particular project. + ## Supported CLI commands The GitLab Composer repository supports the following Composer CLI commands: diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 94cad7e6c65..0e6e04d2645 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -70,7 +70,7 @@ module API end desc 'Composer packages endpoint at group level' - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/packages' do presenter.root end @@ -79,7 +79,7 @@ module API params do requires :sha, type: String, desc: 'Shasum of current json' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p/:sha' do presenter.provider end @@ -88,7 +88,7 @@ module API params do requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? @@ -99,7 +99,7 @@ module API params do requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? not_found! if params[:sha].blank? @@ -119,7 +119,7 @@ module API desc 'Composer packages endpoint for registering packages' namespace ':id/packages/composer' do - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true params do optional :branch, type: String, desc: 'The name of the branch' diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb index 520b1ce6119..43475742214 100644 --- a/lib/gitlab/ci/config/entry/processable.rb +++ b/lib/gitlab/ci/config/entry/processable.rb @@ -26,7 +26,7 @@ module Gitlab validates :name, length: { maximum: 255 }, if: -> { ::Feature.enabled?(:ci_validate_job_length, default_enabled: :yaml) } validates :config, disallowed_keys: { - in: %i[only except when start_in], + in: %i[only except start_in], message: 'key may not be used with `rules`' }, if: :has_rules? diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb index c92f6b6036e..762292f0fa3 100644 --- a/lib/gitlab/ci/pipeline/seed/build.rb +++ b/lib/gitlab/ci/pipeline/seed/build.rb @@ -28,7 +28,7 @@ module Gitlab @except = Gitlab::Ci::Build::Policy .fabricate(attributes.delete(:except)) @rules = Gitlab::Ci::Build::Rules - .new(attributes.delete(:rules), default_when: 'on_success') + .new(attributes.delete(:rules), default_when: attributes[:when]) @cache = Gitlab::Ci::Build::Cache .new(attributes.delete(:cache), @pipeline) diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb index 8ce203d4585..5d91292b8de 100644 --- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb +++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb @@ -6,7 +6,7 @@ module Gitlab class SidekiqServerMiddleware JobReplicaNotUpToDate = Class.new(StandardError) - MINIMUM_DELAY_INTERVAL = 1 + MINIMUM_DELAY_INTERVAL_SECONDS = 0.8 def call(worker, job, _queue) worker_class = worker.class @@ -46,11 +46,13 @@ module Gitlab return :primary_no_wal if wal_locations.blank? + # Happy case: we can read from a replica. + return replica_strategy(worker_class, job) if databases_in_sync?(wal_locations) + sleep_if_needed(job) if databases_in_sync?(wal_locations) - # Happy case: we can read from a replica. - retried_before?(worker_class, job) ? :replica_retried : :replica + replica_strategy(worker_class, job) elsif can_retry?(worker_class, job) # Optimistic case: The worker allows retries and we have retries left. :retry @@ -61,9 +63,9 @@ module Gitlab end def sleep_if_needed(job) - remaining_delay = MINIMUM_DELAY_INTERVAL - (Time.current.to_f - job['created_at'].to_f) + remaining_delay = MINIMUM_DELAY_INTERVAL_SECONDS - (Time.current.to_f - job['created_at'].to_f) - sleep remaining_delay if remaining_delay > 0 && remaining_delay < MINIMUM_DELAY_INTERVAL + sleep remaining_delay if remaining_delay > 0 && remaining_delay < MINIMUM_DELAY_INTERVAL_SECONDS end def get_wal_locations(job) @@ -80,6 +82,10 @@ module Gitlab worker_class.get_data_consistency == :delayed && not_yet_retried?(job) end + def replica_strategy(worker_class, job) + retried_before?(worker_class, job) ? :replica_retried : :replica + end + def retried_before?(worker_class, job) worker_class.get_data_consistency == :delayed && !not_yet_retried?(job) end diff --git a/lib/gitlab/database/postgres_hll/buckets.rb b/lib/gitlab/database/postgres_hll/buckets.rb index 429e823379f..76818bbf340 100644 --- a/lib/gitlab/database/postgres_hll/buckets.rb +++ b/lib/gitlab/database/postgres_hll/buckets.rb @@ -65,8 +65,7 @@ module Gitlab ).to_i if num_zero_buckets > 0 && num_uniques < 2.5 * TOTAL_BUCKETS - ((0.7213 / (1 + 1.079 / TOTAL_BUCKETS)) * (TOTAL_BUCKETS * - Math.log2(TOTAL_BUCKETS.to_f / num_zero_buckets))) + TOTAL_BUCKETS * Math.log(TOTAL_BUCKETS.to_f / num_zero_buckets) else num_uniques end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 571945bf646..b73199f1fdd 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -281,6 +281,8 @@ module QA end def knapsack? + return false unless ENV['CI_NODE_TOTAL'].to_i > 1 + !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN']) end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index fb18311bb52..80d8a9a1892 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -169,6 +169,10 @@ RSpec.describe QA::Runtime::Env do end describe '.knapsack?' do + before do + stub_env('CI_NODE_TOTAL', '2') + end + it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do stub_env('KNAPSACK_GENERATE_REPORT', 'true') @@ -190,28 +194,11 @@ RSpec.describe QA::Runtime::Env do it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do expect(described_class.knapsack?).to be_falsey end - end - describe '.knapsack?' do - it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do + it 'returns false if not running in parallel job' do + stub_env('CI_NODE_TOTAL', '1') stub_env('KNAPSACK_GENERATE_REPORT', 'true') - expect(described_class.knapsack?).to be_truthy - end - - it 'returns true if KNAPSACK_REPORT_PATH is defined' do - stub_env('KNAPSACK_REPORT_PATH', '/a/path') - - expect(described_class.knapsack?).to be_truthy - end - - it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do - stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern') - - expect(described_class.knapsack?).to be_truthy - end - - it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do expect(described_class.knapsack?).to be_falsey end end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 88913b4e972..c122cb0f90c 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -263,6 +263,12 @@ function deploy() { gitaly_image_tag=$(parse_gitaly_image_tag) gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell" gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-ee" + sentry_enabled="false" + + if [ -n ${REVIEW_APPS_SENTRY_DSN} ]; then + echo "REVIEW_APPS_SENTRY_DSN detected, enabling Sentry" + sentry_enabled="true" + fi ensure_namespace "${namespace}" label_namespace "${namespace}" "tls=review-apps-tls" # label namespace for kubed to sync tls @@ -283,6 +289,9 @@ HELM_CMD=$(cat << EOF --set releaseOverride="${release}" \ --set global.hosts.hostSuffix="${HOST_SUFFIX}" \ --set global.hosts.domain="${REVIEW_APPS_DOMAIN}" \ + --set global.sentry.enabled="${sentry_enabled}" \ + --set global.sentry.dsn="${REVIEW_APPS_SENTRY_DSN}" \ + --set global.sentry.environment="review" \ --set gitlab.migrations.image.repository="${gitlab_toolbox_image_repository}" \ --set gitlab.migrations.image.tag="${CI_COMMIT_REF_SLUG}" \ --set gitlab.gitaly.image.repository="${gitlab_gitaly_image_repository}" \ diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb index 3254c436674..c2dbfb59eb2 100644 --- a/spec/finders/packages/group_packages_finder_spec.rb +++ b/spec/finders/packages/group_packages_finder_spec.rb @@ -107,6 +107,28 @@ RSpec.describe Packages::GroupPackagesFinder do end end + context 'deploy tokens' do + let(:add_user_to_group) { false } + + context 'group deploy token' do + let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } + + let(:user) { deploy_token_for_group } + + it { is_expected.to match_array([package1, package2, package4]) } + end + + context 'project deploy token' do + let_it_be(:deploy_token_for_project) { create(:deploy_token, read_package_registry: true) } + let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token_for_project, project: subproject) } + + let(:user) { deploy_token_for_project } + + it { is_expected.to match_array([package4]) } + end + end + context 'avoid N+1 query' do it 'avoids N+1 database queries' do count = ActiveRecord::QueryRecorder.new { subject } diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index 6c9c8fa5df5..62feed3dda0 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -163,7 +163,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do }) end - it { is_expected.not_to be_valid } + it { is_expected.to be_valid } end context 'when bridge configuration uses rules with only' do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 0bb26babfc0..885f3eaff79 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -118,6 +118,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end + context 'when config uses both "when:" and "rules:"' do + let(:config) do + { + script: 'echo', + when: 'on_failure', + rules: [{ if: '$VARIABLE', when: 'on_success' }] + } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + context 'when delayed job' do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } @@ -268,21 +282,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end - context 'when it uses both "when:" and "rules:"' do - let(:config) do - { - script: 'echo', - when: 'on_failure', - rules: [{ if: '$VARIABLE', when: 'on_success' }] - } - end - - it 'returns an error about when: being combined with rules' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job config key may not be used with `rules`: when' - end - end - context 'when delayed job' do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index c9c28e2eb8b..5b9337ede34 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -33,6 +33,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do end end + context 'when config uses both "when:" and "rules:"' do + let(:config) do + { + script: 'echo', + when: 'on_failure', + rules: [{ if: '$VARIABLE', when: 'on_success' }] + } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + context 'when job name is more than 255' do let(:entry) { node_class.new(config, name: ('a' * 256).to_sym) } @@ -90,21 +104,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do end end - context 'when it uses both "when:" and "rules:"' do - let(:config) do - { - script: 'echo', - when: 'on_failure', - rules: [{ if: '$VARIABLE', when: 'on_success' }] - } - end - - it 'returns an error about when: being combined with rules' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job config key may not be used with `rules`: when' - end - end - context 'when only: is used with rules:' do let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } } diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index c53f1be1057..68806fbf287 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) } let(:root_variables) { [] } let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) } - let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } } + let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage, when: 'on_success' } } let(:previous_stages) { [] } let(:current_stage) { double(seeds_names: [attributes[:name]]) } @@ -61,17 +61,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end end - context 'with job:rules but no explicit when:' do - context 'is matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null' }] } } + context 'with job: rules but no explicit when:' do + let(:base_attributes) { { name: 'rspec', ref: 'master' } } + + context 'with a manual job' do + context 'with a matched rule' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) } + + it { is_expected.to include(when: 'manual') } + end + + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) } - it { is_expected.to include(when: 'on_success') } + it { is_expected.to include(when: 'never') } + end end - context 'is not matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null' }] } } + context 'with an automatic job' do + context 'is matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) } - it { is_expected.to include(when: 'never') } + it { is_expected.to include(when: 'on_success') } + end + + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) } + + it { is_expected.to include(when: 'never') } + end end end @@ -901,7 +919,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'using rules:' do using RSpec::Parameterized - let(:attributes) { { name: 'rspec', rules: rule_set } } + let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } } context 'with a matching if: rule' do context 'with an explicit `when: never`' do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index f00a801286d..e8b38b21ef8 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2139,7 +2139,7 @@ module Gitlab end end - context 'with when/rules conflict' do + context 'with when/rules' do subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } let(:config) do @@ -2174,7 +2174,7 @@ module Gitlab } end - it_behaves_like 'returns errors', /may not be used with `rules`: when/ + it { is_expected.to be_valid } end context 'used with job-level when:delayed' do @@ -2190,7 +2190,7 @@ module Gitlab } end - it_behaves_like 'returns errors', /may not be used with `rules`: when, start_in/ + it_behaves_like 'returns errors', /may not be used with `rules`: start_in/ end end diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb index b89f61b53f8..31be3963565 100644 --- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb @@ -122,7 +122,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_ shared_examples_for 'sleeps when necessary' do context 'when WAL locations are blank', :freeze_time do - let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", "wal_locations" => {}, "created_at" => Time.current.to_f - (described_class::MINIMUM_DELAY_INTERVAL - 0.3) } } + let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", "wal_locations" => {}, "created_at" => Time.current.to_f - (described_class::MINIMUM_DELAY_INTERVAL_SECONDS - 0.3) } } it 'does not sleep' do expect(middleware).not_to receive(:sleep) @@ -135,17 +135,39 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_ let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'wal_locations' => wal_locations, "created_at" => Time.current.to_f - elapsed_time } } context 'when delay interval has not elapsed' do - let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL - 0.3 } + let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL_SECONDS - 0.3 } - it 'sleeps until the minimum delay is reached' do - expect(middleware).to receive(:sleep).with(be_within(0.01).of(described_class::MINIMUM_DELAY_INTERVAL - elapsed_time)) + context 'when replica is up to date' do + before do + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + allow(lb).to receive(:select_up_to_date_host).and_return(true) + end + end - run_middleware + it 'does not sleep' do + expect(middleware).not_to receive(:sleep) + + run_middleware + end + end + + context 'when replica is not up to date' do + before do + Gitlab::Database::LoadBalancing.each_load_balancer do |lb| + allow(lb).to receive(:select_up_to_date_host).and_return(false, true) + end + end + + it 'sleeps until the minimum delay is reached' do + expect(middleware).to receive(:sleep).with(be_within(0.01).of(described_class::MINIMUM_DELAY_INTERVAL_SECONDS - elapsed_time)) + + run_middleware + end end end context 'when delay interval has elapsed' do - let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL + 0.3 } + let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL_SECONDS + 0.3 } it 'does not sleep' do expect(middleware).not_to receive(:sleep) @@ -179,7 +201,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_ context 'when delay interval has not elapsed', :freeze_time do let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'wal_locations' => wal_locations, "created_at" => Time.current.to_f - elapsed_time } } - let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL - 0.3 } + let(:elapsed_time) { described_class::MINIMUM_DELAY_INTERVAL_SECONDS - 0.3 } it 'does not sleep' do expect(middleware).not_to receive(:sleep) diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb index 1ae4c9414dd..59b944ac398 100644 --- a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb +++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_ let(:metric_1) { 'metric_1' } let(:metric_2) { 'metric_2' } let(:metric_names) { [metric_1, metric_2] } + let(:error_rate) { Gitlab::Database::PostgresHll::BatchDistinctCounter::ERROR_RATE } describe 'metric calculations' do before do @@ -38,7 +39,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_ end it 'returns the number of unique events in the union of all metrics' do - expect(calculate_metrics_union.round(2)).to eq(3.12) + expect(calculate_metrics_union.round(2)).to be_within(error_rate).percent_of(3) end context 'when there is no aggregated data saved' do @@ -53,7 +54,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_ let(:metric_names) { [metric_1] } it 'returns the number of unique events for that metric' do - expect(calculate_metrics_union.round(2)).to eq(2.08) + expect(calculate_metrics_union.round(2)).to be_within(error_rate).percent_of(2) end end end @@ -64,7 +65,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_ end it 'returns the number of common events in the intersection of all metrics' do - expect(calculate_metrics_intersections.round(2)).to eq(1.04) + expect(calculate_metrics_intersections.round(2)).to be_within(error_rate).percent_of(1) end context 'when there is no aggregated data saved' do @@ -79,7 +80,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_ let(:metric_names) { [metric_1] } it 'returns the number of common/unique events for the intersection of that metric' do - expect(calculate_metrics_intersections.round(2)).to eq(2.08) + expect(calculate_metrics_intersections.round(2)).to be_within(error_rate).percent_of(2) end end end diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb index f390bba0157..0a32bdb95d3 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric do end.new(time_frame: 'all') end - it 'calculates a correct result', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348139' do + it 'calculates a correct result' do expect(subject.value).to be_within(Gitlab::Database::PostgresHll::BatchDistinctCounter::ERROR_RATE).percent_of(3) end diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb index a88ab9435f2..325ace6fbbf 100644 --- a/spec/lib/gitlab/utils/usage_data_spec.rb +++ b/spec/lib/gitlab/utils/usage_data_spec.rb @@ -118,7 +118,7 @@ RSpec.describe Gitlab::Utils::UsageData do # build_needs set: ['1', '2', '3', '4', '5'] # ci_build set ['a', 'b'] # with them, current implementation is expected to consistently report - # 5.217656147118495 and 2.0809220082170614 values + # the same static values # This test suite is expected to assure, that HyperLogLog implementation # behaves consistently between changes made to other parts of codebase. # In case of fine tuning or changes to HyperLogLog algorithm implementation @@ -130,8 +130,8 @@ RSpec.describe Gitlab::Utils::UsageData do let(:model) { Ci::BuildNeed } let(:column) { :name } - let(:build_needs_estimated_cardinality) { 5.217656147118495 } - let(:ci_builds_estimated_cardinality) { 2.0809220082170614 } + let(:build_needs_estimated_cardinality) { 5.024574181542231 } + let(:ci_builds_estimated_cardinality) { 2.003916452421793 } before do allow(model.connection).to receive(:transaction_open?).and_return(false) diff --git a/spec/models/concerns/transactions_spec.rb b/spec/models/concerns/transactions_spec.rb new file mode 100644 index 00000000000..404a33196e6 --- /dev/null +++ b/spec/models/concerns/transactions_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Transactions do + let(:model) { build(:project) } + + it 'is not in a transaction' do + expect(model.class).not_to be_inside_transaction + end + + it 'is in a transaction', :aggregate_failures do + Project.transaction do + expect(model.class).to be_inside_transaction + end + + ApplicationRecord.transaction do + expect(model.class).to be_inside_transaction + end + end +end diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb index e75725cacba..21b4634ce25 100644 --- a/spec/requests/api/composer_packages_spec.rb +++ b/spec/requests/api/composer_packages_spec.rb @@ -9,6 +9,10 @@ RSpec.describe API::ComposerPackages do let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:package_name) { 'package-name' } let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) } + let_it_be(:deploy_token_for_project) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } + let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token_for_project, project: project) } + let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } let(:headers) { {} } @@ -92,6 +96,8 @@ RSpec.describe API::ComposerPackages do group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) end + it_behaves_like 'Composer access with deploy tokens' + context 'with access to the api' do where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do 'PRIVATE' | :developer | true | true | :include_package @@ -162,6 +168,8 @@ RSpec.describe API::ComposerPackages do it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end + + it_behaves_like 'Composer access with deploy tokens' end it_behaves_like 'rejects Composer access with unknown group id' @@ -219,6 +227,8 @@ RSpec.describe API::ComposerPackages do end end end + + it_behaves_like 'Composer access with deploy tokens' end it_behaves_like 'rejects Composer access with unknown group id' @@ -265,6 +275,8 @@ RSpec.describe API::ComposerPackages do it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end + + it_behaves_like 'Composer access with deploy tokens' end it_behaves_like 'rejects Composer access with unknown group id' @@ -308,6 +320,8 @@ RSpec.describe API::ComposerPackages do it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end + + it_behaves_like 'Composer publish with deploy tokens' end it_behaves_like 'rejects Composer access with unknown project id' diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 3e72342f537..a24841fe286 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -29,7 +29,7 @@ RSpec.describe BuildDetailsEntity do end it 'contains the needed key value pairs' do - expect(subject).to include(:coverage, :erased_at, :duration) + expect(subject).to include(:coverage, :erased_at, :finished_at, :duration) expect(subject).to include(:runner, :pipeline) expect(subject).to include(:raw_path, :new_issue_path) end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index df76ea27d13..ef879d536c3 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1992,6 +1992,75 @@ RSpec.describe Ci::CreatePipelineService do let(:rules_job) { find_job('rules-job') } let(:delayed_job) { find_job('delayed-job') } + context 'with when:manual' do + let(:config) do + <<-EOY + job-with-rules: + script: 'echo hey' + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + + job-when-with-rules: + script: 'echo hey' + when: manual + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + + job-when-with-rules-when: + script: 'echo hey' + when: manual + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: on_success + + job-with-rules-when: + script: 'echo hey' + rules: + - if: $CI_COMMIT_REF_NAME =~ /master/ + when: manual + + job-without-rules: + script: 'echo this is a job with NO rules' + EOY + end + + let(:job_with_rules) { find_job('job-with-rules') } + let(:job_when_with_rules) { find_job('job-when-with-rules') } + let(:job_when_with_rules_when) { find_job('job-when-with-rules-when') } + let(:job_with_rules_when) { find_job('job-with-rules-when') } + let(:job_without_rules) { find_job('job-without-rules') } + + context 'when matching the rules' do + let(:ref_name) { 'refs/heads/master' } + + it 'adds the job-with-rules with a when:manual' do + expect(job_with_rules).to be_persisted + expect(job_when_with_rules).to be_persisted + expect(job_when_with_rules_when).to be_persisted + expect(job_with_rules_when).to be_persisted + expect(job_without_rules).to be_persisted + + expect(job_with_rules.when).to eq('on_success') + expect(job_when_with_rules.when).to eq('manual') + expect(job_when_with_rules_when.when).to eq('on_success') + expect(job_with_rules_when.when).to eq('manual') + expect(job_without_rules.when).to eq('on_success') + end + end + + context 'when there is no match to the rule' do + let(:ref_name) { 'refs/heads/wip' } + + it 'does not add job_with_rules' do + expect(job_with_rules).to be_nil + expect(job_when_with_rules).to be_nil + expect(job_when_with_rules_when).to be_nil + expect(job_with_rules_when).to be_nil + expect(job_without_rules).to be_persisted + end + end + end + shared_examples 'rules jobs are excluded' do it 'only persists the job without rules' do expect(pipeline).to be_persisted diff --git a/spec/services/ci/parse_dotenv_artifact_service_spec.rb b/spec/services/ci/parse_dotenv_artifact_service_spec.rb index ba71ddab40e..6bf22b7c8b2 100644 --- a/spec/services/ci/parse_dotenv_artifact_service_spec.rb +++ b/spec/services/ci/parse_dotenv_artifact_service_spec.rb @@ -37,6 +37,32 @@ RSpec.describe Ci::ParseDotenvArtifactService do end end + context 'when dotenv variables have duplicate variables' do + let!(:artifact) { create(:ci_job_artifact, :dotenv, job: build) } + let(:blob) do + <<~EOS + KEY1=VAR1 + KEY2=VAR2 + KEY2=VAR3 + KEY1=VAR4 + EOS + end + + before do + allow(artifact).to receive(:each_blob).and_yield(blob) + end + + it 'latest values get used' do + subject + + expect(subject[:status]).to eq(:success) + + expect(build.job_variables.as_json).to contain_exactly( + hash_including('key' => 'KEY1', 'value' => 'VAR4'), + hash_including('key' => 'KEY2', 'value' => 'VAR3')) + end + end + context 'when parse error happens' do before do allow(service).to receive(:scan_line!) { raise described_class::ParserError, 'Invalid Format' } diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb index e45be21f152..9f4fdcf7ba1 100644 --- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb @@ -173,3 +173,65 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do end end end + +RSpec.shared_examples 'Composer access with deploy tokens' do + shared_examples 'a deploy token for Composer GET requests' do + context 'with deploy token headers' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } + + before do + group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header(deploy_token.username, 'bar') } + + it_behaves_like 'returning response status', :not_found + end + end + end + + context 'group deploy token' do + let(:deploy_token) { deploy_token_for_group } + + it_behaves_like 'a deploy token for Composer GET requests' + end + + context 'project deploy token' do + let(:deploy_token) { deploy_token_for_project } + + it_behaves_like 'a deploy token for Composer GET requests' + end +end + +RSpec.shared_examples 'Composer publish with deploy tokens' do + shared_examples 'a deploy token for Composer publish requests' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header(deploy_token.username, 'bar') } + + it_behaves_like 'returning response status', :unauthorized + end + end + + context 'group deploy token' do + let(:deploy_token) { deploy_token_for_group } + + it_behaves_like 'a deploy token for Composer publish requests' + end + + context 'group deploy token' do + let(:deploy_token) { deploy_token_for_project } + + it_behaves_like 'a deploy token for Composer publish requests' + end +end |