Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-16 15:08:32 +00:00
parent 9bfdb5cf67
commit 10fd79745d
64 changed files with 872 additions and 312 deletions

View file

@ -81,7 +81,7 @@ export const getTimeAxisOptions = ({ timezone = timezones.LOCAL } = {}) => ({
formatter: date => formatDate(date, { format: formats.shortTime, timezone }),
},
axisPointer: {
snap: true,
snap: false,
},
});

View file

@ -85,7 +85,8 @@ export default {
},
defaultBranch: {
type: String,
required: true,
required: false,
default: '',
},
emptyGettingStartedSvgPath: {
type: String,

View file

@ -1,9 +1,9 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import { createStore } from './stores';
import createRouter from './router';
Vue.use(GlToast);
@ -21,6 +21,7 @@ export default (props = {}) => {
logsPath,
currentEnvironmentName,
dashboardTimezone,
metricsDashboardBasePath,
...dataProps
} = el.dataset;
@ -40,18 +41,19 @@ export default (props = {}) => {
dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
const router = createRouter(metricsDashboardBasePath);
// eslint-disable-next-line no-new
new Vue({
el,
store,
render(createElement) {
return createElement(Dashboard, {
props: {
...dataProps,
...props,
},
});
router,
data() {
return {
dashboardProps: { ...dataProps, ...props },
};
},
template: `<router-view :dashboardProps="dashboardProps"/>`,
});
}
};

View file

@ -0,0 +1,18 @@
<script>
import Dashboard from '../components/dashboard.vue';
export default {
components: {
Dashboard,
},
props: {
dashboardProps: {
type: Object,
required: true,
},
},
};
</script>
<template>
<dashboard v-bind="{ ...dashboardProps }" />
</template>

View file

@ -0,0 +1,3 @@
export const BASE_DASHBOARD_PAGE = 'dashboard';
export default {};

View file

@ -0,0 +1,15 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
Vue.use(VueRouter);
export default function createRouter(base) {
const router = new VueRouter({
base,
mode: 'history',
routes,
});
return router;
}

View file

@ -0,0 +1,18 @@
import DashboardPage from '../pages/dashboard_page.vue';
import { BASE_DASHBOARD_PAGE } from './constants';
/**
* Because the cluster health page uses the dashboard
* app instead the of the dashboard component, hitting
* `/` route is not possible. Hence using `*` until the
* health page is refactored.
* https://gitlab.com/gitlab-org/gitlab/-/issues/221096
*/
export default [
{
name: BASE_DASHBOARD_PAGE,
path: '*',
component: DashboardPage,
},
];

View file

@ -1,3 +1,3 @@
import monitoringBundle from '~/monitoring/monitoring_bundle';
import monitoringApp from '~/monitoring/monitoring_app';
document.addEventListener('DOMContentLoaded', monitoringBundle);
document.addEventListener('DOMContentLoaded', monitoringApp);

View file

@ -32,6 +32,7 @@ export default {
v-for="panel in panels"
:key="panel.name"
:href="`#${panel.name}`"
:data-qa-selector="`${panel.name}_link`"
class="blank-state blank-state-link experiment-new-project-page-blank-state"
@click="track('click_tab', { label: panel.name })"
>

View file

@ -308,7 +308,6 @@ body {
);
}
&.gl-dark,
&.ui-light {
@include gitlab-theme(
$gray-700,
@ -391,13 +390,47 @@ body {
}
&.gl-dark {
@include gitlab-theme(
$gray-900,
$gray-500,
$gray-700,
$gray-800,
$gray-50,
$gray-100
);
.logo-text svg {
fill: $gl-text-color;
}
.navbar-gitlab {
background-color: $gray-50;
.navbar-sub-nav,
.navbar-nav {
li {
> a:hover,
> a:focus,
> button:hover,
> button:focus {
color: $gl-text-color;
background-color: $gray-200;
}
}
li.active,
li.dropdown.show {
> a,
> button {
color: $gl-text-color;
background-color: $gray-200;
}
}
}
.search {
form {
background-color: $gray-100;
box-shadow: inset 0 0 0 1px $border-color;
&:active,
&:hover {
background-color: $gray-100;
box-shadow: inset 0 0 0 1px $blue-200;
}
}
}
}
}
}

View file

@ -12,6 +12,7 @@
.select2-container.select2-drop-above {
.select2-choice {
background: $white;
color: $gl-text-color;
border-color: $input-border;
height: 34px;
padding: $gl-vert-padding $gl-input-padding;

View file

@ -25,6 +25,13 @@ $ide-commit-header-height: 48px;
@include str-truncated(250px);
}
.ide-layout {
// Fix for iOS 13+, the height of the page is actually less than
// 100vh because of the presence of the bottom bar
max-height: 100%;
position: fixed;
}
.ide-view {
position: relative;
margin-top: 0;

View file

@ -99,7 +99,7 @@ $border-white-normal: $gray-900;
$body-bg: $gray-50;
$input-bg: $gray-100;
$input-focus-bg: $gray-50;
$input-focus-bg: $gray-100;
$input-color: $gray-900;
$input-group-addon-bg: $gray-900;

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module FindSnippet
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
private
# rubocop:disable CodeReuse/ActiveRecord
def snippet
strong_memoize(:snippet) do
snippet_klass.inc_relations_for_view.find_by(id: snippet_id)
end
end
# rubocop:enable CodeReuse/ActiveRecord
def snippet_klass
raise NotImplementedError
end
def snippet_id
params[:id]
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module SnippetAuthorizations
extend ActiveSupport::Concern
private
def authorize_read_snippet!
return render_404 unless can?(current_user, :read_snippet, snippet)
end
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_snippet, snippet)
end
def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_snippet, snippet)
end
def authorize_create_snippet!
return render_404 unless can?(current_user, :create_snippet)
end
end

View file

@ -3,9 +3,18 @@
module SnippetsActions
extend ActiveSupport::Concern
include SendsBlob
include RendersNotes
include RendersBlob
include PaginatedCollection
include Gitlab::NoteableMetadata
included do
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
before_action :redirect_if_binary, only: [:edit, :update]
respond_to :html
end
def edit
@ -43,6 +52,58 @@ module SnippetsActions
request.format.js?
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def show
conditionally_expand_blob(blob)
respond_to do |format|
format.html do
@note = Note.new(noteable: @snippet, project: @snippet.project)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
render 'show'
end
format.json do
render_blob_json(blob)
end
format.js do
if @snippet.embeddable?
render 'shared/snippets/show'
else
head :not_found
end
end
end
end
def update
update_params = snippet_params.merge(spammable_params)
service_response = Snippets::UpdateService.new(@snippet.project, current_user, update_params).execute(@snippet)
@snippet = service_response.payload[:snippet]
handle_repository_error(:edit)
end
def destroy
service_response = Snippets::DestroyService.new(current_user, @snippet).execute
if service_response.success?
redirect_to gitlab_dashboard_snippets_path(@snippet), status: :found
elsif service_response.http_status == 403
access_denied!
else
redirect_to gitlab_snippet_path(@snippet),
status: :found,
alert: service_response.message
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private
def content_disposition

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class Projects::Snippets::ApplicationController < Projects::ApplicationController
include FindSnippet
include SnippetAuthorizations
private
# This overrides the default snippet create authorization
# because ProjectSnippets are checked against the project rather
# than the user
def authorize_create_snippet!
return render_404 unless can?(current_user, :create_snippet, project)
end
def snippet_klass
ProjectSnippet
end
end

View file

@ -1,34 +1,19 @@
# frozen_string_literal: true
class Projects::SnippetsController < Projects::ApplicationController
include RendersNotes
class Projects::SnippetsController < Projects::Snippets::ApplicationController
include SnippetsActions
include ToggleAwardEmoji
include SpammableActions
include SnippetsActions
include RendersBlob
include PaginatedCollection
include Gitlab::NoteableMetadata
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow create snippet
before_action :authorize_create_snippet!, only: [:new, :create]
# Allow read any snippet
before_action :authorize_read_snippet!, except: [:new, :create, :index]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
# Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
respond_to :html
def index
@snippet_counts = Snippets::CountService
.new(current_user, project: @project)
@ -56,61 +41,8 @@ class Projects::SnippetsController < Projects::ApplicationController
handle_repository_error(:new)
end
def update
update_params = snippet_params.merge(spammable_params)
service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet)
@snippet = service_response.payload[:snippet]
handle_repository_error(:edit)
end
def show
conditionally_expand_blob(blob)
respond_to do |format|
format.html do
@note = @project.notes.new(noteable: @snippet)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
render 'show'
end
format.json do
render_blob_json(blob)
end
format.js do
if @snippet.embeddable?
render 'shared/snippets/show'
else
head :not_found
end
end
end
end
def destroy
service_response = Snippets::DestroyService.new(current_user, @snippet).execute
if service_response.success?
redirect_to project_snippets_path(project), status: :found
elsif service_response.http_status == 403
access_denied!
else
redirect_to project_snippet_path(project, @snippet),
status: :found,
alert: service_response.message
end
end
protected
def snippet
@snippet ||= @project.snippets.inc_relations_for_view.find(params[:id])
end
alias_method :awardable, :snippet
alias_method :spammable, :snippet
@ -118,18 +50,6 @@ class Projects::SnippetsController < Projects::ApplicationController
project_snippet_path(@project, @snippet)
end
def authorize_read_snippet!
return render_404 unless can?(current_user, :read_snippet, @snippet)
end
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_snippet, @snippet)
end
def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_snippet, @snippet)
end
def snippet_params
params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description)
end

