From ef19a5c55c0f38b29f7ac98119041b054c6579c7 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 18 Jun 2020 06:08:33 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../pipelines/application_controller.rb | 24 ++ app/graphql/types/project_type.rb | 3 +- app/graphql/types/release_assets_type.rb | 3 +- app/graphql/types/release_link_type.rb | 1 + app/graphql/types/release_source_type.rb | 3 +- app/graphql/types/release_type.rb | 12 +- app/models/ci/build.rb | 4 +- app/policies/releases/source_policy.rb | 6 - app/presenters/release_presenter.rb | 14 +- ...dd-intermediate-group-deploy-key-table.yml | 5 + ...22205606_create_group_deploy_keys_group.rb | 27 ++ db/structure.sql | 33 ++ doc/administration/gitaly/praefect.md | 35 +- .../graphql/reference/gitlab_schema.graphql | 16 +- doc/api/graphql/reference/gitlab_schema.json | 20 +- doc/api/graphql/reference/index.md | 12 +- doc/development/api_graphql_styleguide.md | 25 +- doc/install/requirements.md | 6 +- .../secret_detection/index.md | 6 +- .../merge_requests/accessibility_testing.md | 8 +- .../browser_performance_testing.md | 3 + .../project/merge_requests/code_quality.md | 3 + .../merge_requests/fail_fast_testing.md | 74 ++++ .../test_coverage_visualization.md | 3 + .../testing_and_reports_in_merge_requests.md | 3 + lib/api/entities/release.rb | 9 +- .../graphql/types/release_assets_type_spec.rb | 2 +- .../graphql/types/release_source_type_spec.rb | 2 +- spec/graphql/types/release_type_spec.rb | 1 - spec/models/ci/build_spec.rb | 6 +- spec/policies/releases/source_policy_spec.rb | 88 ----- spec/presenters/release_presenter_spec.rb | 32 ++ .../api/graphql/project/release_spec.rb | 341 +++++++++++------- .../api/graphql/project/releases_spec.rb | 152 ++++++++ spec/requests/api/runner_spec.rb | 6 +- spec/simplecov_env.rb | 2 + 36 files changed, 703 insertions(+), 287 deletions(-) create mode 100644 app/controllers/projects/pipelines/application_controller.rb create mode 100644 changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml create mode 100644 db/migrate/20200522205606_create_group_deploy_keys_group.rb create mode 100644 doc/user/project/merge_requests/fail_fast_testing.md delete mode 100644 spec/policies/releases/source_policy_spec.rb create mode 100644 spec/requests/api/graphql/project/releases_spec.rb diff --git a/app/controllers/projects/pipelines/application_controller.rb b/app/controllers/projects/pipelines/application_controller.rb new file mode 100644 index 0000000000000..92887750813cd --- /dev/null +++ b/app/controllers/projects/pipelines/application_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Abstract class encapsulating common logic for creating new controllers in a pipeline context + +module Projects + module Pipelines + class ApplicationController < Projects::ApplicationController + include Gitlab::Utils::StrongMemoize + + before_action :pipeline + before_action :authorize_read_pipeline! + + private + + def pipeline + strong_memoize(:pipeline) do + project.all_pipelines.find(params[:pipeline_id]).tap do |pipeline| + render_404 unless can?(current_user, :read_pipeline, pipeline) + end + end + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index bbfb7fc4f2053..f83faa3639669 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -251,7 +251,8 @@ class ProjectType < BaseObject null: true, description: 'A single release of the project', resolver: Resolvers::ReleasesResolver.single, - feature_flag: :graphql_release_data + feature_flag: :graphql_release_data, + authorize: :download_code field :container_expiration_policy, Types::ContainerExpirationPolicyType, diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb index 58ad05b53653a..45bfe5fe06f46 100644 --- a/app/graphql/types/release_assets_type.rb +++ b/app/graphql/types/release_assets_type.rb @@ -3,6 +3,7 @@ module Types class ReleaseAssetsType < BaseObject graphql_name 'ReleaseAssets' + description 'A container for all assets associated with a release' authorize :read_release @@ -10,7 +11,7 @@ class ReleaseAssetsType < BaseObject present_using ReleasePresenter - field :assets_count, GraphQL::INT_TYPE, null: true, + field :count, GraphQL::INT_TYPE, null: true, method: :assets_count, description: 'Number of assets of the release' field :links, Types::ReleaseLinkType.connection_type, null: true, description: 'Asset links of the release' diff --git a/app/graphql/types/release_link_type.rb b/app/graphql/types/release_link_type.rb index 070f14a90dffe..00fd05f6f7ee3 100644 --- a/app/graphql/types/release_link_type.rb +++ b/app/graphql/types/release_link_type.rb @@ -3,6 +3,7 @@ module Types class ReleaseLinkType < BaseObject graphql_name 'ReleaseLink' + description 'Represents an asset link associated with a release' authorize :read_release diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb index 0ec1ad85a39e1..891da47211656 100644 --- a/app/graphql/types/release_source_type.rb +++ b/app/graphql/types/release_source_type.rb @@ -3,8 +3,9 @@ module Types class ReleaseSourceType < BaseObject graphql_name 'ReleaseSource' + description 'Represents the source code attached to a release in a particular format' - authorize :read_release_sources + authorize :download_code field :format, GraphQL::STRING_TYPE, null: true, description: 'Format of the source' diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index 3d8e5a93c683d..f900b476da9f4 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -3,6 +3,7 @@ module Types class ReleaseType < BaseObject graphql_name 'Release' + description 'Represents a release' authorize :read_release @@ -10,10 +11,12 @@ class ReleaseType < BaseObject present_using ReleasePresenter - field :tag_name, GraphQL::STRING_TYPE, null: false, method: :tag, - description: 'Name of the tag associated with the release' + field :tag_name, GraphQL::STRING_TYPE, null: true, method: :tag, + description: 'Name of the tag associated with the release', + authorize: :download_code field :tag_path, GraphQL::STRING_TYPE, null: true, - description: 'Relative web path to the tag associated with the release' + description: 'Relative web path to the tag associated with the release', + authorize: :download_code field :description, GraphQL::STRING_TYPE, null: true, description: 'Description (also known as "release notes") of the release' markdown_field :description_html, null: true @@ -39,8 +42,7 @@ def author field :commit, Types::CommitType, null: true, complexity: 10, calls_gitaly: true, - description: 'The commit associated with the release', - authorize: :reporter_access + description: 'The commit associated with the release' def commit return if release.sha.nil? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 1bf7e2571142d..3e80456a6f242 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -27,7 +27,7 @@ class Build < Ci::Processable upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }, refspecs: -> (build) { build.merge_request_ref? }, artifacts_exclude: -> (build) { build.supports_artifacts_exclude? }, - release_steps: -> (build) { build.release_steps? } + multi_build_steps: -> (build) { build.multi_build_steps? } }.freeze DEFAULT_RETRIES = { @@ -890,7 +890,7 @@ def supports_artifacts_exclude? Gitlab::Ci::Features.artifacts_exclude_enabled? end - def release_steps? + def multi_build_steps? options.dig(:release)&.any? && Gitlab::Ci::Features.release_generation_enabled? end diff --git a/app/policies/releases/source_policy.rb b/app/policies/releases/source_policy.rb index 8b86b9255899f..3b11c66123743 100644 --- a/app/policies/releases/source_policy.rb +++ b/app/policies/releases/source_policy.rb @@ -3,11 +3,5 @@ module Releases class SourcePolicy < BasePolicy delegate { @subject.project } - - rule { can?(:public_access) | can?(:reporter_access) }.policy do - enable :read_release_sources - end - - rule { ~can?(:read_release) }.prevent :read_release_sources end end diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb index 7b0a3d1e7b9d1..4393ca05f48b1 100644 --- a/app/presenters/release_presenter.rb +++ b/app/presenters/release_presenter.rb @@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated presents :release - delegate :project, :tag, :assets_count, to: :release + delegate :project, :tag, to: :release def commit_path return unless release.commit && can_download_code? @@ -43,6 +43,18 @@ def edit_url edit_project_release_url(project, release) end + def assets_count + if can_download_code? + release.assets_count + else + release.assets_count(except: [:sources]) + end + end + + def name + can_download_code? ? release.name : "Release-#{release.id}" + end + private def can_download_code? diff --git a/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml b/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml new file mode 100644 index 0000000000000..4adfa614830c1 --- /dev/null +++ b/changelogs/unreleased/215160-add-intermediate-group-deploy-key-table.yml @@ -0,0 +1,5 @@ +--- +title: Create group_deploy_keys_groups intermediate table +merge_request: 32901 +author: +type: added diff --git a/db/migrate/20200522205606_create_group_deploy_keys_group.rb b/db/migrate/20200522205606_create_group_deploy_keys_group.rb new file mode 100644 index 0000000000000..acc72634c4c8b --- /dev/null +++ b/db/migrate/20200522205606_create_group_deploy_keys_group.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class CreateGroupDeployKeysGroup < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + DOWNTIME = false + + def up + with_lock_retries do + create_table :group_deploy_keys_groups do |t| + t.timestamps_with_timezone + + t.references :group, index: false, null: false, foreign_key: { to_table: :namespaces, on_delete: :cascade } + t.references :group_deploy_key, null: false, foreign_key: { on_delete: :cascade } + + t.index [:group_id, :group_deploy_key_id], unique: true, name: 'index_group_deploy_keys_group_on_group_deploy_key_and_group_ids' + end + end + end + + def down + with_lock_retries do + # rubocop:disable Migration/DropTable + drop_table :group_deploy_keys_groups + # rubocop:enable Migration/DropTable + end + end +end diff --git a/db/structure.sql b/db/structure.sql index d18af03fe025f..d0f6496724e04 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3208,6 +3208,23 @@ CREATE TABLE public.group_deploy_keys ( CONSTRAINT check_f58fa0a0f7 CHECK ((char_length(key) <= 4096)) ); +CREATE TABLE public.group_deploy_keys_groups ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + group_id bigint NOT NULL, + group_deploy_key_id bigint NOT NULL +); + +CREATE SEQUENCE public.group_deploy_keys_groups_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE public.group_deploy_keys_groups_id_seq OWNED BY public.group_deploy_keys_groups.id; + CREATE SEQUENCE public.group_deploy_keys_id_seq START WITH 1 INCREMENT BY 1 @@ -7759,6 +7776,8 @@ ALTER TABLE ONLY public.group_custom_attributes ALTER COLUMN id SET DEFAULT next ALTER TABLE ONLY public.group_deploy_keys ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_keys_id_seq'::regclass); +ALTER TABLE ONLY public.group_deploy_keys_groups ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_keys_groups_id_seq'::regclass); + ALTER TABLE ONLY public.group_deploy_tokens ALTER COLUMN id SET DEFAULT nextval('public.group_deploy_tokens_id_seq'::regclass); ALTER TABLE ONLY public.group_group_links ALTER COLUMN id SET DEFAULT nextval('public.group_group_links_id_seq'::regclass); @@ -8569,6 +8588,9 @@ ALTER TABLE ONLY public.group_custom_attributes ALTER TABLE ONLY public.group_deletion_schedules ADD CONSTRAINT group_deletion_schedules_pkey PRIMARY KEY (group_id); +ALTER TABLE ONLY public.group_deploy_keys_groups + ADD CONSTRAINT group_deploy_keys_groups_pkey PRIMARY KEY (id); + ALTER TABLE ONLY public.group_deploy_keys ADD CONSTRAINT group_deploy_keys_pkey PRIMARY KEY (id); @@ -10032,6 +10054,10 @@ CREATE INDEX index_group_deletion_schedules_on_marked_for_deletion_on ON public. CREATE INDEX index_group_deletion_schedules_on_user_id ON public.group_deletion_schedules USING btree (user_id); +CREATE UNIQUE INDEX index_group_deploy_keys_group_on_group_deploy_key_and_group_ids ON public.group_deploy_keys_groups USING btree (group_id, group_deploy_key_id); + +CREATE INDEX index_group_deploy_keys_groups_on_group_deploy_key_id ON public.group_deploy_keys_groups USING btree (group_deploy_key_id); + CREATE UNIQUE INDEX index_group_deploy_keys_on_fingerprint ON public.group_deploy_keys USING btree (fingerprint); CREATE INDEX index_group_deploy_keys_on_fingerprint_sha256 ON public.group_deploy_keys USING btree (fingerprint_sha256); @@ -12746,6 +12772,9 @@ ALTER TABLE ONLY public.project_repositories ALTER TABLE ONLY public.packages_nuget_dependency_link_metadata ADD CONSTRAINT fk_rails_c3313ee2e4 FOREIGN KEY (dependency_link_id) REFERENCES public.packages_dependency_links(id) ON DELETE CASCADE; +ALTER TABLE ONLY public.group_deploy_keys_groups + ADD CONSTRAINT fk_rails_c3854f19f5 FOREIGN KEY (group_deploy_key_id) REFERENCES public.group_deploy_keys(id) ON DELETE CASCADE; + ALTER TABLE ONLY public.merge_request_user_mentions ADD CONSTRAINT fk_rails_c440b9ea31 FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE; @@ -12884,6 +12913,9 @@ ALTER TABLE ONLY public.merge_request_metrics ALTER TABLE ONLY public.draft_notes ADD CONSTRAINT fk_rails_e753681674 FOREIGN KEY (merge_request_id) REFERENCES public.merge_requests(id) ON DELETE CASCADE; +ALTER TABLE ONLY public.group_deploy_keys_groups + ADD CONSTRAINT fk_rails_e87145115d FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY public.description_versions ADD CONSTRAINT fk_rails_e8f4caf9c7 FOREIGN KEY (epic_id) REFERENCES public.epics(id) ON DELETE CASCADE; @@ -13987,6 +14019,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200521225327 20200521225337 20200521225346 +20200522205606 20200522235146 20200525114553 20200525121014 diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index cab84ceb00cff..24e07982f1e77 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -739,9 +739,17 @@ current primary node is found to be unhealthy. It is likely that we will implement support for Consul, and a cloud native strategy in the future. -## Identifying Impact of a Primary Node Failure +## Primary Node Failure -When a primary Gitaly node fails, there is a chance of data loss. Data loss can occur if there were outstanding replication jobs the secondaries did not manage to process before the failure. The `dataloss` Praefect sub-command helps identify these cases by counting the number of dead replication jobs for each repository. This command must be executed on a Praefect node. +Praefect recovers from a failing primary Gitaly node by promoting a healthy secondary as the new primary. To minimize data loss, Praefect elects the secondary with the least unreplicated writes from the primary. There can still be some unreplicated writes, leading to data loss. + +Praefect switches a virtual storage in to read-only mode after a failover event. This eases data recovery efforts by preventing new, possibly conflicting writes to the newly elected primary. This allows the administrator to attempt recovering the lost data before allowing new writes. + +If you prefer write availability over consistency, this behavior can be turned off by setting `praefect['failover_read_only_after_failover'] = false` in `/etc/gitlab/gitlab.rb` and [reconfiguring Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure). + +### Checking for data loss + +The Praefect `dataloss` sub-command helps identify lost writes by counting the number of dead replication jobs for each repository within a given time frame. This command must be executed on a Praefect node. A time frame to search can be specified with `-from` and `-to`: @@ -769,6 +777,29 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t To check a project's repository checksums across on all Gitaly nodes, run the [replicas Rake task](../raketasks/praefect.md#replica-checksums) on the main GitLab node. +### Recovering lost writes + +The Praefect `reconcile` sub-command can be used to recover lost writes from the +previous primary once it is back online. This is only possible when the virtual storage +is still in read-only mode. + +```shell +sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml reconcile -virtual -reference -target -f +``` + +Refer to [Backend Node Recovery](#backend-node-recovery) section for more details on +the `reconcile` sub-command. + +### Enabling Writes + +Any data recovery attempts should have been made before enabling writes to eliminate +any chance of conflicting writes. Virtual storage can be re-enabled for writes by using +the Praefect `enable-writes` sub-command. + +```shell +sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage +``` + ## Backend Node Recovery When a Praefect backend node fails and is no longer able to diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 49b223c46391a..52698c0532f77 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -10102,6 +10102,9 @@ enum RegistryState { SYNCED } +""" +Represents a release +""" type Release { """ Assets of the release @@ -10196,7 +10199,7 @@ type Release { """ Name of the tag associated with the release """ - tagName: String! + tagName: String """ Relative web path to the tag associated with the release @@ -10204,11 +10207,14 @@ type Release { tagPath: String } +""" +A container for all assets associated with a release +""" type ReleaseAssets { """ Number of assets of the release """ - assetsCount: Int + count: Int """ Asset links of the release @@ -10356,6 +10362,9 @@ type ReleaseEvidenceEdge { node: ReleaseEvidence } +""" +Represents an asset link associated with a release +""" type ReleaseLink { """ Indicates the link points to an external resource @@ -10443,6 +10452,9 @@ enum ReleaseLinkType { RUNBOOK } +""" +Represents the source code attached to a release in a particular format +""" type ReleaseSource { """ Format of the source diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 170b11624cb35..8292a698ff6fe 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -29588,7 +29588,7 @@ { "kind": "OBJECT", "name": "Release", - "description": null, + "description": "Represents a release", "fields": [ { "name": "assets", @@ -29815,13 +29815,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -29851,10 +29847,10 @@ { "kind": "OBJECT", "name": "ReleaseAssets", - "description": null, + "description": "A container for all assets associated with a release", "fields": [ { - "name": "assetsCount", + "name": "count", "description": "Number of assets of the release", "args": [ @@ -30281,7 +30277,7 @@ { "kind": "OBJECT", "name": "ReleaseLink", - "description": null, + "description": "Represents an asset link associated with a release", "fields": [ { "name": "external", @@ -30515,7 +30511,7 @@ { "kind": "OBJECT", "name": "ReleaseSource", - "description": null, + "description": "Represents the source code attached to a release in a particular format", "fields": [ { "name": "format", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 791e67c4757d8..a8e033d3420b9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1400,6 +1400,8 @@ Represents a Project Member ## Release +Represents a release + | Name | Type | Description | | --- | ---- | ---------- | | `assets` | ReleaseAssets | Assets of the release | @@ -1410,14 +1412,16 @@ Represents a Project Member | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `name` | String | Name of the release | | `releasedAt` | Time | Timestamp of when the release was released | -| `tagName` | String! | Name of the tag associated with the release | +| `tagName` | String | Name of the tag associated with the release | | `tagPath` | String | Relative web path to the tag associated with the release | ## ReleaseAssets +A container for all assets associated with a release + | Name | Type | Description | | --- | ---- | ---------- | -| `assetsCount` | Int | Number of assets of the release | +| `count` | Int | Number of assets of the release | ## ReleaseEvidence @@ -1432,6 +1436,8 @@ Evidence for a release ## ReleaseLink +Represents an asset link associated with a release + | Name | Type | Description | | --- | ---- | ---------- | | `external` | Boolean | Indicates the link points to an external resource | @@ -1442,6 +1448,8 @@ Evidence for a release ## ReleaseSource +Represents the source code attached to a release in a particular format + | Name | Type | Description | | --- | ---- | ---------- | | `format` | String | Format of the source | diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index a6d25d78dc9b7..0fb8ed8e2c49a 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -717,8 +717,7 @@ will be returned as the result of the mutation. ### Naming conventions -Each mutation must define a `graphql_name`, which is the name of the -mutation in the GraphQL schema. +Each mutation must define a `graphql_name`, which is the name of the mutation in the GraphQL schema. Example: @@ -728,9 +727,8 @@ class UserUpdateMutation < BaseMutation end ``` -Our GraphQL mutation names are historically inconsistent, but new -mutation names should follow the convention `'{Resource}{Action}'` -or `'{Resource}{Action}{Attribute}'`. +Our GraphQL mutation names are historically inconsistent, but new mutation names should follow the +convention `'{Resource}{Action}'` or `'{Resource}{Action}{Attribute}'`. Mutations that **create** new resources should use the verb `Create`. @@ -738,9 +736,10 @@ Example: - `CommitCreate` -Mutations that **update** data should use the verb `Update` or a -domain-specific verb like `Set`, `Add`, or `Toggle` if more -appropriate. +Mutations that **update** data should use: + +- The verb `Update`. +- A domain-specific verb like `Set`, `Add`, or `Toggle` if more appropriate. Examples: @@ -749,17 +748,17 @@ Examples: - `IssueUpdate` - `TodoMarkDone` -Mutations that **remove** data should use the verb `Delete` rather than -`Destroy`. Or use a domain-specific verb like `Remove` if more -appropriate. +Mutations that **remove** data should use: + +- The verb `Delete` rather than `Destroy`. +- A domain-specific verb like `Remove` if more appropriate. Examples: - `AwardEmojiRemove` - `NoteDelete` -If you need advice for mutation naming, canvass the Slack `#graphql` -channel for feedback. +If you need advice for mutation naming, canvass the Slack `#graphql` channel for feedback. ### Arguments diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 0673f3e7ea3e7..69680755b6820 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -54,8 +54,10 @@ The minimum required Go version is 1.13. ### Git versions -GitLab 11.11 and higher only supports Git 2.24.x and newer, and -[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/54255). +From GitLab 13.1: + +- Git 2.25.x and later is required. +- Git 2.27.x and later [is recommended](https://gitlab.com/gitlab-org/gitaly/-/issues/2829). ### Node.js versions diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 85933c31a340c..c9a3741faa36f 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -118,15 +118,15 @@ declare a job with the same name as the SAST job to override. Place this new job inclusion and specify any additional keys under it. In the following example, we include the Secret Detection template and at the same time we -override the `secret-scan` job with the `SECRET_DETECTION_HISTORIC_SCAN` variable to `true`: +override the `secret_detection` job with the `SECRET_DETECTION_HISTORIC_SCAN` variable to `true`: ```yaml include: - template: Secret-Detection.gitlab-ci.yml -secrets-scan: +secret_detection: variables: - SECRET_DETECTION_HISTORIC_SCAN: true + SECRET_DETECTION_HISTORIC_SCAN: "true" ``` Because the template is [evaluated before](../../../ci/yaml/README.md#include) diff --git a/doc/user/project/merge_requests/accessibility_testing.md b/doc/user/project/merge_requests/accessibility_testing.md index 417b85a6082a2..f3a0aac9ff4d2 100644 --- a/doc/user/project/merge_requests/accessibility_testing.md +++ b/doc/user/project/merge_requests/accessibility_testing.md @@ -1,4 +1,7 @@ --- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers type: reference, howto --- @@ -20,7 +23,10 @@ analyzed to a file called `accessibility`. ## Accessibility Merge Request widget -[Since GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/39425), in addition to the report artifact that is created, GitLab will also show the +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39425) in GitLab 13.0 behind the disabled [feature flag](../../../administration/feature_flags.md) `:accessibility_report_view`. +> - [Feature Flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/217372) in GitLab 13.1. + +In addition to the report artifact that is created, GitLab will also show the Accessibility Report in the merge request widget area: ![Accessibility Merge Request Widget](img/accessibility_mr_widget_v13_0.png) diff --git a/doc/user/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md index 390d480724d43..75103dd208eb1 100644 --- a/doc/user/project/merge_requests/browser_performance_testing.md +++ b/doc/user/project/merge_requests/browser_performance_testing.md @@ -1,4 +1,7 @@ --- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers type: reference, howto --- diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md index 7b4d4b668d5e6..c9abc30998c21 100644 --- a/doc/user/project/merge_requests/code_quality.md +++ b/doc/user/project/merge_requests/code_quality.md @@ -1,4 +1,7 @@ --- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers type: reference, howto --- diff --git a/doc/user/project/merge_requests/fail_fast_testing.md b/doc/user/project/merge_requests/fail_fast_testing.md new file mode 100644 index 0000000000000..ddd97059a6a14 --- /dev/null +++ b/doc/user/project/merge_requests/fail_fast_testing.md @@ -0,0 +1,74 @@ +--- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +type: reference, howto +--- + +# Fail Fast Testing **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198550) in GitLab 13.1. + +For applications that use RSpec for running tests, we've introduced the `Verify/Failfast` +[template to run subsets of your test suite](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/Verify/FailFast.gitlab-ci.yml), +based on the changes in your merge request. + +The template uses the [test_file_finder (`tff`) gem](https://gitlab.com/gitlab-org/ci-cd/test_file_finder/) +that accepts a list of files as input, and returns a list of spec (test) files +that it believes to be relevant to the input files. + +`tff` is designed for Ruby on Rails projects, so the `Verify/FailFast` template is +configured to run when changes to Ruby files are detected. By default, it runs in +the [`.pre` stage](../../../ci/yaml/README.md#pre-and-post) of a GitLab CI/CD pipeline, +before all other stages. + +## Requirements + +This template requires: + +- A project built in Rails that uses RSpec for testing. +- CI/CD configured to: + - Use a Docker image with Ruby available. + - Use [Pipelines for Merge Requests](../../../ci/merge_request_pipelines/index.md#configuring-pipelines-for-merge-requests) +- [Pipelines for Merged Results](../../../ci/merge_request_pipelines/pipelines_for_merged_results/index.md#enable-pipelines-for-merged-results) + enabled in the project settings. + +## Configure Fast RSpec Failure + +We'll use the following plain RSpec configuration as a starting point. It installs all the +project gems and executes `rspec`, on merge request pipelines only. + +```yaml +rspec-complete: + stage: test + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + script: + - bundle install + - bundle exec rspec +``` + +To run the most relevant specs first instead of the whole suite, [`include`](../../../ci/yaml/README.md#include) +the template by adding the following to your CI/CD configuration: + +```yaml +include: + - template: Verify/FailFast.gitlab-ci.yml +``` + +### Example test loads + +For illustrative purposes, let's say our Rails app spec suite consists of 100 specs per model for ten models. + +If no Ruby files are changed: + +- `rspec-rails-modified-paths-specs` will not run any tests. +- `rspec-complete` will run the full suite of 1000 tests. + +If one Ruby model is changed, for example `app/models/example.rb`, then `rspec-rails-modified-paths-specs` +will run the 100 tests for `example.rb`: + +- If all of these 100 tests pass, then the full `rspec-complete` suite of 1000 tests is allowed to run. +- If any of these 100 tests fail, they will fail quickly, and `rspec-complete` will not run any tests. + +The final case saves resources and time as the full 1000 test suite does not run. diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md index 1c0e698aba55a..12194423a0051 100644 --- a/doc/user/project/merge_requests/test_coverage_visualization.md +++ b/doc/user/project/merge_requests/test_coverage_visualization.md @@ -1,4 +1,7 @@ --- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers type: reference, howto --- diff --git a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md index f7614ed7996ed..5e84e37e2258f 100644 --- a/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md +++ b/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md @@ -1,4 +1,7 @@ --- +stage: Verify +group: Testing +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers type: index description: "Test your code and display reports in merge requests" --- diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index 99fa496d36866..afe14cf33cfe4 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -5,9 +5,7 @@ module Entities class Release < Grape::Entity include ::API::Helpers::Presentable - expose :name do |release, _| - can_download_code? ? release.name : "Release-#{release.id}" - end + expose :name expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? } expose :description expose :description_html do |entity| @@ -23,10 +21,7 @@ class Release < Grape::Entity expose :tag_path, expose_nil: false expose :assets do - expose :assets_count, as: :count do |release, _| - assets_to_exclude = can_download_code? ? [] : [:sources] - release.assets_count(except: assets_to_exclude) - end + expose :assets_count, as: :count expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? } expose :links, using: Entities::Releases::Link do |release, options| release.links.sorted diff --git a/spec/graphql/types/release_assets_type_spec.rb b/spec/graphql/types/release_assets_type_spec.rb index 58f0f7ee69727..d8db162db62af 100644 --- a/spec/graphql/types/release_assets_type_spec.rb +++ b/spec/graphql/types/release_assets_type_spec.rb @@ -7,7 +7,7 @@ it 'has the expected fields' do expected_fields = %w[ - assets_count links sources + count links sources ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/release_source_type_spec.rb b/spec/graphql/types/release_source_type_spec.rb index e471ac1a5acb5..929c6339479d9 100644 --- a/spec/graphql/types/release_source_type_spec.rb +++ b/spec/graphql/types/release_source_type_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe GitlabSchema.types['ReleaseSource'] do - it { expect(described_class).to require_graphql_authorizations(:read_release_sources) } + it { expect(described_class).to require_graphql_authorizations(:download_code) } it 'has the expected fields' do expected_fields = %w[ diff --git a/spec/graphql/types/release_type_spec.rb b/spec/graphql/types/release_type_spec.rb index feafe5ed5195f..11eab7127c1d4 100644 --- a/spec/graphql/types/release_type_spec.rb +++ b/spec/graphql/types/release_type_spec.rb @@ -44,6 +44,5 @@ subject { described_class.fields['commit'] } it { is_expected.to have_graphql_type(Types::CommitType) } - it { is_expected.to require_graphql_authorizations(:reporter_access) } end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 4caf80eaf7dce..a646f08ffb4be 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -4302,15 +4302,15 @@ def run_job_without_exception end end - context 'when `release_steps` feature is required by build' do + context 'when `multi_build_steps` feature is required by build' do before do expect(build).to receive(:runner_required_feature_names) do - [:release_steps] + [:multi_build_steps] end end context 'when runner provides given feature' do - let(:runner_features) { { release_steps: true } } + let(:runner_features) { { multi_build_steps: true } } it { is_expected.to be_truthy } end diff --git a/spec/policies/releases/source_policy_spec.rb b/spec/policies/releases/source_policy_spec.rb deleted file mode 100644 index 1bc6d5415d306..0000000000000 --- a/spec/policies/releases/source_policy_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Releases::SourcePolicy do - using RSpec::Parameterized::TableSyntax - - let(:policy) { described_class.new(user, source) } - - let_it_be(:public_user) { create(:user) } - let_it_be(:guest) { create(:user) } - let_it_be(:reporter) { create(:user) } - - let(:release) { create(:release, project: project) } - let(:source) { release.sources.first } - - shared_examples 'source code access' do - it "allows access a release's source code" do - expect(policy).to be_allowed(:read_release_sources) - end - end - - shared_examples 'no source code access' do - it "does not allow access a release's source code" do - expect(policy).to be_disallowed(:read_release_sources) - end - end - - context 'a private project' do - let_it_be(:project) { create(:project, :private) } - - context 'accessed by a public user' do - let(:user) { public_user } - - it_behaves_like 'no source code access' - end - - context 'accessed by a user with Guest permissions' do - let(:user) { guest } - - before do - project.add_guest(user) - end - - it_behaves_like 'no source code access' - end - - context 'accessed by a user with Reporter permissions' do - let(:user) { reporter } - - before do - project.add_reporter(user) - end - - it_behaves_like 'source code access' - end - end - - context 'a public project' do - let_it_be(:project) { create(:project, :public) } - - context 'accessed by a public user' do - let(:user) { public_user } - - it_behaves_like 'source code access' - end - - context 'accessed by a user with Guest permissions' do - let(:user) { guest } - - before do - project.add_guest(user) - end - - it_behaves_like 'source code access' - end - - context 'accessed by a user with Reporter permissions' do - let(:user) { reporter } - - before do - project.add_reporter(user) - end - - it_behaves_like 'source code access' - end - end -end diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb index d1f023b87601a..57de99f6a6193 100644 --- a/spec/presenters/release_presenter_spec.rb +++ b/spec/presenters/release_presenter_spec.rb @@ -112,4 +112,36 @@ it { is_expected.to be_nil } end end + + describe '#assets_count' do + subject { presenter.assets_count } + + it 'returns the number of assets associated to the release' do + is_expected.to be release.assets_count + end + + context 'when a user is not allowed to download release sources' do + let(:presenter) { described_class.new(release, current_user: guest) } + + it 'returns the number of all non-source assets associated to the release' do + is_expected.to be release.assets_count(except: [:sources]) + end + end + end + + describe '#name' do + subject { presenter.name } + + it 'returns the release name' do + is_expected.to eq release.name + end + + context "when a user is not allowed to access any repository information" do + let(:presenter) { described_class.new(release, current_user: guest) } + + it 'returns a replacement name to avoid potentially leaking tag information' do + is_expected.to eq "Release-#{release.id}" + end + end + end end diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index f8624a97a2bad..e377f32ad10b4 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -7,15 +7,10 @@ include GraphqlHelpers include Presentable - let_it_be(:project) { create(:project, :repository) } - let_it_be(:milestone_1) { create(:milestone, project: project) } - let_it_be(:milestone_2) { create(:milestone, project: project) } - let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) } - let_it_be(:release_link_1) { create(:release_link, release: release) } - let_it_be(:release_link_2) { create(:release_link, release: release) } let_it_be(:developer) { create(:user) } - - let(:current_user) { developer } + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:stranger) { create(:user) } def query(rq = release_fields) graphql_query_for(:project, { fullPath: project.full_path }, @@ -27,179 +22,267 @@ def query(rq = release_fields) let(:data) { graphql_data.dig(*path) } - before do - project.add_developer(developer) - end + shared_examples 'full access to the release field' do + describe 'scalar fields' do + let(:path) { path_prefix } - describe 'scalar fields' do - let(:path) { path_prefix } - let(:release_fields) do - query_graphql_field(%{ - tagName - tagPath - description - descriptionHtml - name - createdAt - releasedAt - }) - end + let(:release_fields) do + query_graphql_field(%{ + tagName + tagPath + description + descriptionHtml + name + createdAt + releasedAt + }) + end - before do - post_query - end + before do + post_query + end - it 'finds all release data' do - expect(data).to eq({ - 'tagName' => release.tag, - 'tagPath' => project_tag_path(project, release.tag), - 'description' => release.description, - 'descriptionHtml' => release.description_html, - 'name' => release.name, - 'createdAt' => release.created_at.iso8601, - 'releasedAt' => release.released_at.iso8601 - }) + it 'finds all release data' do + expect(data).to eq({ + 'tagName' => release.tag, + 'tagPath' => project_tag_path(project, release.tag), + 'description' => release.description, + 'descriptionHtml' => release.description_html, + 'name' => release.name, + 'createdAt' => release.created_at.iso8601, + 'releasedAt' => release.released_at.iso8601 + }) + end end - end - describe 'milestones' do - let(:path) { path_prefix + %w[milestones nodes] } - let(:release_fields) do - query_graphql_field(:milestones, nil, 'nodes { id title }') + describe 'milestones' do + let(:path) { path_prefix + %w[milestones nodes] } + + let(:release_fields) do + query_graphql_field(:milestones, nil, 'nodes { id title }') + end + + it 'finds all milestones associated to a release' do + post_query + + expected = release.milestones.map do |milestone| + { 'id' => global_id_of(milestone), 'title' => milestone.title } + end + + expect(data).to match_array(expected) + end end - it 'finds all milestones associated to a release' do - post_query + describe 'author' do + let(:path) { path_prefix + %w[author] } - expected = release.milestones.map do |milestone| - { 'id' => global_id_of(milestone), 'title' => milestone.title } + let(:release_fields) do + query_graphql_field(:author, nil, 'id username') end - expect(data).to match_array(expected) - end - end + it 'finds the author of the release' do + post_query - describe 'author' do - let(:path) { path_prefix + %w[author] } - let(:release_fields) do - query_graphql_field(:author, nil, 'id username') + expect(data).to eq({ + 'id' => global_id_of(release.author), + 'username' => release.author.username + }) + end end - it 'finds the author of the release' do - post_query + describe 'commit' do + let(:path) { path_prefix + %w[commit] } - expect(data).to eq({ - 'id' => global_id_of(release.author), - 'username' => release.author.username - }) - end - end + let(:release_fields) do + query_graphql_field(:commit, nil, 'sha') + end + + it 'finds the commit associated with the release' do + post_query - describe 'commit' do - let(:path) { path_prefix + %w[commit] } - let(:release_fields) do - query_graphql_field(:commit, nil, 'sha') + expect(data).to eq({ 'sha' => release.commit.sha }) + end end - it 'finds the commit associated with the release' do - post_query + describe 'assets' do + describe 'count' do + let(:path) { path_prefix + %w[assets] } - expect(data).to eq({ 'sha' => release.commit.sha }) - end - end + let(:release_fields) do + query_graphql_field(:assets, nil, 'count') + end - describe 'assets' do - describe 'assetsCount' do - let(:path) { path_prefix + %w[assets] } - let(:release_fields) do - query_graphql_field(:assets, nil, 'assetsCount') + it 'returns the number of assets associated to the release' do + post_query + + expect(data).to eq({ 'count' => release.sources.size + release.links.size }) + end end - it 'returns the number of assets associated to the release' do - post_query + describe 'links' do + let(:path) { path_prefix + %w[assets links nodes] } + + let(:release_fields) do + query_graphql_field(:assets, nil, + query_graphql_field(:links, nil, 'nodes { id name url external }')) + end - expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size }) + it 'finds all release links' do + post_query + + expected = release.links.map do |link| + { + 'id' => global_id_of(link), + 'name' => link.name, + 'url' => link.url, + 'external' => link.external? + } + end + + expect(data).to match_array(expected) + end + end + + describe 'sources' do + let(:path) { path_prefix + %w[assets sources nodes] } + + let(:release_fields) do + query_graphql_field(:assets, nil, + query_graphql_field(:sources, nil, 'nodes { format url }')) + end + + it 'finds all release sources' do + post_query + + expected = release.sources.map do |source| + { + 'format' => source.format, + 'url' => source.url + } + end + + expect(data).to match_array(expected) + end end end - describe 'links' do - let(:path) { path_prefix + %w[assets links nodes] } + describe 'evidences' do + let(:path) { path_prefix + %w[evidences] } + let(:release_fields) do - query_graphql_field(:assets, nil, - query_graphql_field(:links, nil, 'nodes { id name url external }')) + query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }') end - it 'finds all release links' do + it 'finds all evidence fields' do post_query - expected = release.links.map do |link| - { - 'id' => global_id_of(link), - 'name' => link.name, - 'url' => link.url, - 'external' => link.external? - } - end + evidence = release.evidences.first.present + expected = { + 'id' => global_id_of(evidence), + 'sha' => evidence.sha, + 'filepath' => evidence.filepath, + 'collectedAt' => evidence.collected_at.utc.iso8601 + } - expect(data).to match_array(expected) + expect(data["nodes"].first).to eq(expected) end end + end + + shared_examples 'no access to the release field' do + describe 'repository-related fields' do + let(:path) { path_prefix } - describe 'sources' do - let(:path) { path_prefix + %w[assets sources nodes] } let(:release_fields) do - query_graphql_field(:assets, nil, - query_graphql_field(:sources, nil, 'nodes { format url }')) + query_graphql_field('description') end - it 'finds all release sources' do + before do post_query + end - expected = release.sources.map do |source| - { - 'format' => source.format, - 'url' => source.url - } - end + it 'returns nil' do + expect(data).to eq(nil) + end + end + end - expect(data).to match_array(expected) + describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do + context 'when the project is private' do + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:milestone_1) { create(:milestone, project: project) } + let_it_be(:milestone_2) { create(:milestone, project: project) } + let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) } + let_it_be(:release_link_1) { create(:release_link, release: release) } + let_it_be(:release_link_2) { create(:release_link, release: release) } + + before_all do + project.add_developer(developer) + project.add_guest(guest) + project.add_reporter(reporter) + end + + context 'when the user is not logged in' do + let(:current_user) { stranger } + + it_behaves_like 'no access to the release field' + end + + context 'when the user has Guest permissions' do + let(:current_user) { guest } + + it_behaves_like 'no access to the release field' + end + + context 'when the user has Reporter permissions' do + let(:current_user) { reporter } + + it_behaves_like 'full access to the release field' + end + + context 'when the user has Developer permissions' do + let(:current_user) { developer } + + it_behaves_like 'full access to the release field' end end - describe 'evidences' do - let(:path) { path_prefix + %w[evidences] } - let(:release_fields) do - query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }') + context 'when the project is public' do + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:milestone_1) { create(:milestone, project: project) } + let_it_be(:milestone_2) { create(:milestone, project: project) } + let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) } + let_it_be(:release_link_1) { create(:release_link, release: release) } + let_it_be(:release_link_2) { create(:release_link, release: release) } + + before_all do + project.add_developer(developer) + project.add_guest(guest) + project.add_reporter(reporter) end - context 'for a developer' do - it 'finds all evidence fields' do - post_query + context 'when the user is not logged in' do + let(:current_user) { stranger } - evidence = release.evidences.first.present - expected = { - 'id' => global_id_of(evidence), - 'sha' => evidence.sha, - 'filepath' => evidence.filepath, - 'collectedAt' => evidence.collected_at.utc.iso8601 - } + it_behaves_like 'full access to the release field' + end - expect(data["nodes"].first).to eq(expected) - end + context 'when the user has Guest permissions' do + let(:current_user) { guest } + + it_behaves_like 'full access to the release field' end - context 'for a guest' do - let(:current_user) { create :user } + context 'when the user has Reporter permissions' do + let(:current_user) { reporter } - before do - project.add_guest(current_user) - end + it_behaves_like 'full access to the release field' + end - it 'denies access' do - post_query + context 'when the user has Developer permissions' do + let(:current_user) { developer } - expect(data['node']).to be_nil - end + it_behaves_like 'full access to the release field' end end end diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb new file mode 100644 index 0000000000000..4254eb36376de --- /dev/null +++ b/spec/requests/api/graphql/project/releases_spec.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Query.project(fullPath).releases()' do + include GraphqlHelpers + + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:stranger) { create(:user) } + + let(:query) do + graphql_query_for(:project, { fullPath: project.full_path }, + %{ + releases { + nodes { + tagName + tagPath + name + commit { + sha + } + assets { + count + sources { + nodes { + url + } + } + } + evidences { + nodes { + sha + } + } + } + } + }) + end + + let(:post_query) { post_graphql(query, current_user: current_user) } + + let(:data) { graphql_data.dig('project', 'releases', 'nodes', 0) } + + shared_examples 'full access to all repository-related fields' do + describe 'repository-related fields' do + before do + post_query + end + + it 'returns data for fields that are protected in private projects' do + expected_sources = release.sources.map do |s| + { 'url' => s.url } + end + + expected_evidences = release.evidences.map do |e| + { 'sha' => e.sha } + end + + expect(data).to eq({ + 'tagName' => release.tag, + 'tagPath' => project_tag_path(project, release.tag), + 'name' => release.name, + 'commit' => { + 'sha' => release.commit.sha + }, + 'assets' => { + 'count' => release.assets_count, + 'sources' => { + 'nodes' => expected_sources + } + }, + 'evidences' => { + 'nodes' => expected_evidences + } + }) + end + end + end + + shared_examples 'no access to any repository-related fields' do + describe 'repository-related fields' do + before do + post_query + end + + it 'does not return data for fields that expose repository information' do + expect(data).to eq({ + 'tagName' => nil, + 'tagPath' => nil, + 'name' => "Release-#{release.id}", + 'commit' => nil, + 'assets' => { + 'count' => release.assets_count(except: [:sources]), + 'sources' => { + 'nodes' => [] + } + }, + 'evidences' => { + 'nodes' => [] + } + }) + end + end + end + + describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do + context 'when the project is private' do + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:release) { create(:release, :with_evidence, project: project) } + + before_all do + project.add_guest(guest) + project.add_reporter(reporter) + end + + context 'when the user has Guest permissions' do + let(:current_user) { guest } + + it_behaves_like 'no access to any repository-related fields' + end + + context 'when the user has Reporter permissions' do + let(:current_user) { reporter } + + it_behaves_like 'full access to all repository-related fields' + end + end + + context 'when the project is public' do + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:release) { create(:release, :with_evidence, project: project) } + + before_all do + project.add_guest(guest) + project.add_reporter(reporter) + end + + context 'when the user is not logged in' do + let(:current_user) { stranger } + + it_behaves_like 'full access to all repository-related fields' + end + + context 'when the user has Guest permissions' do + let(:current_user) { guest } + + it_behaves_like 'full access to all repository-related fields' + end + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 774615757b99a..fe1435826a072 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -651,9 +651,9 @@ context 'when job is for a release' do let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) } - context 'when `release_steps` is passed by the runner' do + context 'when `multi_build_steps` is passed by the runner' do it 'exposes release info' do - request_job info: { features: { release_steps: true } } + request_job info: { features: { multi_build_steps: true } } expect(response).to have_gitlab_http_status(:created) expect(response.headers).not_to have_key('X-GitLab-Last-Update') @@ -677,7 +677,7 @@ end end - context 'when `release_steps` is not passed by the runner' do + context 'when `multi_build_steps` is not passed by the runner' do it 'drops the job' do request_job diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index c5b8a6db60537..92f7eb211d60c 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -46,8 +46,10 @@ def configure_profile add_filter 'lib/gitlab/sidekiq_middleware/' add_filter 'lib/system_check/' + add_group 'Channels', 'app/channels' add_group 'Controllers', 'app/controllers' add_group 'Finders', 'app/finders' + add_group 'GraphQL', 'app/graphql' add_group 'Helpers', 'app/helpers' add_group 'Libraries', 'lib' add_group 'Mailers', 'app/mailers'