Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-18 06:08:33 +00:00
parent aed2039d57
commit ef19a5c55c
36 changed files with 720 additions and 304 deletions

View File

@ -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

View File

@ -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,

View File

@ -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'

View File

@ -3,6 +3,7 @@
module Types
class ReleaseLinkType < BaseObject
graphql_name 'ReleaseLink'
description 'Represents an asset link associated with a release'
authorize :read_release

View File

@ -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'

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -0,0 +1,5 @@
---
title: Create group_deploy_keys_groups intermediate table
merge_request: 32901
author:
type: added

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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 |

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
---

View File

@ -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
---

View File

@ -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.

View File

@ -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
---

View File

@ -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"
---

View File

@ -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

View File

@ -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)

View File

@ -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[

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'