View file

@ -41,16 +41,20 @@ class Projects::TagsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def create
# TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
evidence_pipeline = find_evidence_pipeline
result = ::Tags::CreateService.new(@project, current_user)
.execute(params[:tag_name], params[:ref], params[:message])
if result[:status] == :success
# Release creation with Tags was deprecated in GitLab 11.7
# TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
if params[:release_description].present?
release_params = {
tag: params[:tag_name],
name: params[:tag_name],
description: params[:release_description]
description: params[:release_description],
evidence_pipeline: evidence_pipeline
}
Releases::CreateService
@ -93,4 +97,14 @@ class Projects::TagsController < Projects::ApplicationController
end
end
end
private
# TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
def find_evidence_pipeline
evidence_pipeline_sha = @project.repository.commit(params[:ref])&.sha
return unless evidence_pipeline_sha
@project.ci_pipelines.for_sha(evidence_pipeline_sha).last
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
class Snippets::ApplicationController < ApplicationController
include FindSnippet
include SnippetAuthorizations
private
def authorize_read_snippet!
return if can?(current_user, :read_snippet, snippet)
if current_user
render_404
else
authenticate_user!
end
end
def snippet_klass
PersonalSnippet
end
end

View file

@ -1,19 +1,12 @@
# frozen_string_literal: true
class SnippetsController < ApplicationController
include RendersNotes
class SnippetsController < Snippets::ApplicationController
include SnippetsActions
include PreviewMarkdown
include ToggleAwardEmoji
include SpammableActions
include SnippetsActions
include RendersBlob
include PreviewMarkdown
include PaginatedCollection
include Gitlab::NoteableMetadata
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
before_action :authorize_create_snippet!, only: [:new, :create]
before_action :authorize_read_snippet!, only: [:show, :raw]
@ -23,7 +16,6 @@ class SnippetsController < ApplicationController
skip_before_action :authenticate_user!, only: [:index, :show, :raw]
layout 'snippets'
respond_to :html
def index
if params[:username].present?
@ -60,62 +52,8 @@ class SnippetsController < ApplicationController
end
end
def update
service_response = Snippets::UpdateService.new(nil, current_user, snippet_params).execute(@snippet)
@snippet = service_response.payload[:snippet]
handle_repository_error(:edit)
end
def show
conditionally_expand_blob(blob)
respond_to do |format|
format.html do
@note = Note.new(noteable: @snippet)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
render 'show'
end
format.json do
render_blob_json(blob)
end
format.js do
if @snippet.embeddable?
render 'shared/snippets/show'
else
head :not_found
end
end
end
end
def destroy
service_response = Snippets::DestroyService.new(current_user, @snippet).execute
if service_response.success?
redirect_to dashboard_snippets_path, status: :found
elsif service_response.http_status == 403
access_denied!
else
redirect_to snippet_path(@snippet),
status: :found,
alert: service_response.message
end
end
protected
# rubocop: disable CodeReuse/ActiveRecord
def snippet
@snippet ||= PersonalSnippet.inc_relations_for_view.find_by(id: params[:id])
end
# rubocop: enable CodeReuse/ActiveRecord
alias_method :awardable, :snippet
alias_method :spammable, :snippet
@ -123,28 +61,6 @@ class SnippetsController < ApplicationController
snippet_path(@snippet)
end
def authorize_read_snippet!
return if can?(current_user, :read_snippet, @snippet)
if current_user
render_404
else
authenticate_user!
end
end
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_snippet, @snippet)
end
def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_snippet, @snippet)
end
def authorize_create_snippet!
return render_404 unless can?(current_user, :create_snippet)
end
def snippet_params
params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description).merge(spammable_params)
end

