Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/monitoring/components/charts/options.js2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue3
-rw-r--r--app/assets/javascripts/monitoring/monitoring_app.js (renamed from app/assets/javascripts/monitoring/monitoring_bundle.js)18
-rw-r--r--app/assets/javascripts/monitoring/pages/dashboard_page.vue18
-rw-r--r--app/assets/javascripts/monitoring/router/constants.js3
-rw-r--r--app/assets/javascripts/monitoring/router/index.js15
-rw-r--r--app/assets/javascripts/monitoring/router/routes.js18
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js4
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue1
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss51
-rw-r--r--app/assets/stylesheets/framework/selects.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss7
-rw-r--r--app/assets/stylesheets/themes/_dark.scss2
-rw-r--r--app/controllers/concerns/find_snippet.rb24
-rw-r--r--app/controllers/concerns/snippet_authorizations.rb23
-rw-r--r--app/controllers/concerns/snippets_actions.rb61
-rw-r--r--app/controllers/projects/snippets/application_controller.rb19
-rw-r--r--app/controllers/projects/snippets_controller.rb86
-rw-r--r--app/controllers/projects/tags_controller.rb18
-rw-r--r--app/controllers/snippets/application_controller.rb22
-rw-r--r--app/controllers/snippets_controller.rb92
-rw-r--r--app/helpers/gitlab_routing_helper.rb8
-rw-r--r--app/models/concerns/route_model_query.rb19
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/redirect_route.rb2
-rw-r--r--app/models/route.rb1
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/releases/create_evidence_service.rb3
-rw-r--r--app/services/releases/create_service.rb29
-rw-r--r--app/workers/authorized_projects_worker.rb2
-rw-r--r--app/workers/create_evidence_worker.rb9
-rw-r--r--changelogs/unreleased/213587-ide-ipad-scroll-issue.yml5
-rw-r--r--changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml5
-rw-r--r--changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml5
-rw-r--r--changelogs/unreleased/services-usage-2.yml5
-rw-r--r--doc/ci/variables/predefined_variables.md2
-rw-r--r--doc/user/infrastructure/index.md39
-rw-r--r--lib/api/terraform/state.rb10
-rw-r--r--lib/api/users.rb5
-rw-r--r--lib/gitlab/auth/auth_finders.rb1
-rw-r--r--lib/gitlab/repo_path.rb35
-rw-r--r--package.json2
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/flow/project.rb8
-rw-r--r--qa/qa/page/project/new_experiment.rb26
-rw-r--r--qa/qa/resource/project.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb10
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb66
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js6
-rw-r--r--spec/frontend/monitoring/pages/dashboard_page_spec.js36
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb24
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb18
-rw-r--r--spec/models/concerns/route_model_query_spec.rb28
-rw-r--r--spec/models/project_spec.rb6
-rw-r--r--spec/models/snippet_spec.rb26
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb15
-rw-r--r--spec/requests/api/terraform/state_spec.rb73
-rw-r--r--spec/services/releases/create_service_spec.rb74
-rw-r--r--spec/support/helpers/api_helpers.rb11
-rw-r--r--spec/support/helpers/http_basic_auth_helpers.rb26
-rw-r--r--spec/workers/create_evidence_worker_spec.rb15
-rw-r--r--yarn.lock8
64 files changed, 868 insertions, 308 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js
index e94743309c4..f7822e69b1d 100644
--- a/app/assets/javascripts/monitoring/components/charts/options.js
+++ b/app/assets/javascripts/monitoring/components/charts/options.js
@@ -81,7 +81,7 @@ export const getTimeAxisOptions = ({ timezone = timezones.LOCAL } = {}) => ({
formatter: date => formatDate(date, { format: formats.shortTime, timezone }),
},
axisPointer: {
- snap: true,
+ snap: false,
},
});
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 1e7f7882d2a..f54319d283e 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -85,7 +85,8 @@ export default {
},
defaultBranch: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
emptyGettingStartedSvgPath: {
type: String,
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_app.js
index e8783a68fe3..08543fa6eb3 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_app.js
@@ -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"/>`,
});
}
};
diff --git a/app/assets/javascripts/monitoring/pages/dashboard_page.vue b/app/assets/javascripts/monitoring/pages/dashboard_page.vue
new file mode 100644
index 00000000000..519a20d7be3
--- /dev/null
+++ b/app/assets/javascripts/monitoring/pages/dashboard_page.vue
@@ -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>
diff --git a/app/assets/javascripts/monitoring/router/constants.js b/app/assets/javascripts/monitoring/router/constants.js
new file mode 100644
index 00000000000..acfcd03f928
--- /dev/null
+++ b/app/assets/javascripts/monitoring/router/constants.js
@@ -0,0 +1,3 @@
+export const BASE_DASHBOARD_PAGE = 'dashboard';
+
+export default {};
diff --git a/app/assets/javascripts/monitoring/router/index.js b/app/assets/javascripts/monitoring/router/index.js
new file mode 100644
index 00000000000..12692612bbc
--- /dev/null
+++ b/app/assets/javascripts/monitoring/router/index.js
@@ -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;
+}
diff --git a/app/assets/javascripts/monitoring/router/routes.js b/app/assets/javascripts/monitoring/router/routes.js
new file mode 100644
index 00000000000..1e0cc1715a7
--- /dev/null
+++ b/app/assets/javascripts/monitoring/router/routes.js
@@ -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,
+ },
+];
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
index 0b644780ad4..d3028aec313 100644
--- a/app/assets/javascripts/pages/projects/environments/metrics/index.js
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -1,3 +1,3 @@
-import monitoringBundle from '~/monitoring/monitoring_bundle';
+import monitoringApp from '~/monitoring/monitoring_app';
-document.addEventListener('DOMContentLoaded', monitoringBundle);
+document.addEventListener('DOMContentLoaded', monitoringApp);
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
index 8d005c085a2..ea22818da0e 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
@@ -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 })"
>
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 4736fb5fbd1..8d5afe1d312 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -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;
+ }
+ }
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index c8c7d275339..c2ab6f5b8c5 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -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;
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index e96cc9ebf93..9c92f891834 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -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;
diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss
index 23487a2d5f7..1f2a7645495 100644
--- a/app/assets/stylesheets/themes/_dark.scss
+++ b/app/assets/stylesheets/themes/_dark.scss
@@ -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;
diff --git a/app/controllers/concerns/find_snippet.rb b/app/controllers/concerns/find_snippet.rb
new file mode 100644
index 00000000000..d51f1a1b3ad
--- /dev/null
+++ b/app/controllers/concerns/find_snippet.rb
@@ -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
diff --git a/app/controllers/concerns/snippet_authorizations.rb b/app/controllers/concerns/snippet_authorizations.rb
new file mode 100644
index 00000000000..9bbb0cc6faa
--- /dev/null
+++ b/app/controllers/concerns/snippet_authorizations.rb
@@ -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
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index e78723bdda2..51fc12398d9 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -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
diff --git a/app/controllers/projects/snippets/application_controller.rb b/app/controllers/projects/snippets/application_controller.rb
new file mode 100644
index 00000000000..3f488b07e96
--- /dev/null
+++ b/app/controllers/projects/snippets/application_controller.rb
@@ -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
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 9233f063f55..5ee6abef804 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -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
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index c89bfd110c4..df20daa8f7e 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -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
diff --git a/app/controllers/snippets/application_controller.rb b/app/controllers/snippets/application_controller.rb
new file mode 100644
index 00000000000..a533e46a75d
--- /dev/null
+++ b/app/controllers/snippets/application_controller.rb
@@ -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
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 425e0458b41..87d87390e57 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,19 +1,12 @@
# frozen_string_literal: true
-class SnippetsController < ApplicationController
- include RendersNotes
- include ToggleAwardEmoji
- include SpammableActions
+class SnippetsController < Snippets::ApplicationController
include SnippetsActions
- include RendersBlob
include PreviewMarkdown
- include PaginatedCollection
- include Gitlab::NoteableMetadata
-
- skip_before_action :verify_authenticity_token,
- if: -> { action_name == 'show' && js_request? }
+ include ToggleAwardEmoji
+ include SpammableActions
- 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
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 52238f89887..8a9380f4771 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -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)
diff --git a/app/models/concerns/route_model_query.rb b/app/models/concerns/route_model_query.rb
new file mode 100644
index 00000000000..bcf64e1b9d6
--- /dev/null
+++ b/app/models/concerns/route_model_query.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index f1758f1a830..845e9e83e78 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 22f60802257..9844e6b5381 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -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
diff --git a/app/models/route.rb b/app/models/route.rb
index 706589e79b8..db475682245 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -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
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b63ab003711..c8a785eec9f 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -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)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index fd1366d2c4a..2e949f2fc55 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -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
diff --git a/app/services/releases/create_evidence_service.rb b/app/services/releases/create_evidence_service.rb
index b9814778e12..ac13dce1729 100644
--- a/app/services/releases/create_evidence_service.rb
+++ b/app/services/releases/create_evidence_service.rb
@@ -2,8 +2,9 @@
module Releases
class CreateEvidenceService
- def initialize(release)
+ def initialize(release, pipeline: nil)
@release = release
+ @pipeline = pipeline
end
def execute
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index 2a4cbe2dc4b..92a0b875dd4 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -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
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index a35e0320553..62eea8e0462 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -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
diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb
index c8fa9b42f38..b18028e4114 100644
--- a/app/workers/create_evidence_worker.rb
+++ b/app/workers/create_evidence_worker.rb
@@ -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
diff --git a/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml
new file mode 100644
index 00000000000..b542581f0f8
--- /dev/null
+++ b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues with scroll on iOS / iPad OS
+merge_request: 34486
+author:
+type: fixed
diff --git a/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml
new file mode 100644
index 00000000000..c7fec94b9fb
--- /dev/null
+++ b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml
@@ -0,0 +1,5 @@
+---
+title: Allow CI_JOB_TOKEN for authenticating to the Terraform state API
+merge_request: 34618
+author:
+type: added
diff --git a/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml
new file mode 100644
index 00000000000..7fca5a43e6c
--- /dev/null
+++ b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml
@@ -0,0 +1,5 @@
+---
+title: Fix order of integrations to be sorted alphabetically
+merge_request: 34501
+author:
+type: fixed
diff --git a/changelogs/unreleased/services-usage-2.yml b/changelogs/unreleased/services-usage-2.yml
new file mode 100644
index 00000000000..8cc4e96d520
--- /dev/null
+++ b/changelogs/unreleased/services-usage-2.yml
@@ -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
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 1b64b67d9a8..8463bbeb582 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -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) |
diff --git a/doc/user/infrastructure/index.md b/doc/user/infrastructure/index.md
index 1166e70fa56..c17d1831b50 100644
--- a/doc/user/infrastructure/index.md
+++ b/doc/user/infrastructure/index.md
@@ -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
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index 5141d1fd499..e7c9627c753 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -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
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 2b2c753cfc1..3d8ae09edf1 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -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
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index b7e78189d37..93342fbad51 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -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 ||
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 67e23624045..6593e741c1c 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -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
diff --git a/package.json b/package.json
index ae50e672dad..99614c0acc3 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/qa/qa.rb b/qa/qa.rb
index f9e31ed6a07..4649f452c6f 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -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'
diff --git a/qa/qa/flow/project.rb b/qa/qa/flow/project.rb
index 8eddd0f30b2..db42a3a3594 100644
--- a/qa/qa/flow/project.rb
+++ b/qa/qa/flow/project.rb
@@ -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
diff --git a/qa/qa/page/project/new_experiment.rb b/qa/qa/page/project/new_experiment.rb
new file mode 100644
index 00000000000..813f7f6cefe
--- /dev/null
+++ b/qa/qa/page/project/new_experiment.rb
@@ -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
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index bc276e73404..645f4e97ee0 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -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)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 9b504ad76b4..21ae10774c9 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -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
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 9ca56f58055..122d1b072d0 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -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
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index d1afcf7da0d..271b65748ea 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -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 = {
diff --git a/spec/frontend/monitoring/pages/dashboard_page_spec.js b/spec/frontend/monitoring/pages/dashboard_page_spec.js
new file mode 100644
index 00000000000..e3c56ef4cbf
--- /dev/null
+++ b/spec/frontend/monitoring/pages/dashboard_page_spec.js
@@ -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();
+ });
+});
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 64b23043dac..4def04f4284 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 774a87752b9..2aef206c7fd 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 68571b9de20..6d54342ac46 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -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)
diff --git a/spec/models/concerns/route_model_query_spec.rb b/spec/models/concerns/route_model_query_spec.rb
new file mode 100644
index 00000000000..ac58c8d44fa
--- /dev/null
+++ b/spec/models/concerns/route_model_query_spec.rb
@@ -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
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1a223b6c5bd..9ec306d297e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 4d6586c1df4..22e78c49ce5 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -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
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 3266fed1741..5e775841f12 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -3,20 +3,9 @@
require 'spec_helper'
describe 'OAuth tokens' do
- 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
+ include HttpBasicAuthHelpers
+ context 'Resource Owner Password Credentials' do
def request_oauth_token(user, headers = {})
post '/oauth/token',
params: { username: user.username, password: user.password, grant_type: 'password' },
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 844cd948411..ec9db5566e3 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -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
+ 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)
+ 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 'for a project that does not exist' do
- let(:project_id) { '0000' }
+ context 'with developer permissions' do
+ let(:current_user) { developer }
- it 'returns not found' do
+ it 'returns forbidden if the user cannot access the state' do
request
- expect(response).to have_gitlab_http_status(:not_found)
+ 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
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index ece145dcc4b..4e3d9d5f108 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -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
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index eb9594a4fb6..b1e6078c4f2 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -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)
diff --git a/spec/support/helpers/http_basic_auth_helpers.rb b/spec/support/helpers/http_basic_auth_helpers.rb
new file mode 100644
index 00000000000..c0b24b3dfa4
--- /dev/null
+++ b/spec/support/helpers/http_basic_auth_helpers.rb
@@ -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
diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/create_evidence_worker_spec.rb
index 8cba0777793..b8c622f7d1d 100644
--- a/spec/workers/create_evidence_worker_spec.rb
+++ b/spec/workers/create_evidence_worker_spec.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index d5943bfaf57..3b3766f540a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"