Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
aed2039d57
commit
ef19a5c55c
|
@ -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
|
|
@ -251,7 +251,8 @@ module Types
|
|||
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,
|
||||
|
|
|
@ -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 @@ module Types
|
|||
|
||||
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'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module Types
|
||||
class ReleaseLinkType < BaseObject
|
||||
graphql_name 'ReleaseLink'
|
||||
description 'Represents an asset link associated with a release'
|
||||
|
||||
authorize :read_release
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module Types
|
||||
class ReleaseType < BaseObject
|
||||
graphql_name 'Release'
|
||||
description 'Represents a release'
|
||||
|
||||
authorize :read_release
|
||||
|
||||
|
@ -10,10 +11,12 @@ module Types
|
|||
|
||||
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 @@ module Types
|
|||
|
||||
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?
|
||||
|
|
|
@ -27,7 +27,7 @@ module Ci
|
|||
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 @@ module Ci
|
|||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
|
|||
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?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Create group_deploy_keys_groups intermediate table
|
||||
merge_request: 32901
|
||||
author:
|
||||
type: added
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 <virtual-storage> -reference <previous-primary> -target <current-primary> -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 <virtual-storage>
|
||||
```
|
||||
|
||||
## Backend Node Recovery
|
||||
|
||||
When a Praefect backend node fails and is no longer able to
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
@ -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"
|
||||
---
|
||||
|
|
|
@ -5,9 +5,7 @@ module API
|
|||
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 @@ module API
|
|||
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
|
||||
|
|
|
@ -7,7 +7,7 @@ describe GitlabSchema.types['ReleaseAssets'] do
|
|||
|
||||
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)
|
||||
|
|
|
@ -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[
|
||||
|
|
|
@ -44,6 +44,5 @@ describe GitlabSchema.types['Release'] do
|
|||
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
|
||||
|
|
|
@ -4302,15 +4302,15 @@ describe Ci::Build do
|
|||
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
|
||||
|
|
|
@ -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
|
|
@ -112,4 +112,36 @@ describe ReleasePresenter do
|
|||
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
|
||||
|
|
|
@ -7,15 +7,10 @@ describe 'Query.project(fullPath).release(tagName)' do
|
|||
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 @@ describe 'Query.project(fullPath).release(tagName)' do
|
|||
|
||||
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
|
||||
|
||||
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
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
describe 'author' do
|
||||
let(:path) { path_prefix + %w[author] }
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:author, nil, 'id username')
|
||||
end
|
||||
|
||||
it 'finds the author of the release' do
|
||||
post_query
|
||||
|
||||
expect(data).to eq({
|
||||
'id' => global_id_of(release.author),
|
||||
'username' => release.author.username
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
describe 'commit' do
|
||||
let(:path) { path_prefix + %w[commit] }
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:commit, nil, 'sha')
|
||||
end
|
||||
|
||||
it 'finds the commit associated with the release' do
|
||||
post_query
|
||||
|
||||
expect(data).to eq({ 'sha' => release.commit.sha })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'assets' do
|
||||
describe 'assetsCount' do
|
||||
let(:path) { path_prefix + %w[assets] }
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:assets, nil, 'assetsCount')
|
||||
query_graphql_field(%{
|
||||
tagName
|
||||
tagPath
|
||||
description
|
||||
descriptionHtml
|
||||
name
|
||||
createdAt
|
||||
releasedAt
|
||||
})
|
||||
end
|
||||
|
||||
it 'returns the number of assets associated to the release' do
|
||||
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
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size })
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
}
|
||||
expected = release.milestones.map do |milestone|
|
||||
{ 'id' => global_id_of(milestone), 'title' => milestone.title }
|
||||
end
|
||||
|
||||
expect(data).to match_array(expected)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sources' do
|
||||
let(:path) { path_prefix + %w[assets sources nodes] }
|
||||
describe 'author' do
|
||||
let(:path) { path_prefix + %w[author] }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:assets, nil,
|
||||
query_graphql_field(:sources, nil, 'nodes { format url }'))
|
||||
query_graphql_field(:author, nil, 'id username')
|
||||
end
|
||||
|
||||
it 'finds all release sources' do
|
||||
it 'finds the author of the release' do
|
||||
post_query
|
||||
|
||||
expected = release.sources.map do |source|
|
||||
{
|
||||
'format' => source.format,
|
||||
'url' => source.url
|
||||
}
|
||||
expect(data).to eq({
|
||||
'id' => global_id_of(release.author),
|
||||
'username' => release.author.username
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
describe 'commit' do
|
||||
let(:path) { path_prefix + %w[commit] }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:commit, nil, 'sha')
|
||||
end
|
||||
|
||||
it 'finds the commit associated with the release' do
|
||||
post_query
|
||||
|
||||
expect(data).to eq({ 'sha' => release.commit.sha })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'assets' do
|
||||
describe 'count' do
|
||||
let(:path) { path_prefix + %w[assets] }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:assets, nil, 'count')
|
||||
end
|
||||
|
||||
expect(data).to match_array(expected)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 'evidences' do
|
||||
let(:path) { path_prefix + %w[evidences] }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
|
||||
end
|
||||
|
||||
context 'for a developer' do
|
||||
it 'finds all evidence fields' do
|
||||
post_query
|
||||
it 'finds all evidence fields' do
|
||||
post_query
|
||||
|
||||
evidence = release.evidences.first.present
|
||||
expected = {
|
||||
'id' => global_id_of(evidence),
|
||||
'sha' => evidence.sha,
|
||||
'filepath' => evidence.filepath,
|
||||
'collectedAt' => evidence.collected_at.utc.iso8601
|
||||
}
|
||||
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["nodes"].first).to eq(expected)
|
||||
end
|
||||
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 }
|
||||
|
||||
let(:release_fields) do
|
||||
query_graphql_field('description')
|
||||
end
|
||||
|
||||
context 'for a guest' do
|
||||
let(:current_user) { create :user }
|
||||
before do
|
||||
post_query
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
end
|
||||
it 'returns nil' do
|
||||
expect(data).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'denies access' do
|
||||
post_query
|
||||
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) }
|
||||
|
||||
expect(data['node']).to be_nil
|
||||
end
|
||||
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
|
||||
|
||||
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 'when the user is not logged in' do
|
||||
let(:current_user) { stranger }
|
||||
|
||||
it_behaves_like 'full access to the release field'
|
||||
end
|
||||
|
||||
context 'when the user has Guest permissions' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it_behaves_like 'full 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
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -651,9 +651,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
|
|||
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 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
|
|||
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
|
||||
|
||||
|
|
|
@ -46,8 +46,10 @@ module SimpleCovEnv
|
|||
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'
|
||||
|
|
Loading…
Reference in New Issue