View file

@ -245,6 +245,14 @@ module GitlabRoutingHelper
end
end
def gitlab_dashboard_snippets_path(snippet, *args)
if snippet.is_a?(ProjectSnippet)
project_snippets_path(snippet.project, *args)
else
dashboard_snippets_path
end
end
def gitlab_raw_snippet_path(snippet, *args)
if snippet.is_a?(ProjectSnippet)
raw_project_snippet_path(snippet.project, snippet, *args)

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
# Shared scope between Route and RedirectRoute
module RouteModelQuery
extend ActiveSupport::Concern
class_methods do
def find_source_of_path(path, case_sensitive: true)
scope =
if case_sensitive
where(path: path)
else
where('LOWER(path) = LOWER(?)', path)
end
scope.first&.source
end
end
end

View file

@ -1253,7 +1253,7 @@ class Project < ApplicationRecord
available_services_names.map do |service_name|
find_or_initialize_service(service_name)
end
end.sort_by(&:title)
end
def disabled_services

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class RedirectRoute < ApplicationRecord
include RouteModelQuery
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :source, presence: true

View file

@ -3,6 +3,7 @@
class Route < ApplicationRecord
include CaseSensitivity
include Gitlab::SQL::Pattern
include RouteModelQuery
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :source, presence: true

View file

@ -167,7 +167,11 @@ class Snippet < ApplicationRecord
end
def self.find_by_id_and_project(id:, project:)
Snippet.find_by(id: id, project: project)
if project.is_a?(Project)
ProjectSnippet.find_by(id: id, project: project)
elsif project.nil?
PersonalSnippet.find_by(id: id)
end
end
def self.max_file_limit(user)

View file

@ -64,7 +64,7 @@ module Projects
end
def remove_snippets
response = Snippets::BulkDestroyService.new(current_user, project.snippets).execute
response = ::Snippets::BulkDestroyService.new(current_user, project.snippets).execute
response.success?
end

View file

@ -2,8 +2,9 @@
module Releases
class CreateEvidenceService
def initialize(release)
def initialize(release, pipeline: nil)
@release = release
@pipeline = pipeline
end
def execute

View file

@ -9,11 +9,16 @@ module Releases
return error('Release already exists', 409) if release
return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
# should be found before the creation of new tag
# because tag creation can spawn new pipeline
# which won't have any data for evidence yet
evidence_pipeline = find_evidence_pipeline
tag = ensure_tag
return tag unless tag.is_a?(Gitlab::Git::Tag)
create_release(tag)
create_release(tag, evidence_pipeline)
end
def find_or_build_release
@ -42,14 +47,14 @@ module Releases
Ability.allowed?(current_user, :create_release, project)
end
def create_release(tag)
def create_release(tag, evidence_pipeline)
release = build_release(tag)
release.save!
notify_create_release(release)
create_evidence!(release)
create_evidence!(release, evidence_pipeline)
success(tag: tag, release: release)
rescue => e
@ -73,13 +78,25 @@ module Releases
)
end
def create_evidence!(release)
def find_evidence_pipeline
# TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
return params[:evidence_pipeline] if params[:evidence_pipeline]
sha = existing_tag&.dereferenced_target&.sha
sha ||= repository.commit(ref)&.sha
return unless sha
project.ci_pipelines.for_sha(sha).last
end
def create_evidence!(release, pipeline)
return if release.historical_release?
if release.upcoming_release?
CreateEvidenceWorker.perform_at(release.released_at, release.id)
CreateEvidenceWorker.perform_at(release.released_at, release.id, pipeline&.id)
else
CreateEvidenceWorker.perform_async(release.id)
CreateEvidenceWorker.perform_async(release.id, pipeline&.id)
end
end
end

View file

@ -7,8 +7,8 @@ class AuthorizedProjectsWorker
feature_category :authentication_and_authorization
urgency :high
weight 2
idempotent!
loggable_arguments 1 # For the job waiter key
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
# visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231

View file

@ -6,10 +6,15 @@ class CreateEvidenceWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :release_evidence
weight 2
def perform(release_id)
# pipeline_id is optional for backward compatibility with existing jobs
# caller should always try to provide the pipeline and pass nil only
# if pipeline is absent
def perform(release_id, pipeline_id = nil)
release = Release.find_by_id(release_id)
return unless release
::Releases::CreateEvidenceService.new(release).execute
pipeline = Ci::Pipeline.find_by_id(pipeline_id)
::Releases::CreateEvidenceService.new(release, pipeline: pipeline).execute
end
end

View file

@ -0,0 +1,5 @@
---
title: Fix issues with scroll on iOS / iPad OS
merge_request: 34486
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Allow CI_JOB_TOKEN for authenticating to the Terraform state API
merge_request: 34618
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Fix order of integrations to be sorted alphabetically
merge_request: 34501
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Use Keys::DestroyService for deleting an SSH key when an admin deletes a key via the API
merge_request: 34535
author: Rajendra Kadam
type: fixed

View file

@ -64,7 +64,7 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started |
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry](../../user/packages/container_registry/index.md) and downloading [dependent repositories](../../user/project/new_ci_build_permissions_model.md#dependent-repositories) |
| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry](../../user/packages/container_registry/index.md), downloading [dependent repositories](../../user/project/new_ci_build_permissions_model.md#dependent-repositories), and accessing [GitLab-managed Terraform state](../../user/infrastructure/index.md#gitlab-managed-terraform-state). |
| `CI_JOB_JWT` | 12.10 | all | RS256 JSON web token that can be used for authenticating with third party systems that support JWT authentication, for example [HashiCorp's Vault](../examples/authenticating-with-hashicorp-vault). |
| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
| `CI_KUBERNETES_ACTIVE` | 13.0 | all | Included with the value `true` only if the pipeline has a Kubernetes cluster available for deployments. Not included if no cluster is available. Can be used as an alternative to [`only:kubernetes`/`except:kubernetes`](../yaml/README.md#onlykubernetesexceptkubernetes) with [`rules:if`](../yaml/README.md#rulesif) |

View file

@ -25,7 +25,7 @@ Amazon S3 or Google Cloud Storage. Its features include:
To get started with a GitLab-managed Terraform State, there are two different options:
- [Use a local machine](#get-started-using-local-development).
- [Use GitLab CI](#get-started-using-a-gitlab-ci).
- [Use GitLab CI](#get-started-using-gitlab-ci).
## Get started using local development
@ -44,10 +44,15 @@ local machine, this is a simple way to get started:
}
```
1. Create a [Personal Access Token](../profile/personal_access_tokens.md) with
the `api` scope. The Terraform backend is restricted to users with
[Maintainer access](../permissions.md) to the repository.
1. On your local machine, run `terraform init`, passing in the following options,
replacing `<YOUR-PROJECT-NAME>` and `<YOUR-PROJECT-ID>` with the values for
your project. This command initializes your Terraform state, and stores that
state within your GitLab project. This example uses `gitlab.com`:
replacing `<YOUR-PROJECT-NAME>`, `<YOUR-PROJECT-ID>`, `<YOUR-USERNAME>` and
`<YOUR-ACCESS-TOKEN>` with the relevant values. This command initializes your
Terraform state, and stores that state within your GitLab project. This example
uses `gitlab.com`:
```shell
terraform init \
@ -61,30 +66,24 @@ local machine, this is a simple way to get started:
-backend-config="retry_wait_min=5"
```
Next, [configure the backend](#configure-the-variables-and-backend).
Next, [configure the backend](#configure-the-backend).
## Get started using a GitLab CI
## Get started using GitLab CI
If you don't want to start with local development, you can also use GitLab CI to
run your `terraform plan` and `terraform apply` commands.
Next, [configure the backend](#configure-the-variables-and-backend).
Next, [configure the backend](#configure-the-backend).
## Configure the variables and backend
## Configure the backend
After executing the `terraform init` command, you must configure the needed CI
variables, the Terraform backend, and the CI YAML file:
After executing the `terraform init` command, you must configure the Terraform backend
and the CI YAML file:
1. Create a [Personal Access Token](../profile/personal_access_tokens.md) with
the `api` scope. The Terraform backend is restricted to tokens with
[Maintainer access](../permissions.md) to the repository.
1. To keep the Personal Access Token secure, add it as a
[CI/CD environment variable](../../ci/variables/README.md). For the examples on
this page, it's set to the environment variable `GITLAB_TF_PASSWORD`.
CAUTION: **Important:**
The Terraform backend is restricted to users with [Maintainer access](../permissions.md)
to the repository.
CAUTION: **Important:**
If you plan to use the environment variable on an unprotected branch, make sure
to set the variable protection settings correctly.
1. In your Terraform project, define the [HTTP backend](https://www.terraform.io/docs/backends/types/http.html)
by adding the following code block in a `.tf` file (such as `backend.tf`) to
define the remote backend:
@ -129,7 +128,7 @@ variables, the Terraform backend, and the CI YAML file:
before_script:
- cd ${TF_ROOT}
- terraform --version
- terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=${GITLAB_USER_LOGIN}" -backend-config="password=${GITLAB_TF_PASSWORD}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
- terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
stages:
- validate

View file

@ -32,7 +32,7 @@ module API
end
desc 'Get a terraform state by its name'
route_setting :authentication, basic_auth_personal_access_token: true
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get do
remote_state_handler.find_with_lock do |state|
no_content! unless state.file.exists?
@ -44,7 +44,7 @@ module API
end
desc 'Add a new terraform state or update an existing one'
route_setting :authentication, basic_auth_personal_access_token: true
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post do
data = request.body.read
no_content! if data.empty?
@ -57,7 +57,7 @@ module API
end
desc 'Delete a terraform state of a certain name'
route_setting :authentication, basic_auth_personal_access_token: true
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
delete do
remote_state_handler.handle_with_lock do |state|
state.destroy!
@ -66,7 +66,7 @@ module API
end
desc 'Lock a terraform state of a certain name'
route_setting :authentication, basic_auth_personal_access_token: true
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
params do
requires :ID, type: String, limit: 255, desc: 'Terraform state lock ID'
requires :Operation, type: String, desc: 'Terraform operation'
@ -103,7 +103,7 @@ module API
end
desc 'Unlock a terraform state of a certain name'
route_setting :authentication, basic_auth_personal_access_token: true
route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
params do
optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID'
end

View file

@ -306,7 +306,10 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
destroy_conditionally!(key)
destroy_conditionally!(key) do |key|
destroy_service = ::Keys::DestroyService.new(current_user)
destroy_service.execute(key)
end
end
# rubocop: enable CodeReuse/ActiveRecord

View file

@ -56,6 +56,7 @@ module Gitlab
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth
token = current_request.params[JOB_TOKEN_PARAM].presence ||
current_request.params[RUNNER_JOB_TOKEN_PARAM].presence ||

View file

@ -4,6 +4,11 @@ module Gitlab
module RepoPath
NotFoundError = Class.new(StandardError)
# @return [Array]
# 1. container (ActiveRecord which holds repository)
# 2. project (Project)
# 3. repo_type
# 4. redirected_path
def self.parse(path)
repo_path = path.sub(/\.git\z/, '').sub(%r{\A/}, '')
redirected_path = nil
@ -17,7 +22,7 @@ module Gitlab
# `Gitlab::GlRepository::PROJECT` type.
next unless type.valid?(repo_path)
# Removing the suffix (.wiki, .design, ...) from the project path
# Removing the suffix (.wiki, .design, ...) from path
full_path = repo_path.chomp(type.path_suffix)
container, project, redirected_path = find_container(type, full_path)
@ -36,23 +41,31 @@ module Gitlab
[snippet, snippet&.project, redirected_path]
else
project, redirected_path = find_project(full_path)
container, redirected_path = find_routes_source(full_path)
[project, project, redirected_path]
if container.is_a?(Project)
[container, container, redirected_path]
else
[container, nil, redirected_path]
end
end
end
def self.find_project(project_path)
return [nil, nil] if project_path.blank?
def self.find_routes_source(path)
return [nil, nil] if path.blank?
project = Project.find_by_full_path(project_path, follow_redirects: true)
redirected_path = redirected?(project, project_path) ? project_path : nil
source =
Route.find_source_of_path(path) ||
Route.find_source_of_path(path, case_sensitive: false) ||
RedirectRoute.find_source_of_path(path, case_sensitive: false)
[project, redirected_path]
redirected_path = redirected?(source, path) ? path : nil
[source, redirected_path]
end
def self.redirected?(project, project_path)
project && project.full_path.casecmp(project_path) != 0
def self.redirected?(container, container_path)
container && container.full_path.casecmp(container_path) != 0
end
# Snippet_path can be either:
@ -62,7 +75,7 @@ module Gitlab
return [nil, nil] if snippet_path.blank?
snippet_id, project_path = extract_snippet_info(snippet_path)
project, redirected_path = find_project(project_path)
project, redirected_path = find_routes_source(project_path)
[Snippet.find_by_id_and_project(id: snippet_id, project: project), redirected_path]
end

View file

@ -41,7 +41,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.139.0",
"@gitlab/ui": "16.10.0",
"@gitlab/ui": "16.12.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",

View file

@ -231,6 +231,7 @@ module QA
module Project
autoload :New, 'qa/page/project/new'
autoload :NewExperiment, 'qa/page/project/new_experiment'
autoload :Show, 'qa/page/project/show'
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'

View file

@ -14,6 +14,14 @@ module QA
member_settings.add_member(username)
end
end
def go_to_create_project_from_template
if Page::Project::NewExperiment.perform(&:shown?)
Page::Project::NewExperiment.perform(&:click_create_from_template_link)
else
Page::Project::New.perform(&:click_create_from_template_tab)
end
end
end
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module QA
module Page
module Project
class NewExperiment < Page::Base
view 'app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue' do
element :blank_project_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern
element :create_from_template_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern
end
def shown?
has_element? :blank_project_link
end
def click_blank_project_link
click_element :blank_project_link
end
def click_create_from_template_link
click_element :create_from_template_link
end
end
end
end
end

View file

@ -71,12 +71,14 @@ module QA
end
if @template_name
QA::Flow::Project.go_to_create_project_from_template
Page::Project::New.perform do |new_page|
new_page.click_create_from_template_tab
new_page.use_template_for_project(@template_name)
end
end
Page::Project::NewExperiment.perform(&:click_blank_project_link) if Page::Project::NewExperiment.perform(&:shown?)
Page::Project::New.perform do |new_page|
new_page.choose_test_namespace
new_page.choose_name(@name)

View file

@ -3,6 +3,12 @@
module QA
context 'Create', :requires_admin do
describe 'push after setting the file size limit via admin/application_settings' do
# Note: The file size limits in this test should be greater than the limits in
# ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from
# triggering the limit set in this test (which can happen on Staging where the
# tests are run in parallel).
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/218620#note_361634705
include Support::Api
before(:context) do
@ -31,7 +37,7 @@ module QA
end
it 'push fails when the file size is above the limit' do
set_file_size_limit(1)
set_file_size_limit(2)
retry_on_fail do
expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
@ -52,7 +58,7 @@ module QA
output = Resource::Repository::Push.fabricate! do |p|
p.repository_http_uri = @project.repository_http_location.uri
p.file_name = file_name
p.file_content = SecureRandom.random_bytes(2000000)
p.file_content = SecureRandom.random_bytes(3000000)
p.commit_message = commit_message
p.new_branch = false
end

View file

@ -6,6 +6,7 @@ RSpec.describe Projects::TagsController do
let(:project) { create(:project, :public, :repository) }
let!(:release) { create(:release, project: project) }
let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
let(:user) { create(:user) }
describe 'GET index' do
before do
@ -61,4 +62,69 @@ RSpec.describe Projects::TagsController do
end
end
end
describe 'POST #create' do
before do
project.add_developer(user)
sign_in(user)
end
let(:release_description) { nil }
let(:request) do
post(:create, params: {
namespace_id: project.namespace.to_param,
project_id: project,
tag_name: '1.0',
ref: 'master',
release_description: release_description
})
end
it 'creates tag' do
request
expect(response).to have_gitlab_http_status(:found)
expect(project.repository.find_tag('1.0')).to be_present
end
# TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
context 'when release description is set' do
let(:release_description) { 'some release description' }
it 'creates tag and release' do
request
expect(response).to have_gitlab_http_status(:found)
expect(project.repository.find_tag('1.0')).to be_present
release = project.releases.find_by_tag!('1.0')
expect(release).to be_present
expect(release.description).to eq(release_description)
end
it 'passes the last pipeline for evidence creation', :sidekiq_inline do
sha = project.repository.commit('master').sha
create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline
pipeline = create(:ci_empty_pipeline, sha: sha, project: project)
# simulating pipeline creation by new tag
expect_any_instance_of(Repository).to receive(:add_tag).and_wrap_original do |m, *args|
create(:ci_empty_pipeline, sha: sha, project: project)
m.call(*args)
end
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
request
release = project.releases.find_by_tag!('1.0')
expect(release).to be_present
expect(release.description).to eq(release_description)
end
end
end
end

View file

@ -411,6 +411,12 @@ describe('Time series component', () => {
});
});
describe('xAxis pointer', () => {
it('snap is set to false by default', () => {
expect(getChartOptions().xAxis.axisPointer.snap).toBe(false);
});
});
describe('are extended by `option`', () => {
const mockSeriesName = 'Extra series 1';
const mockOption = {

View file

@ -0,0 +1,36 @@
import { shallowMount } from '@vue/test-utils';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { propsData } from '../mock_data';
describe('monitoring/pages/dashboard_page', () => {
let wrapper;
const buildWrapper = (props = {}) => {
wrapper = shallowMount(DashboardPage, {
propsData: {
...props,
},
});
};
const findDashboardComponent = () => wrapper.find(Dashboard);
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
it('throws errors if dashboard props are not passed', () => {
expect(() => buildWrapper()).toThrow('Missing required prop: "dashboardProps"');
});
it('renders the dashboard page with dashboard component', () => {
buildWrapper({ dashboardProps: propsData });
expect(findDashboardComponent().props()).toMatchObject(propsData);
expect(findDashboardComponent()).toExist();
});
});

View file

@ -238,6 +238,16 @@ describe GitlabRoutingHelper do
expect(gitlab_toggle_award_emoji_snippet_url(personal_snippet)).to eq("http://test.host/snippets/#{personal_snippet.id}/toggle_award_emoji")
end
end
describe '#gitlab_dashboard_snippets_path' do
it 'returns the personal snippets dashboard path' do
expect(gitlab_dashboard_snippets_path(personal_snippet)).to eq("/dashboard/snippets")
end
it 'returns the project snippets dashboard path' do
expect(gitlab_dashboard_snippets_path(project_snippet)).to eq("/#{project_snippet.project.full_path}/snippets")
end
end
end
context 'wikis' do

View file

@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Auth::AuthFinders do
include described_class
include HttpBasicAuthHelpers
let(:user) { create(:user) }
let(:env) do
@ -22,10 +23,7 @@ describe Gitlab::Auth::AuthFinders do
end
def set_basic_auth_header(username, password)
set_header(
'HTTP_AUTHORIZATION',
ActionController::HttpAuthentication::Basic.encode_credentials(username, password)
)
env.merge!(basic_auth_header(username, password))
end
describe '#find_user_from_warden' do
@ -653,6 +651,24 @@ describe Gitlab::Auth::AuthFinders do
it_behaves_like 'job token params', described_class::JOB_TOKEN_PARAM
it_behaves_like 'job token params', described_class::RUNNER_JOB_TOKEN_PARAM
end
context 'when the job token is provided via basic auth' do
let(:route_authentication_setting) { { job_token_allowed: :basic_auth } }
let(:username) { Ci::Build::CI_REGISTRY_USER }
let(:token) { job.token }
before do
set_basic_auth_header(username, token)
end
it { is_expected.to eq(user) }
context 'credentials are provided but route setting is incorrect' do
let(:route_authentication_setting) { { job_token_allowed: :unknown } }
it { is_expected.to be_nil }
end
end
end
describe '#find_runner_from_token' do

View file

@ -67,11 +67,11 @@ describe ::Gitlab::RepoPath do
end
end
describe '.find_project' do
describe '.find_routes_source' do
context 'when finding a project by its canonical path' do
context 'when the cases match' do
it 'returns the project and nil' do
expect(described_class.find_project(project.full_path)).to eq([project, nil])
expect(described_class.find_routes_source(project.full_path)).to eq([project, nil])
end
end
@ -81,14 +81,14 @@ describe ::Gitlab::RepoPath do
# requests, we should accept wrongly-cased URLs because it is a pain to
# block people's git operations and force them to update remote URLs.
it 'returns the project and nil' do
expect(described_class.find_project(project.full_path.upcase)).to eq([project, nil])
expect(described_class.find_routes_source(project.full_path.upcase)).to eq([project, nil])
end
end
end
context 'when finding a project via a redirect' do
it 'returns the project and nil' do
expect(described_class.find_project(redirect.path)).to eq([project, redirect.path])
expect(described_class.find_routes_source(redirect.path)).to eq([project, redirect.path])
end
end
end
@ -110,6 +110,16 @@ describe ::Gitlab::RepoPath do
end
end
context 'when path is namespace path, but has same id as project' do
let(:namespace) { build_stubbed(:namespace, id: project.id) }
it 'returns nil if path is referring to namespace' do
allow(described_class).to receive(:find_route_source).and_return(namespace)
expect(described_class.find_snippet("#{namespace.full_path}/snippets/#{project_snippet.id}")).to eq([nil, nil])
end
end
it 'returns nil for snippets not associated with the project' do
snippet = create(:project_snippet)

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Route, 'RouteModelQuery', :aggregate_failures do
let_it_be(:group1) { create(:group, path: 'Group1') }
let_it_be(:group2) { create(:group, path: 'Group2') }
let_it_be(:project1) { create(:project, path: 'Project1', group: group1) }
let_it_be(:project2) { create(:project, path: 'Project2', group: group2) }
describe '.find_source_of_path' do
it 'finds exact match' do
expect(described_class.find_source_of_path('Group1')).to eq(group1)
expect(described_class.find_source_of_path('Group2/Project2')).to eq(project2)
expect(described_class.find_source_of_path('GROUP1')).to be_nil
expect(described_class.find_source_of_path('GROUP2/PROJECT2')).to be_nil
end
it 'finds case insensitive match' do
expect(described_class.find_source_of_path('Group1', case_sensitive: false)).to eq(group1)
expect(described_class.find_source_of_path('Group2/Project2', case_sensitive: false)).to eq(project2)
expect(described_class.find_source_of_path('GROUP1', case_sensitive: false)).to eq(group1)
expect(described_class.find_source_of_path('GROUP2/PROJECT2', case_sensitive: false)).to eq(project2)
end
end
end

View file

@ -5227,13 +5227,13 @@ describe Project do
describe '#find_or_initialize_services' do
it 'returns only enabled services' do
allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover])
allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity])
allow(subject).to receive(:disabled_services).and_return(%w[prometheus])
services = subject.find_or_initialize_services
expect(services.count).to eq 1
expect(services).to include(PushoverService)
expect(services.count).to eq(2)
expect(services.map(&:title)).to eq(['JetBrains TeamCity CI', 'Pushover'])
end
end

View file

@ -206,6 +206,32 @@ describe Snippet do
end
end
describe '.find_by_id_and_project' do
let_it_be(:project) { create(:project) }
let_it_be(:project_snippet) { create(:project_snippet, project: project) }
let_it_be(:personal_snippet) { create(:personal_snippet) }
context 'when project is provided' do
it 'returns ProjectSnippet' do
expect(described_class.find_by_id_and_project(id: project_snippet.id, project: project)).to eq(project_snippet)
end
end
context 'when project is nil' do
it 'returns PersonalSnippet' do
expect(described_class.find_by_id_and_project(id: personal_snippet.id, project: nil)).to eq(personal_snippet)
end
end
context 'when project variable is not a Project' do
let(:namespace) { build_stubbed(:namespace, id: project.id) }
it 'returns nil' do
expect(described_class.find_by_id_and_project(id: project_snippet.id, project: namespace)).to be_nil
end
end
end
describe '.with_optional_visibility' do
context 'when a visibility level is provided' do
it 'returns snippets with the given visibility' do

View file

@ -3,20 +3,9 @@
require 'spec_helper'
describe 'OAuth tokens' do
include HttpBasicAuthHelpers
context 'Resource Owner Password Credentials' do
def basic_auth_header(username, password)
{
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(
username,
password
)
}
end
def client_basic_auth_header(client)
basic_auth_header(client.uid, client.secret)
end
def request_oauth_token(user, headers = {})
post '/oauth/token',
params: { username: user.username, password: user.password, grant_type: 'password' },

View file

@ -3,6 +3,8 @@
require 'spec_helper'
describe API::Terraform::State do
include HttpBasicAuthHelpers
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }
@ -10,7 +12,7 @@ describe API::Terraform::State do
let!(:state) { create(:terraform_state, :with_file, project: project) }
let(:current_user) { maintainer }
let(:auth_header) { basic_auth_header(current_user) }
let(:auth_header) { user_basic_auth_header(current_user) }
let(:project_id) { project.id }
let(:state_name) { state.name }
let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}" }
@ -23,7 +25,7 @@ describe API::Terraform::State do
subject(:request) { get api(state_path), headers: auth_header }
context 'without authentication' do
let(:auth_header) { basic_auth_header('failing_token') }
let(:auth_header) { basic_auth_header('bad', 'token') }
it 'returns 401 if user is not authenticated' do
request
@ -32,34 +34,71 @@ describe API::Terraform::State do
end
end
context 'with maintainer permissions' do
let(:current_user) { maintainer }
context 'personal acceess token authentication' do
context 'with maintainer permissions' do
let(:current_user) { maintainer }
it 'returns terraform state belonging to a project of given state name' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(state.file.read)
end
context 'for a project that does not exist' do
let(:project_id) { '0000' }
it 'returns not found' do
it 'returns terraform state belonging to a project of given state name' do
request
expect(response).to have_gitlab_http_status(:not_found)
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(state.file.read)
end
context 'for a project that does not exist' do
let(:project_id) { '0000' }
it 'returns not found' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with developer permissions' do
let(:current_user) { developer }
it 'returns forbidden if the user cannot access the state' do
request
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'with developer permissions' do
let(:current_user) { developer }
context 'job token authentication' do
let(:auth_header) { job_basic_auth_header(job) }
it 'returns forbidden if the user cannot access the state' do
request
context 'with maintainer permissions' do
let(:job) { create(:ci_build, project: project, user: maintainer) }
expect(response).to have_gitlab_http_status(:forbidden)
it 'returns terraform state belonging to a project of given state name' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(state.file.read)
end
context 'for a project that does not exist' do
let(:project_id) { '0000' }
it 'returns not found' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with developer permissions' do
let(:job) { create(:ci_build, project: project, user: developer) }
it 'returns forbidden if the user cannot access the state' do
request
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
end

View file

@ -188,6 +188,7 @@ describe Releases::CreateService do
end
context 'Evidence collection' do
let(:sha) { project.repository.commit('master').sha }
let(:params) do
{
name: 'New release',
@ -229,6 +230,75 @@ describe Releases::CreateService do
end
end
shared_examples 'uses the right pipeline for evidence' do
it 'creates evidence without pipeline if it does not exist', :sidekiq_inline do
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: nil) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { subject }.to change(Releases::Evidence, :count).by(1)
end
it 'uses the last pipeline for evidence', :sidekiq_inline do
create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline
pipeline = create(:ci_empty_pipeline, sha: sha, project: project)
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { subject }.to change(Releases::Evidence, :count).by(1)
end
context 'when old evidence_pipeline is passed to service' do
let!(:old_pipeline) { create(:ci_empty_pipeline, sha: sha, project: project) }
let!(:new_pipeline) { create(:ci_empty_pipeline, sha: sha, project: project) }
let(:params) do
super().merge(
evidence_pipeline: old_pipeline
)
end
it 'uses the old pipeline for evidence', :sidekiq_inline do
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: old_pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { subject }.to change(Releases::Evidence, :count).by(1)
end
end
it 'pipeline is still being used for evidence if new pipeline is being created for tag', :sidekiq_inline do
pipeline = create(:ci_empty_pipeline, sha: sha, project: project)
expect(project.repository).to receive(:add_tag).and_wrap_original do |m, *args|
create(:ci_empty_pipeline, sha: sha, project: project)
m.call(*args)
end
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { subject }.to change(Releases::Evidence, :count).by(1)
end
it 'uses the last pipeline for evidence when tag is already created', :sidekiq_inline do
Tags::CreateService.new(project, user).execute('v0.1', 'master', nil)
expect(project.repository.find_tag('v0.1')).to be_present
create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline
pipeline = create(:ci_empty_pipeline, sha: sha, project: project)
expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { subject }.to change(Releases::Evidence, :count).by(1)
end
end
context 'immediate release' do
let(:released_at) { nil }
@ -257,6 +327,8 @@ describe Releases::CreateService do
expect(last_release.upcoming_release?).to be_falsy
end
include_examples 'uses the right pipeline for evidence'
end
context 'upcoming release' do
@ -287,6 +359,8 @@ describe Releases::CreateService do
expect(last_release.upcoming_release?).to be_truthy
end
include_examples 'uses the right pipeline for evidence'
end
end
end

View file

@ -40,17 +40,6 @@ module ApiHelpers
end
end
def basic_auth_header(user = nil)
return { 'HTTP_AUTHORIZATION' => user } unless user.respond_to?(:username)
{
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(
user.username,
create(:personal_access_token, user: user).token
)
}
end
def expect_empty_array_response
expect_successful_response_with_paginated_array
expect(json_response.length).to eq(0)

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module HttpBasicAuthHelpers
def user_basic_auth_header(user)
access_token = create(:personal_access_token, user: user)
basic_auth_header(user.username, access_token.token)
end
def job_basic_auth_header(job)
basic_auth_header(Ci::Build::CI_REGISTRY_USER, job.token)
end
def client_basic_auth_header(client)
basic_auth_header(client.uid, client.secret)
end
def basic_auth_header(username, password)
{
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(
username,
password
)
}
end
end

View file

@ -3,13 +3,24 @@
require 'spec_helper'
describe CreateEvidenceWorker do
let(:release) { create(:release) }
let(:project) { create(:project, :repository) }
let(:release) { create(:release, project: project) }
let(:pipeline) { create(:ci_empty_pipeline, sha: release.sha, project: project) }
# support old scheduled workers without pipeline
it 'creates a new Evidence record' do
expect_next_instance_of(::Releases::CreateEvidenceService, release) do |service|
expect_next_instance_of(::Releases::CreateEvidenceService, release, pipeline: nil) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { described_class.new.perform(release.id) }.to change(Releases::Evidence, :count).by(1)
end
it 'creates a new Evidence record with pipeline' do
expect_next_instance_of(::Releases::CreateEvidenceService, release, pipeline: pipeline) do |service|
expect(service).to receive(:execute).and_call_original
end
expect { described_class.new.perform(release.id, pipeline.id) }.to change(Releases::Evidence, :count).by(1)
end
end

View file

@ -840,10 +840,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.139.0.tgz#8a4874e76000e2dd7d3ed3a8967d62bed47d7ea7"
integrity sha512-o1KAmQLYL727HodlPHkmj+d+Kdw8OIgHzlKmmPYMzeE+As2l1oz6CTilca56KqXGklOgrouC9P2puMwyX8e/6g==
"@gitlab/ui@16.10.0":
version "16.10.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-16.10.0.tgz#845d6b655baff813ba865e1abb3c318b8cfe2f8a"
integrity sha512-IZj38XWjAsr/kS2hYVONQQHYKzQIHUQ9h/v/qpDd24CnFV9BODf5vh+iCO+vZFejKaDTI22a0d+VRyLjUIL8ag==
"@gitlab/ui@16.12.1":
version "16.12.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-16.12.1.tgz#4d6865308596b09e36961210df7a8a489aaadb6d"
integrity sha512-jF6/I71Q0mjHetIRDO8O4VO2KIGWKL/yH2Mdb/CqQKaEasgnc/YpuyHGCsBXqDPxCjRbXPeKp0EywICQx4agZA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"