Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3707364380
commit
9877050db1
|
@ -1957,7 +1957,6 @@ Layout/LineLength:
|
||||||
- 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb'
|
- 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb'
|
||||||
- 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb'
|
- 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb'
|
||||||
- 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb'
|
- 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb'
|
||||||
- 'ee/spec/features/groups/members/manage_groups_spec.rb'
|
|
||||||
- 'ee/spec/features/groups/members/manage_members_spec.rb'
|
- 'ee/spec/features/groups/members/manage_members_spec.rb'
|
||||||
- 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb'
|
- 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb'
|
||||||
- 'ee/spec/features/groups/saml_providers_spec.rb'
|
- 'ee/spec/features/groups/saml_providers_spec.rb'
|
||||||
|
|
65
CHANGELOG.md
65
CHANGELOG.md
|
@ -2,6 +2,27 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 15.1.1 (2022-06-30)
|
||||||
|
|
||||||
|
### Security (16 changes)
|
||||||
|
|
||||||
|
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@0c9628791bf383734ec8f32e1d0040ca2fd62178) ([merge request](gitlab-org/security/gitlab!2550))
|
||||||
|
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@b7e06c1e812fdf0a2fab4aca07cdea33ff22b41c) ([merge request](gitlab-org/security/gitlab!2564))
|
||||||
|
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@fae2720ffd7ec5ce3eb88e3b68b2879f4f664cf4) ([merge request](gitlab-org/security/gitlab!2547))
|
||||||
|
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@071c3fa4ae63d03117a3c02752711d29f6f620b1) ([merge request](gitlab-org/security/gitlab!2492))
|
||||||
|
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@5b16b65cfe57a946f25842b7818dafe6c8a934ea) ([merge request](gitlab-org/security/gitlab!2555))
|
||||||
|
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@b3ff7ee5a64382ff9ee34bc3fc44acd0117f86d9) ([merge request](gitlab-org/security/gitlab!2532))
|
||||||
|
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@a0ad79588f170e1c58206e42d8b550d75e874a4d) ([merge request](gitlab-org/security/gitlab!2531))
|
||||||
|
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@3b7c699ffcca64721c0876da12435c148f8e83a7) ([merge request](gitlab-org/security/gitlab!2530))
|
||||||
|
- [Filter milestone release by user access](gitlab-org/security/gitlab@dc79edc16c7422279235d2ad8a4807644840fc4c) ([merge request](gitlab-org/security/gitlab!2535))
|
||||||
|
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@5221ca59f09361f90798348851fa12c91e5d9e35) ([merge request](gitlab-org/security/gitlab!2513))
|
||||||
|
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@03dfb153355d0465ea25a6d73db895c975fc32df) ([merge request](gitlab-org/security/gitlab!2538))
|
||||||
|
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@eb52b11c7b29319d16e21feec97bafbdf0f3c3e5) ([merge request](gitlab-org/security/gitlab!2542))
|
||||||
|
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@a35c6aa42c35da96bf1df263b4a3aa1fe38af75d) ([merge request](gitlab-org/security/gitlab!2508))
|
||||||
|
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@9d6f0da89f6d2e8f3c7fbccea0d22fc6b17e0305) ([merge request](gitlab-org/security/gitlab!2505))
|
||||||
|
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@bf15e9ceddf4b30105103defa50dd4a9094ac246) ([merge request](gitlab-org/security/gitlab!2516))
|
||||||
|
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d9a6ca9aa36cfd6dd916be2d4f1e8e25329ecc73) ([merge request](gitlab-org/security/gitlab!2527))
|
||||||
|
|
||||||
## 15.1.0 (2022-06-21)
|
## 15.1.0 (2022-06-21)
|
||||||
|
|
||||||
### Added (147 changes)
|
### Added (147 changes)
|
||||||
|
@ -963,6 +984,28 @@ entry.
|
||||||
- [Fix JH skipped subscription portal spec](gitlab-org/gitlab@0e7e7cb4a62d004989c47fafe6fe1f9ffd90da44) by @chaomao ([merge request](gitlab-org/gitlab!87213))
|
- [Fix JH skipped subscription portal spec](gitlab-org/gitlab@0e7e7cb4a62d004989c47fafe6fe1f9ffd90da44) by @chaomao ([merge request](gitlab-org/gitlab!87213))
|
||||||
- [Add not null constraint to requirements.issue_id validate:false](gitlab-org/gitlab@5ccac890b13c53c5761ccb8e5cb7ca202e0656c3) ([merge request](gitlab-org/gitlab!86590))
|
- [Add not null constraint to requirements.issue_id validate:false](gitlab-org/gitlab@5ccac890b13c53c5761ccb8e5cb7ca202e0656c3) ([merge request](gitlab-org/gitlab!86590))
|
||||||
|
|
||||||
|
## 15.0.4 (2022-06-30)
|
||||||
|
|
||||||
|
### Security (17 changes)
|
||||||
|
|
||||||
|
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@7dea5867ea5e115a3a91576fec91de8e7f2a9915) ([merge request](gitlab-org/security/gitlab!2551))
|
||||||
|
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@c0df8beef0297e9b99b954fcdcbf07cee3f0e9d6) ([merge request](gitlab-org/security/gitlab!2553))
|
||||||
|
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@012ff20c80754ff9ac38b82894346a51aa0a9b4c) ([merge request](gitlab-org/security/gitlab!2566))
|
||||||
|
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@619d77865f3e61f3cfb6ca92011ded44f6baf0ad) ([merge request](gitlab-org/security/gitlab!2548))
|
||||||
|
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@7b1a458df5c553d6fa99b4fec0d677c9e924ad86) ([merge request](gitlab-org/security/gitlab!2493))
|
||||||
|
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@adb8b2829e3d6b69ea32a7524c6f772be1debf82) ([merge request](gitlab-org/security/gitlab!2556))
|
||||||
|
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@4fd766e90ea6e8899897d7b7d9551b2edb5dce9a) ([merge request](gitlab-org/security/gitlab!2533))
|
||||||
|
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@d43b2c600a5fc31592eb8f07a4fcfdf3141911f7) ([merge request](gitlab-org/security/gitlab!2500))
|
||||||
|
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@aac30c9f3228efd643d3fc204ee49f740f1ebc81) ([merge request](gitlab-org/security/gitlab!2524))
|
||||||
|
- [Filter milestone release by user access](gitlab-org/security/gitlab@aa1b76b8eb2966463c8a10869e00f3320bf4ea1a) ([merge request](gitlab-org/security/gitlab!2536))
|
||||||
|
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@fa090cd9d2adab46c6c3f2a70b351a61847b5c6c) ([merge request](gitlab-org/security/gitlab!2482))
|
||||||
|
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@981be1afc7c6bf8f699ced1ae930b201699e29e3) ([merge request](gitlab-org/security/gitlab!2511))
|
||||||
|
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@fa9cf0a41f338e285701db231316897d362ce306) ([merge request](gitlab-org/security/gitlab!2541))
|
||||||
|
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@8be3da271d2a6ff3285846c50a5ce4dd584419ff) ([merge request](gitlab-org/security/gitlab!2496))
|
||||||
|
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@dcc830d14cc0ee616dc3ad263d66bd42f92b56a2) ([merge request](gitlab-org/security/gitlab!2504))
|
||||||
|
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@42425cd68755c53ed33952111be9803ce3b37515) ([merge request](gitlab-org/security/gitlab!2514))
|
||||||
|
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d2ce0a236204b97a853bc35332d49d7427f38fbc) ([merge request](gitlab-org/security/gitlab!2528))
|
||||||
|
|
||||||
## 15.0.3 (2022-06-16)
|
## 15.0.3 (2022-06-16)
|
||||||
|
|
||||||
### Fixed (2 changes)
|
### Fixed (2 changes)
|
||||||
|
@ -1737,6 +1780,28 @@ entry.
|
||||||
- [Move methods to build email unsubscribe link to helper](gitlab-org/gitlab@ae4391a84d14d51ca5b5f2ffaada96e3b37a1d51) ([merge request](gitlab-org/gitlab!84696)) **GitLab Enterprise Edition**
|
- [Move methods to build email unsubscribe link to helper](gitlab-org/gitlab@ae4391a84d14d51ca5b5f2ffaada96e3b37a1d51) ([merge request](gitlab-org/gitlab!84696)) **GitLab Enterprise Edition**
|
||||||
- [Deprecate `push_rules_supersede_code_owners` feature flag](gitlab-org/gitlab@9ee99872b66a69c5a2d1c1c9863d960832a1d91f) ([merge request](gitlab-org/gitlab!85390))
|
- [Deprecate `push_rules_supersede_code_owners` feature flag](gitlab-org/gitlab@9ee99872b66a69c5a2d1c1c9863d960832a1d91f) ([merge request](gitlab-org/gitlab!85390))
|
||||||
|
|
||||||
|
## 14.10.5 (2022-06-30)
|
||||||
|
|
||||||
|
### Security (17 changes)
|
||||||
|
|
||||||
|
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@b146ad7b8c6fba9d3c5bea365ff8afd49949dcb0) ([merge request](gitlab-org/security/gitlab!2552))
|
||||||
|
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@09ebb50ceee5a2226c1f70fa1d6c25391d51dda6) ([merge request](gitlab-org/security/gitlab!2554))
|
||||||
|
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@c91bfdb4f96e70e377a84b99c4edaa2fdecb8e16) ([merge request](gitlab-org/security/gitlab!2567))
|
||||||
|
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@6f892fb2a4b84473c3796533551f915c16cf77d9) ([merge request](gitlab-org/security/gitlab!2549))
|
||||||
|
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@153a7c447e03a509b7f06ac7381f4f9db414c9ea) ([merge request](gitlab-org/security/gitlab!2494))
|
||||||
|
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@530c7be82ae90138898ff99008d994b1c85d8cf1) ([merge request](gitlab-org/security/gitlab!2557))
|
||||||
|
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@f2c71f64c258bef9f56f4892d11a4dbf20d668e6) ([merge request](gitlab-org/security/gitlab!2534))
|
||||||
|
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@c644d94f58e30e1a9d87521b039a347412f0fead) ([merge request](gitlab-org/security/gitlab!2501))
|
||||||
|
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@8a623e8a4fdbd3421ac3ae0e37e156b7d3b04970) ([merge request](gitlab-org/security/gitlab!2525))
|
||||||
|
- [Filter milestone release by user access](gitlab-org/security/gitlab@d7d6431a52808107a71f15d29e856eef2cb313e5) ([merge request](gitlab-org/security/gitlab!2537))
|
||||||
|
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@756fb242c4d6acf6cfd95fa39f37410eaf009747) ([merge request](gitlab-org/security/gitlab!2485))
|
||||||
|
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@ca50492a32a2e367b0bc75dae0f91dc52d23b2ed) ([merge request](gitlab-org/security/gitlab!2512))
|
||||||
|
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@1450068a44d67af3cbe09fedcc4b1e9b4ea2e586) ([merge request](gitlab-org/security/gitlab!2540))
|
||||||
|
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@dafaf3e50e8b1a18ff362cbb60e9482c9d60fc33) ([merge request](gitlab-org/security/gitlab!2497))
|
||||||
|
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@0fad0cdde00b68c2a0f19ffa2681b438fcad4097) ([merge request](gitlab-org/security/gitlab!2503))
|
||||||
|
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@c5b79e969f10e3604eff16a9edef716e700cd201) ([merge request](gitlab-org/security/gitlab!2515))
|
||||||
|
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@65379002bd7a0259c425455c937b110bd96096dc) ([merge request](gitlab-org/security/gitlab!2529))
|
||||||
|
|
||||||
## 14.10.4 (2022-06-01)
|
## 14.10.4 (2022-06-01)
|
||||||
|
|
||||||
### Security (7 changes)
|
### Security (7 changes)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug'];
|
export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug'];
|
||||||
|
|
||||||
export const transformFrontendSettings = ({
|
export const transformFrontendSettings = ({
|
||||||
apiHost,
|
apiHost,
|
||||||
|
@ -9,6 +9,7 @@ export const transformFrontendSettings = ({
|
||||||
}) => {
|
}) => {
|
||||||
const project = selectedProject
|
const project = selectedProject
|
||||||
? {
|
? {
|
||||||
|
sentry_project_id: selectedProject.id,
|
||||||
slug: selectedProject.slug,
|
slug: selectedProject.slug,
|
||||||
name: selectedProject.name,
|
name: selectedProject.name,
|
||||||
organization_name: selectedProject.organizationName,
|
organization_name: selectedProject.organizationName,
|
||||||
|
|
|
@ -537,7 +537,7 @@ export default class AccessDropdown {
|
||||||
return `
|
return `
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="${isActiveClass}">
|
<a href="#" class="${isActiveClass}">
|
||||||
<strong>${key.title}</strong>
|
<strong>${escape(key.title)}</strong>
|
||||||
<p>
|
<p>
|
||||||
${sprintf(
|
${sprintf(
|
||||||
__('Owned by %{image_tag}'),
|
__('Owned by %{image_tag}'),
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
buttonText() {
|
buttonText() {
|
||||||
if (!this.hasSelectedColor()) {
|
if (!this.hasSelectedColor) {
|
||||||
return this.dropdownButtonText;
|
return this.dropdownButtonText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ export default {
|
||||||
:color="localSelectedColor.color"
|
:color="localSelectedColor.color"
|
||||||
:title="localSelectedColor.title"
|
:title="localSelectedColor.title"
|
||||||
/>
|
/>
|
||||||
<span v-else>{{ buttonText }}</span>
|
<span v-else data-testid="fallback-button-text">{{ buttonText }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<dropdown-header
|
<dropdown-header
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
before_action :authorize_read_sentry_issue!
|
before_action :authorize_read_sentry_issue!
|
||||||
|
before_action :authorize_update_sentry_issue!, only: %i[update]
|
||||||
before_action :set_issue_id, only: :details
|
before_action :set_issue_id, only: :details
|
||||||
|
|
||||||
before_action only: [:index] do
|
before_action only: [:index] do
|
||||||
|
|
|
@ -129,7 +129,7 @@ module Projects
|
||||||
:integrated,
|
:integrated,
|
||||||
:api_host,
|
:api_host,
|
||||||
:token,
|
:token,
|
||||||
project: [:slug, :name, :organization_slug, :organization_name]
|
project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id]
|
||||||
],
|
],
|
||||||
|
|
||||||
grafana_integration_attributes: [:token, :grafana_url, :enabled]
|
grafana_integration_attributes: [:token, :grafana_url, :enabled]
|
||||||
|
|
|
@ -6,19 +6,29 @@ module Ci
|
||||||
|
|
||||||
ALLOWED_INDEXED_COLUMNS = %w[id].freeze
|
ALLOWED_INDEXED_COLUMNS = %w[id].freeze
|
||||||
|
|
||||||
def initialize(runner, params = {})
|
def initialize(runner, current_user, params = {})
|
||||||
@runner = runner
|
@runner = runner
|
||||||
|
@user = current_user
|
||||||
@params = params
|
@params = params
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
items = @runner.builds
|
items = @runner.builds
|
||||||
|
items = by_permission(items)
|
||||||
items = by_status(items)
|
items = by_status(items)
|
||||||
sort_items(items)
|
sort_items(items)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
def by_permission(items)
|
||||||
|
return items if @user.can_read_all_resources?
|
||||||
|
|
||||||
|
items.for_project(@user.authorized_project_mirrors(Gitlab::Access::REPORTER).select(:project_id))
|
||||||
|
end
|
||||||
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def by_status(items)
|
def by_status(items)
|
||||||
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
||||||
|
|
|
@ -25,7 +25,7 @@ module Packages
|
||||||
end
|
end
|
||||||
|
|
||||||
def projects_visible_to_current_user
|
def projects_visible_to_current_user
|
||||||
::Project.public_or_visible_to_user(current_user)
|
::Project.public_or_visible_to_user(current_user, ::Gitlab::Access::REPORTER)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ module Resolvers
|
||||||
Should not be requested more than once per request.
|
Should not be requested more than once per request.
|
||||||
MD
|
MD
|
||||||
|
|
||||||
authorize :read_pipeline
|
authorize :create_pipeline
|
||||||
|
|
||||||
argument :project_path, GraphQL::Types::ID,
|
argument :project_path, GraphQL::Types::ID,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
|
@ -160,27 +160,6 @@ module IntegrationsHelper
|
||||||
!Gitlab.com?
|
!Gitlab.com?
|
||||||
end
|
end
|
||||||
|
|
||||||
def jira_issue_breadcrumb_link(issue_reference)
|
|
||||||
link_to '', { class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
|
|
||||||
icon = image_tag image_path('illustrations/logos/jira.svg'), width: 15, height: 15, class: 'gl-mr-2'
|
|
||||||
[icon, html_escape(issue_reference)].join.html_safe
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def zentao_issue_breadcrumb_link(issue)
|
|
||||||
link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
|
|
||||||
icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2'
|
|
||||||
[icon, html_escape(issue[:id])].join.html_safe
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def zentao_issues_show_data
|
|
||||||
{
|
|
||||||
issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json),
|
|
||||||
issues_list_path: project_integrations_zentao_issues_path(@project)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -300,6 +300,7 @@ module ProjectsHelper
|
||||||
setting.organization_slug.blank?
|
setting.organization_slug.blank?
|
||||||
|
|
||||||
{
|
{
|
||||||
|
sentry_project_id: setting.sentry_project_id,
|
||||||
name: setting.project_name,
|
name: setting.project_name,
|
||||||
organization_name: setting.organization_name,
|
organization_name: setting.organization_name,
|
||||||
organization_slug: setting.organization_slug,
|
organization_slug: setting.organization_slug,
|
||||||
|
|
|
@ -153,11 +153,11 @@ module TimeboxesHelper
|
||||||
n_("%{releases} release", "%{releases} releases", count) % { releases: count }
|
n_("%{releases} release", "%{releases} releases", count) % { releases: count }
|
||||||
end
|
end
|
||||||
|
|
||||||
def recent_releases_with_counts(milestone)
|
def recent_releases_with_counts(milestone, user)
|
||||||
total_count = milestone.releases.size
|
total_count = milestone.releases.size
|
||||||
return [[], 0, 0] if total_count == 0
|
return [[], 0, 0] if total_count == 0
|
||||||
|
|
||||||
recent_releases = milestone.releases.recent.to_a
|
recent_releases = milestone.releases.recent.filter { |release| Ability.allowed?(user, :read_release, release) }
|
||||||
more_count = total_count - recent_releases.size
|
more_count = total_count - recent_releases.size
|
||||||
[recent_releases, total_count, more_count]
|
[recent_releases, total_count, more_count]
|
||||||
end
|
end
|
||||||
|
|
|
@ -171,6 +171,11 @@ class ApplicationSetting < ApplicationRecord
|
||||||
|
|
||||||
validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' }
|
validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' }
|
||||||
|
|
||||||
|
validates :metrics_method_call_threshold,
|
||||||
|
numericality: { greater_than_or_equal_to: 0 },
|
||||||
|
presence: true,
|
||||||
|
if: :prometheus_metrics_enabled
|
||||||
|
|
||||||
validates :plantuml_url,
|
validates :plantuml_url,
|
||||||
presence: true,
|
presence: true,
|
||||||
if: :plantuml_enabled
|
if: :plantuml_enabled
|
||||||
|
|
|
@ -4,6 +4,8 @@ module Ci
|
||||||
# This model represents a shadow table of the main database's projects table.
|
# This model represents a shadow table of the main database's projects table.
|
||||||
# It allows us to navigate the project and namespace hierarchy on the ci database.
|
# It allows us to navigate the project and namespace hierarchy on the ci database.
|
||||||
class ProjectMirror < ApplicationRecord
|
class ProjectMirror < ApplicationRecord
|
||||||
|
include FromUnion
|
||||||
|
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
|
|
||||||
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }
|
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
module Clusters
|
module Clusters
|
||||||
module Applications
|
module Applications
|
||||||
class Runner < ApplicationRecord
|
class Runner < ApplicationRecord
|
||||||
VERSION = '0.42.0'
|
VERSION = '0.42.1'
|
||||||
|
|
||||||
self.table_name = 'clusters_applications_runners'
|
self.table_name = 'clusters_applications_runners'
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@ module Enums
|
||||||
bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
|
bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
|
||||||
downstream_pipeline_creation_failed: 1_007,
|
downstream_pipeline_creation_failed: 1_007,
|
||||||
secrets_provider_not_found: 1_008,
|
secrets_provider_not_found: 1_008,
|
||||||
reached_max_descendant_pipelines_depth: 1_009
|
reached_max_descendant_pipelines_depth: 1_009,
|
||||||
|
ip_restriction_failure: 1_010
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,9 +5,6 @@ class DeployToken < ApplicationRecord
|
||||||
include TokenAuthenticatable
|
include TokenAuthenticatable
|
||||||
include PolicyActor
|
include PolicyActor
|
||||||
include Gitlab::Utils::StrongMemoize
|
include Gitlab::Utils::StrongMemoize
|
||||||
include IgnorableColumns
|
|
||||||
|
|
||||||
ignore_column :token, remove_with: '15.2', remove_after: '2022-07-22'
|
|
||||||
|
|
||||||
add_authentication_token_field :token, encrypted: :required
|
add_authentication_token_field :token, encrypted: :required
|
||||||
|
|
||||||
|
|
|
@ -125,17 +125,22 @@ module ErrorTracking
|
||||||
|
|
||||||
def issue_details(opts = {})
|
def issue_details(opts = {})
|
||||||
with_reactive_cache('issue_details', opts.stringify_keys) do |result|
|
with_reactive_cache('issue_details', opts.stringify_keys) do |result|
|
||||||
|
ensure_issue_belongs_to_project!(result[:issue].project_id)
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def issue_latest_event(opts = {})
|
def issue_latest_event(opts = {})
|
||||||
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
|
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
|
||||||
|
ensure_issue_belongs_to_project!(result[:latest_event].project_id)
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_issue(opts = {})
|
def update_issue(opts = {})
|
||||||
|
issue_to_be_updated = sentry_client.issue_details(issue_id: opts[:issue_id])
|
||||||
|
ensure_issue_belongs_to_project!(issue_to_be_updated.project_id)
|
||||||
|
|
||||||
handle_exceptions do
|
handle_exceptions do
|
||||||
{ updated: sentry_client.update_issue(opts) }
|
{ updated: sentry_client.update_issue(opts) }
|
||||||
end
|
end
|
||||||
|
@ -177,6 +182,25 @@ module ErrorTracking
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def ensure_issue_belongs_to_project!(project_id_from_api)
|
||||||
|
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_sentry_project_id!
|
||||||
|
return sentry_project_id if sentry_project_id.present?
|
||||||
|
|
||||||
|
raise("Couldn't find project: #{organization_name} / #{project_name} on Sentry") if sentry_project.nil?
|
||||||
|
|
||||||
|
update!(sentry_project_id: sentry_project.id)
|
||||||
|
sentry_project_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def sentry_project
|
||||||
|
strong_memoize(:sentry_project) do
|
||||||
|
sentry_client.projects.find { |project| project.name == project_name && project.organization_name == organization_name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def add_gitlab_issue_details(issue)
|
def add_gitlab_issue_details(issue)
|
||||||
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
|
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
|
||||||
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit
|
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit
|
||||||
|
|
|
@ -1657,6 +1657,14 @@ class User < ApplicationRecord
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorized_project_mirrors(level)
|
||||||
|
projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
|
||||||
|
|
||||||
|
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
|
||||||
|
|
||||||
|
Ci::ProjectMirror.from_union([projects, namespace_projects])
|
||||||
|
end
|
||||||
|
|
||||||
def ci_owned_runners
|
def ci_owned_runners
|
||||||
@ci_owned_runners ||= begin
|
@ci_owned_runners ||= begin
|
||||||
Ci::Runner
|
Ci::Runner
|
||||||
|
@ -2113,6 +2121,10 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ServiceClass
|
# rubocop: enable CodeReuse/ServiceClass
|
||||||
|
|
||||||
|
def ci_project_mirrors_for_project_members(level)
|
||||||
|
project_members.where('access_level >= ?', level).pluck(:source_id)
|
||||||
|
end
|
||||||
|
|
||||||
def notification_email_verified
|
def notification_email_verified
|
||||||
return if notification_email.blank? || temp_oauth_email?
|
return if notification_email.blank? || temp_oauth_email?
|
||||||
|
|
||||||
|
@ -2250,7 +2262,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def ci_owned_project_runners_from_project_members
|
def ci_owned_project_runners_from_project_members
|
||||||
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id)
|
project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
|
||||||
|
|
||||||
Ci::Runner
|
Ci::Runner
|
||||||
.joins(:runner_projects)
|
.joins(:runner_projects)
|
||||||
|
|
|
@ -325,7 +325,6 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :read_deployment
|
enable :read_deployment
|
||||||
enable :read_merge_request
|
enable :read_merge_request
|
||||||
enable :read_sentry_issue
|
enable :read_sentry_issue
|
||||||
enable :update_sentry_issue
|
|
||||||
enable :read_prometheus
|
enable :read_prometheus
|
||||||
enable :read_metrics_dashboard_annotation
|
enable :read_metrics_dashboard_annotation
|
||||||
enable :metrics_dashboard
|
enable :metrics_dashboard
|
||||||
|
@ -440,6 +439,7 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :admin_feature_flags_user_lists
|
enable :admin_feature_flags_user_lists
|
||||||
enable :update_escalation_status
|
enable :update_escalation_status
|
||||||
enable :read_secure_files
|
enable :read_secure_files
|
||||||
|
enable :update_sentry_issue
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { can?(:developer_access) & user_confirmed? }.policy do
|
rule { can?(:developer_access) & user_confirmed? }.policy do
|
||||||
|
|
|
@ -33,7 +33,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
|
||||||
trace_size_exceeded: 'The job log size limit was reached',
|
trace_size_exceeded: 'The job log size limit was reached',
|
||||||
builds_disabled: 'The CI/CD is disabled for this project',
|
builds_disabled: 'The CI/CD is disabled for this project',
|
||||||
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
|
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
|
||||||
deployment_rejected: 'This deployment job was rejected.'
|
deployment_rejected: 'This deployment job was rejected.',
|
||||||
|
ip_restriction_failure: "This job could not be executed because group IP address restrictions are enabled, and the runner's IP address is not in the allowed range."
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
TROUBLESHOOTING_DOC = {
|
TROUBLESHOOTING_DOC = {
|
||||||
|
|
|
@ -16,7 +16,7 @@ class MemberUserEntity < UserEntity
|
||||||
user.blocked?
|
user.blocked?
|
||||||
end
|
end
|
||||||
|
|
||||||
expose :two_factor_enabled do |user|
|
expose :two_factor_enabled, if: -> (user) { current_user_can_manage_members? || current_user?(user) } do |user|
|
||||||
user.two_factor_enabled?
|
user.two_factor_enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,6 +25,18 @@ class MemberUserEntity < UserEntity
|
||||||
user.status.emoji
|
user.status.emoji
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def current_user_can_manage_members?
|
||||||
|
return false unless options[:source]
|
||||||
|
|
||||||
|
Ability.allowed?(options[:current_user], :"admin_#{options[:source].to_ability_name}_member", options[:source])
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user?(user)
|
||||||
|
options[:current_user] == user
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
MemberUserEntity.prepend_mod_with('MemberUserEntity')
|
MemberUserEntity.prepend_mod_with('MemberUserEntity')
|
||||||
|
|
|
@ -89,7 +89,8 @@ module Projects
|
||||||
api_url: api_url,
|
api_url: api_url,
|
||||||
enabled: settings[:enabled],
|
enabled: settings[:enabled],
|
||||||
project_name: settings.dig(:project, :name),
|
project_name: settings.dig(:project, :name),
|
||||||
organization_name: settings.dig(:project, :organization_name)
|
organization_name: settings.dig(:project, :organization_name),
|
||||||
|
sentry_project_id: settings.dig(:project, :sentry_project_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value
|
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-prometheus-settings'), html: { class: 'fieldset-form' } do |f|
|
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-prometheus-settings'), html: { class: 'fieldset-form' } do |f|
|
||||||
= form_errors(@application_setting)
|
= form_errors(@application_setting, pajamas_alert: true)
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
@ -113,6 +113,8 @@
|
||||||
= render_if_exists 'admin/application_settings/slack'
|
= render_if_exists 'admin/application_settings/slack'
|
||||||
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
|
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
|
||||||
= render_if_exists 'admin/application_settings/dingtalk_integration'
|
= render_if_exists 'admin/application_settings/dingtalk_integration'
|
||||||
|
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
|
||||||
|
= render_if_exists 'admin/application_settings/feishu_integration'
|
||||||
= render 'admin/application_settings/third_party_offers'
|
= render 'admin/application_settings/third_party_offers'
|
||||||
= render 'admin/application_settings/snowplow'
|
= render 'admin/application_settings/snowplow'
|
||||||
= render 'admin/application_settings/eks'
|
= render 'admin/application_settings/eks'
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
- if milestone.due_date || milestone.start_date
|
- if milestone.due_date || milestone.start_date
|
||||||
.text-tertiary.gl-mb-2
|
.text-tertiary.gl-mb-2
|
||||||
= milestone_date_range(milestone)
|
= milestone_date_range(milestone)
|
||||||
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
|
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
|
||||||
- unless total_count == 0
|
- unless total_count == 0
|
||||||
.text-tertiary.gl-mb-2.milestone-release-links
|
.text-tertiary.gl-mb-2.milestone-release-links
|
||||||
= sprite_icon("rocket", size: 12)
|
= sprite_icon("rocket", size: 12)
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
= milestone.merge_requests.merged.count
|
= milestone.merge_requests.merged.count
|
||||||
|
|
||||||
- if project
|
- if project
|
||||||
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
|
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
|
||||||
.block.releases
|
.block.releases
|
||||||
.sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
|
.sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
|
||||||
%strong
|
%strong
|
||||||
|
|
|
@ -1020,6 +1020,15 @@
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent: false
|
:idempotent: false
|
||||||
:tags: []
|
:tags: []
|
||||||
|
- :name: github_importer:github_import_import_issue_event
|
||||||
|
:worker_name: Gitlab::GithubImport::ImportIssueEventWorker
|
||||||
|
:feature_category: :importers
|
||||||
|
:has_external_dependencies: true
|
||||||
|
:urgency: :low
|
||||||
|
:resource_boundary: :unknown
|
||||||
|
:weight: 1
|
||||||
|
:idempotent: false
|
||||||
|
:tags: []
|
||||||
- :name: github_importer:github_import_import_lfs_object
|
- :name: github_importer:github_import_import_lfs_object
|
||||||
:worker_name: Gitlab::GithubImport::ImportLfsObjectWorker
|
:worker_name: Gitlab::GithubImport::ImportLfsObjectWorker
|
||||||
:feature_category: :importers
|
:feature_category: :importers
|
||||||
|
@ -1092,6 +1101,15 @@
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent: false
|
:idempotent: false
|
||||||
:tags: []
|
:tags: []
|
||||||
|
- :name: github_importer:github_import_stage_import_issue_events
|
||||||
|
:worker_name: Gitlab::GithubImport::Stage::ImportIssueEventsWorker
|
||||||
|
:feature_category: :importers
|
||||||
|
:has_external_dependencies: false
|
||||||
|
:urgency: :low
|
||||||
|
:resource_boundary: :unknown
|
||||||
|
:weight: 1
|
||||||
|
:idempotent: false
|
||||||
|
:tags: []
|
||||||
- :name: github_importer:github_import_stage_import_issues_and_diff_notes
|
- :name: github_importer:github_import_stage_import_issues_and_diff_notes
|
||||||
:worker_name: Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker
|
:worker_name: Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker
|
||||||
:feature_category: :importers
|
:feature_category: :importers
|
||||||
|
|
|
@ -23,6 +23,7 @@ module Gitlab
|
||||||
pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker,
|
pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker,
|
||||||
pull_request_reviews: Stage::ImportPullRequestsReviewsWorker,
|
pull_request_reviews: Stage::ImportPullRequestsReviewsWorker,
|
||||||
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
|
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
|
||||||
|
issue_events: Stage::ImportIssueEventsWorker,
|
||||||
notes: Stage::ImportNotesWorker,
|
notes: Stage::ImportNotesWorker,
|
||||||
lfs_objects: Stage::ImportLfsObjectsWorker,
|
lfs_objects: Stage::ImportLfsObjectsWorker,
|
||||||
finish: Stage::FinishImportWorker
|
finish: Stage::FinishImportWorker
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
class ImportIssueEventWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
|
include ObjectImporter
|
||||||
|
|
||||||
|
def representation_class
|
||||||
|
Representation::IssueEvent
|
||||||
|
end
|
||||||
|
|
||||||
|
def importer_class
|
||||||
|
Importer::IssueEventImporter
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:issue_event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
module Stage
|
||||||
|
class ImportIssueEventsWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
|
include ApplicationWorker
|
||||||
|
|
||||||
|
data_consistency :always
|
||||||
|
|
||||||
|
sidekiq_options retry: 3
|
||||||
|
include GithubImport::Queue
|
||||||
|
include StageMethods
|
||||||
|
|
||||||
|
# client - An instance of Gitlab::GithubImport::Client.
|
||||||
|
# project - An instance of Project.
|
||||||
|
def import(client, project)
|
||||||
|
importer = ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
|
||||||
|
return skip_to_next_stage(project, importer) if feature_disabled?(project)
|
||||||
|
|
||||||
|
start_importer(project, importer, client)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def start_importer(project, importer, client)
|
||||||
|
info(project.id, message: "starting importer", importer: importer.name)
|
||||||
|
waiter = importer.new(project, client).execute
|
||||||
|
move_to_next_stage(project, waiter.key => waiter.jobs_remaining)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_to_next_stage(project, importer)
|
||||||
|
info(project.id, message: "skipping importer", importer: importer.name)
|
||||||
|
move_to_next_stage(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_to_next_stage(project, waiters = {})
|
||||||
|
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def feature_disabled?(project)
|
||||||
|
Feature.disabled?(:github_importer_issue_events_import, project.group, type: :ops)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,7 +21,7 @@ module Gitlab
|
||||||
hash[waiter.key] = waiter.jobs_remaining
|
hash[waiter.key] = waiter.jobs_remaining
|
||||||
end
|
end
|
||||||
|
|
||||||
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
|
AdvanceStageWorker.perform_async(project.id, waiters, :issue_events)
|
||||||
end
|
end
|
||||||
|
|
||||||
# The importers to run in this stage. Issues can't be imported earlier
|
# The importers to run in this stage. Issues can't be imported earlier
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: github_importer_issue_events_import
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89134
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365977
|
||||||
|
milestone: '15.2'
|
||||||
|
type: ops
|
||||||
|
group: group::import
|
||||||
|
default_enabled: false
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Net
|
||||||
|
class HTTPResponse
|
||||||
|
# rubocop: disable Cop/LineBreakAfterGuardClauses
|
||||||
|
# rubocop: disable Cop/LineBreakAroundConditionalBlock
|
||||||
|
# rubocop: disable Layout/EmptyLineAfterGuardClause
|
||||||
|
# rubocop: disable Style/AndOr
|
||||||
|
# rubocop: disable Style/CharacterLiteral
|
||||||
|
# rubocop: disable Style/InfiniteLoop
|
||||||
|
|
||||||
|
# Original method:
|
||||||
|
# https://github.com/ruby/ruby/blob/v2_7_5/lib/net/http/response.rb#L54-L69
|
||||||
|
#
|
||||||
|
# Our changes:
|
||||||
|
# - Pass along the `start_time` to `Gitlab::BufferedIo`, so we can raise a timeout
|
||||||
|
# if reading the headers takes too long.
|
||||||
|
# - Limit the regexes to avoid ReDoS attacks.
|
||||||
|
def self.each_response_header(sock)
|
||||||
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||||
|
key = value = nil
|
||||||
|
while true
|
||||||
|
line = sock.is_a?(Gitlab::BufferedIo) ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true)
|
||||||
|
line = line.sub(/\s{0,10}\z/, '')
|
||||||
|
break if line.empty?
|
||||||
|
if line[0] == ?\s or line[0] == ?\t and value
|
||||||
|
# :nocov:
|
||||||
|
value << ' ' unless value.empty?
|
||||||
|
value << line.strip
|
||||||
|
# :nocov:
|
||||||
|
else
|
||||||
|
yield key, value if key
|
||||||
|
key, value = line.strip.split(/\s{0,10}:\s{0,10}/, 2)
|
||||||
|
raise Net::HTTPBadResponse, 'wrong header line format' if value.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
yield key, value if key
|
||||||
|
end
|
||||||
|
# rubocop: enable Cop/LineBreakAfterGuardClauses
|
||||||
|
# rubocop: enable Cop/LineBreakAroundConditionalBlock
|
||||||
|
# rubocop: enable Layout/EmptyLineAfterGuardClause
|
||||||
|
# rubocop: enable Style/AndOr
|
||||||
|
# rubocop: enable Style/CharacterLiteral
|
||||||
|
# rubocop: enable Style/InfiniteLoop
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddInstallableConanPackagesIndexToPackages < Gitlab::Database::Migration[2.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
INDEX_NAME = 'idx_installable_conan_pkgs_on_project_id_id'
|
||||||
|
# as defined by Packages::Package.package_types
|
||||||
|
CONAN_PACKAGE_TYPE = 3
|
||||||
|
|
||||||
|
# as defined by Packages::Package::INSTALLABLE_STATUSES
|
||||||
|
DEFAULT_STATUS = 0
|
||||||
|
HIDDEN_STATUS = 1
|
||||||
|
|
||||||
|
def up
|
||||||
|
where = "package_type = #{CONAN_PACKAGE_TYPE} AND status IN (#{DEFAULT_STATUS}, #{HIDDEN_STATUS})"
|
||||||
|
add_concurrent_index :packages_packages,
|
||||||
|
[:project_id, :id],
|
||||||
|
where: where,
|
||||||
|
name: INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index_by_name :packages_packages, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSentryProjectIdToProjectErrorTrackingSettings < Gitlab::Database::Migration[2.0]
|
||||||
|
def change
|
||||||
|
add_column :project_error_tracking_settings, :sentry_project_id, :bigint
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddFeiShuIntegration < Gitlab::Database::Migration[2.0]
|
||||||
|
def change
|
||||||
|
add_column :application_settings, :feishu_integration_enabled, :boolean, null: false,
|
||||||
|
default: false, comment: 'JiHu-specific column'
|
||||||
|
add_column :application_settings, :encrypted_feishu_app_key, :binary, comment: 'JiHu-specific column'
|
||||||
|
add_column :application_settings, :encrypted_feishu_app_key_iv, :binary, comment: 'JiHu-specific column'
|
||||||
|
add_column :application_settings, :encrypted_feishu_app_secret, :binary, comment: 'JiHu-specific column'
|
||||||
|
add_column :application_settings, :encrypted_feishu_app_secret_iv, :binary, comment: 'JiHu-specific column'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
1fdb60b1c72b687aa8bede083ac7038097d538dc815e334d74296b1d39c2acb8
|
|
@ -0,0 +1 @@
|
||||||
|
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5
|
|
@ -0,0 +1 @@
|
||||||
|
80c35cd4dbc2e00e721ccb9313ff0f2f4f85e781c7961680e14769c308f067ed
|
|
@ -11324,6 +11324,11 @@ CREATE TABLE application_settings (
|
||||||
phone_verification_code_enabled boolean DEFAULT false NOT NULL,
|
phone_verification_code_enabled boolean DEFAULT false NOT NULL,
|
||||||
max_number_of_repository_downloads smallint DEFAULT 0 NOT NULL,
|
max_number_of_repository_downloads smallint DEFAULT 0 NOT NULL,
|
||||||
max_number_of_repository_downloads_within_time_period integer DEFAULT 0 NOT NULL,
|
max_number_of_repository_downloads_within_time_period integer DEFAULT 0 NOT NULL,
|
||||||
|
feishu_integration_enabled boolean DEFAULT false NOT NULL,
|
||||||
|
encrypted_feishu_app_key bytea,
|
||||||
|
encrypted_feishu_app_key_iv bytea,
|
||||||
|
encrypted_feishu_app_secret bytea,
|
||||||
|
encrypted_feishu_app_secret_iv bytea,
|
||||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||||
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
||||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||||
|
@ -11386,6 +11391,16 @@ COMMENT ON COLUMN application_settings.encrypted_dingtalk_app_secret_iv IS 'JiHu
|
||||||
|
|
||||||
COMMENT ON COLUMN application_settings.phone_verification_code_enabled IS 'JiHu-specific column';
|
COMMENT ON COLUMN application_settings.phone_verification_code_enabled IS 'JiHu-specific column';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN application_settings.feishu_integration_enabled IS 'JiHu-specific column';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key IS 'JiHu-specific column';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key_iv IS 'JiHu-specific column';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret IS 'JiHu-specific column';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret_iv IS 'JiHu-specific column';
|
||||||
|
|
||||||
CREATE SEQUENCE application_settings_id_seq
|
CREATE SEQUENCE application_settings_id_seq
|
||||||
START WITH 1
|
START WITH 1
|
||||||
INCREMENT BY 1
|
INCREMENT BY 1
|
||||||
|
@ -19341,7 +19356,8 @@ CREATE TABLE project_error_tracking_settings (
|
||||||
encrypted_token_iv character varying,
|
encrypted_token_iv character varying,
|
||||||
project_name character varying,
|
project_name character varying,
|
||||||
organization_name character varying,
|
organization_name character varying,
|
||||||
integrated boolean DEFAULT true NOT NULL
|
integrated boolean DEFAULT true NOT NULL,
|
||||||
|
sentry_project_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE project_export_jobs (
|
CREATE TABLE project_export_jobs (
|
||||||
|
@ -26973,6 +26989,8 @@ CREATE UNIQUE INDEX idx_external_audit_event_destination_id_key_uniq ON audit_ev
|
||||||
|
|
||||||
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
|
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_installable_conan_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id) WHERE ((package_type = 3) AND (status = ANY (ARRAY[0, 1])));
|
||||||
|
|
||||||
CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id);
|
CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id);
|
||||||
|
|
||||||
CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0));
|
CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0));
|
||||||
|
|
|
@ -269,6 +269,9 @@ control over how the Pages daemon runs and serves content in your environment.
|
||||||
| `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/659) in GitLab 14.5.
|
| `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/659) in GitLab 14.5.
|
||||||
| `metrics_address` | The address to listen on for metrics requests. |
|
| `metrics_address` | The address to listen on for metrics requests. |
|
||||||
| `redirect_http` | Redirect pages from HTTP to HTTPS, true/false. |
|
| `redirect_http` | Redirect pages from HTTP to HTTPS, true/false. |
|
||||||
|
| `redirects_max_config_size` | The maximum size of the _redirects file, in bytes (default: 65536). |
|
||||||
|
| `redirects_max_path_segments` | The maximum number of path segments allowed in _redirects rules URLs (default: 25). |
|
||||||
|
| `redirects_max_rule_count` | The maximum number of rules allowed in _redirects (default: 1000). |
|
||||||
| `sentry_dsn` | The address for sending Sentry crash reporting to. |
|
| `sentry_dsn` | The address for sending Sentry crash reporting to. |
|
||||||
| `sentry_enabled` | Enable reporting and logging with Sentry, true/false. |
|
| `sentry_enabled` | Enable reporting and logging with Sentry, true/false. |
|
||||||
| `sentry_environment` | The environment for Sentry crash reporting. |
|
| `sentry_environment` | The environment for Sentry crash reporting. |
|
||||||
|
@ -577,6 +580,19 @@ HTTP Strict Transport Security (HSTS) can be enabled through the `gitlab_pages['
|
||||||
gitlab_pages['headers'] = ['Strict-Transport-Security: max-age=63072000']
|
gitlab_pages['headers'] = ['Strict-Transport-Security: max-age=63072000']
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Pages project redirects limits
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/778) in GitLab 15.2.
|
||||||
|
|
||||||
|
GitLab Pages comes with a set of default limits for the [`_redirects` file](../../user/project/pages/redirects.md)
|
||||||
|
to minimize the impact on performance. You can configure these limits if you'd like to increase or decrease the limits.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gitlab_pages['redirects_max_config_size'] = 131072
|
||||||
|
gitlab_pages['redirects_max_path_segments'] = 50
|
||||||
|
gitlab_pages['redirects_max_rule_count'] = 2000
|
||||||
|
```
|
||||||
|
|
||||||
## Activate verbose logging for daemon
|
## Activate verbose logging for daemon
|
||||||
|
|
||||||
Follow the steps below to configure verbose logging of GitLab Pages daemon.
|
Follow the steps below to configure verbose logging of GitLab Pages daemon.
|
||||||
|
|
|
@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
|
||||||
|
|
||||||
List jobs that are being processed or were processed by specified runner.
|
List jobs that are being processed or were processed by the specified runner. The list of jobs is limited
|
||||||
|
to projects where the user has at least the Reporter role.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
GET /runners/:id/jobs
|
GET /runners/:id/jobs
|
||||||
|
|
|
@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section.
|
||||||
|
|
||||||
## Taking Action on errors
|
## Taking Action on errors
|
||||||
|
|
||||||
You can take action on Sentry Errors from within the GitLab UI.
|
You can take action on Sentry Errors from within the GitLab UI. Marking errors ignored or resolved require at least Developer role.
|
||||||
|
|
||||||
### Ignoring errors
|
### Ignoring errors
|
||||||
|
|
||||||
|
|
|
@ -480,6 +480,9 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
|
||||||
1. Ensure all GitLab web nodes are running GitLab 15.1.Z.
|
1. Ensure all GitLab web nodes are running GitLab 15.1.Z.
|
||||||
1. [Enable the `active_support_hash_digest_sha256` feature flag](../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) to switch `ActiveSupport::Digest` to use SHA256:
|
1. [Enable the `active_support_hash_digest_sha256` feature flag](../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) to switch `ActiveSupport::Digest` to use SHA256:
|
||||||
1. Only then, continue to upgrade to later versions of GitLab.
|
1. Only then, continue to upgrade to later versions of GitLab.
|
||||||
|
- Unauthenticated requests to the [`ciConfig` GraphQL field](../api/graphql/reference/index.md#queryciconfig) are no longer supported.
|
||||||
|
Before you upgrade to GitLab 15.1, add an [access token](../api/index.md#authentication) to your requests.
|
||||||
|
The user creating the token must have [permission](../user/permissions.md) to create pipelines in the project.
|
||||||
|
|
||||||
### 15.0.0
|
### 15.0.0
|
||||||
|
|
||||||
|
|
|
@ -649,6 +649,7 @@ at the group level.
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
|
||||||
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
|
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
|
||||||
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
|
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
|
||||||
|
> - Support for restricting group memberships to groups with a subset of the allowed email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/354791) in GitLab 15.0.1
|
||||||
|
|
||||||
You can prevent users with email addresses in specific domains from being added to a group and its projects.
|
You can prevent users with email addresses in specific domains from being added to a group and its projects.
|
||||||
|
|
||||||
|
@ -669,6 +670,8 @@ The most popular public email domains cannot be restricted, such as:
|
||||||
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
|
||||||
- `msn.com`, `live.com`, `outlook.com`
|
- `msn.com`, `live.com`, `outlook.com`
|
||||||
|
|
||||||
|
When you share a group, both the source and target namespaces must allow the domains of the members' email addresses.
|
||||||
|
|
||||||
## Restrict Git access protocols
|
## Restrict Git access protocols
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default.
|
||||||
|
|
|
@ -45,8 +45,9 @@ Note that:
|
||||||
|
|
||||||
- All paths must start with a forward slash `/`.
|
- All paths must start with a forward slash `/`.
|
||||||
- A default status code of `301` is applied if no [status code](#http-status-codes) is provided.
|
- A default status code of `301` is applied if no [status code](#http-status-codes) is provided.
|
||||||
- The `_redirects` file has a file size limit of 64KB and a maximum of 1,000 rules per project.
|
- The `_redirects` file has a file size limit and a maximum number of rules per project,
|
||||||
Only the first 1,000 rules are processed.
|
configured at the instance level. Only the first matching rules within the configured maximum are processed.
|
||||||
|
The default file size limit is 64KB, and the default maximum number of rules is 1,000.
|
||||||
- If your GitLab Pages site uses the default domain name (such as
|
- If your GitLab Pages site uses the default domain name (such as
|
||||||
`namespace.gitlab.io/projectname`) you must prefix every rule with the project name:
|
`namespace.gitlab.io/projectname`) you must prefix every rule with the project name:
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ module API
|
||||||
runner = get_runner(params[:id])
|
runner = get_runner(params[:id])
|
||||||
authenticate_list_runners_jobs!(runner)
|
authenticate_list_runners_jobs!(runner)
|
||||||
|
|
||||||
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute
|
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
|
||||||
|
|
||||||
present paginate(jobs), with: Entities::Ci::JobBasicWithProject
|
present paginate(jobs), with: Entities::Ci::JobBasicWithProject
|
||||||
end
|
end
|
||||||
|
|
|
@ -82,8 +82,14 @@ module API
|
||||||
params.delete(:label_id)
|
params.delete(:label_id)
|
||||||
params.delete(:name)
|
params.delete(:name)
|
||||||
|
|
||||||
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
|
update_params = declared_params(include_missing: false)
|
||||||
render_validation_error!(label) unless label.valid?
|
|
||||||
|
if update_params.present?
|
||||||
|
authorize! :admin_label, label
|
||||||
|
|
||||||
|
label = ::Labels::UpdateService.new(update_params).execute(label)
|
||||||
|
render_validation_error!(label) unless label.valid?
|
||||||
|
end
|
||||||
|
|
||||||
if parent.is_a?(Project) && update_priority
|
if parent.is_a?(Project) && update_priority
|
||||||
if priority.nil?
|
if priority.nil?
|
||||||
|
@ -97,10 +103,10 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_label(parent)
|
def delete_label(parent)
|
||||||
authorize! :admin_label, parent
|
|
||||||
|
|
||||||
label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
|
label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
|
||||||
|
|
||||||
|
authorize! :admin_label, label
|
||||||
|
|
||||||
destroy_conditionally!(label)
|
destroy_conditionally!(label)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,20 +10,8 @@ module BulkImports
|
||||||
<<-'GRAPHQL'
|
<<-'GRAPHQL'
|
||||||
query($full_path: ID!) {
|
query($full_path: ID!) {
|
||||||
project(fullPath: $full_path) {
|
project(fullPath: $full_path) {
|
||||||
description
|
|
||||||
visibility
|
visibility
|
||||||
archived
|
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
shared_runners_enabled: sharedRunnersEnabled
|
|
||||||
container_registry_enabled: containerRegistryEnabled
|
|
||||||
only_allow_merge_if_pipeline_succeeds: onlyAllowMergeIfPipelineSucceeds
|
|
||||||
only_allow_merge_if_all_discussions_are_resolved: onlyAllowMergeIfAllDiscussionsAreResolved
|
|
||||||
request_access_enabled: requestAccessEnabled
|
|
||||||
printing_merge_request_link_enabled: printingMergeRequestLinkEnabled
|
|
||||||
remove_source_branch_after_merge: removeSourceBranchAfterMerge
|
|
||||||
autoclose_referenced_issues: autocloseReferencedIssues
|
|
||||||
suggestion_commit_message: suggestionCommitMessage
|
|
||||||
wiki_enabled: wikiEnabled
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GRAPHQL
|
GRAPHQL
|
||||||
|
|
|
@ -7,16 +7,18 @@ module BulkImports
|
||||||
PROJECT_IMPORT_TYPE = 'gitlab_project_migration'
|
PROJECT_IMPORT_TYPE = 'gitlab_project_migration'
|
||||||
|
|
||||||
def transform(context, data)
|
def transform(context, data)
|
||||||
|
project = {}
|
||||||
entity = context.entity
|
entity = context.entity
|
||||||
visibility = data.delete('visibility')
|
visibility = data.delete('visibility')
|
||||||
|
|
||||||
data['name'] = entity.destination_name
|
project[:name] = entity.destination_name
|
||||||
data['path'] = entity.destination_name.parameterize
|
project[:path] = entity.destination_name.parameterize
|
||||||
data['import_type'] = PROJECT_IMPORT_TYPE
|
project[:created_at] = data['created_at']
|
||||||
data['visibility_level'] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
|
project[:import_type] = PROJECT_IMPORT_TYPE
|
||||||
data['namespace_id'] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
|
project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
|
||||||
|
project[:namespace_id] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
|
||||||
|
|
||||||
data.transform_keys!(&:to_sym)
|
project
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ module ErrorTracking
|
||||||
stack_trace = parse_stack_trace(event)
|
stack_trace = parse_stack_trace(event)
|
||||||
|
|
||||||
Gitlab::ErrorTracking::ErrorEvent.new(
|
Gitlab::ErrorTracking::ErrorEvent.new(
|
||||||
|
project_id: event['projectID'],
|
||||||
issue_id: event['groupID'],
|
issue_id: event['groupID'],
|
||||||
date_received: event['dateReceived'],
|
date_received: event['dateReceived'],
|
||||||
stack_trace_entries: stack_trace
|
stack_trace_entries: stack_trace
|
||||||
|
|
|
@ -14,6 +14,7 @@ module Gitlab
|
||||||
|
|
||||||
HEADER_READ_TIMEOUT = 20
|
HEADER_READ_TIMEOUT = 20
|
||||||
|
|
||||||
|
# rubocop: disable Style/RedundantBegin
|
||||||
# rubocop: disable Style/RedundantReturn
|
# rubocop: disable Style/RedundantReturn
|
||||||
# rubocop: disable Cop/LineBreakAfterGuardClauses
|
# rubocop: disable Cop/LineBreakAfterGuardClauses
|
||||||
# rubocop: disable Layout/EmptyLineAfterGuardClause
|
# rubocop: disable Layout/EmptyLineAfterGuardClause
|
||||||
|
@ -21,9 +22,7 @@ module Gitlab
|
||||||
# Original method:
|
# Original method:
|
||||||
# https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200
|
# https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200
|
||||||
override :readuntil
|
override :readuntil
|
||||||
def readuntil(terminator, ignore_eof = false)
|
def readuntil(terminator, ignore_eof = false, start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
||||||
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
until idx = @rbuf.index(terminator)
|
until idx = @rbuf.index(terminator)
|
||||||
if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT
|
if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT
|
||||||
|
@ -39,6 +38,7 @@ module Gitlab
|
||||||
return rbuf_consume(@rbuf.size)
|
return rbuf_consume(@rbuf.size)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop: disable Style/RedundantBegin
|
||||||
# rubocop: enable Style/RedundantReturn
|
# rubocop: enable Style/RedundantReturn
|
||||||
# rubocop: enable Cop/LineBreakAfterGuardClauses
|
# rubocop: enable Cop/LineBreakAfterGuardClauses
|
||||||
# rubocop: enable Layout/EmptyLineAfterGuardClause
|
# rubocop: enable Layout/EmptyLineAfterGuardClause
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Gitlab
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def check_runner_upgrade_status(runner_version)
|
def check_runner_upgrade_status(runner_version)
|
||||||
runner_version = ::Gitlab::VersionInfo.parse(runner_version) unless runner_version.is_a?(::Gitlab::VersionInfo)
|
runner_version = ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true)
|
||||||
|
|
||||||
return :invalid unless runner_version.valid?
|
return :invalid unless runner_version.valid?
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@ module Gitlab
|
||||||
trace_size_exceeded: 'log size limit exceeded',
|
trace_size_exceeded: 'log size limit exceeded',
|
||||||
builds_disabled: 'project builds are disabled',
|
builds_disabled: 'project builds are disabled',
|
||||||
environment_creation_failure: 'environment creation failure',
|
environment_creation_failure: 'environment creation failure',
|
||||||
deployment_rejected: 'deployment rejected'
|
deployment_rejected: 'deployment rejected',
|
||||||
|
ip_restriction_failure: 'IP address restriction failure'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
private_constant :REASONS
|
private_constant :REASONS
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Gitlab
|
||||||
class ErrorEvent
|
class ErrorEvent
|
||||||
include ActiveModel::Model
|
include ActiveModel::Model
|
||||||
|
|
||||||
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project
|
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project, :project_id
|
||||||
|
|
||||||
def self.declarative_policy_class
|
def self.declarative_policy_class
|
||||||
'ErrorTracking::BasePolicy'
|
'ErrorTracking::BasePolicy'
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
module Importer
|
||||||
|
module Events
|
||||||
|
class Closed
|
||||||
|
attr_reader :project, :user_id
|
||||||
|
|
||||||
|
def initialize(project, user_id)
|
||||||
|
@project = project
|
||||||
|
@user_id = user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
|
||||||
|
def execute(issue_event)
|
||||||
|
create_event(issue_event)
|
||||||
|
create_state_event(issue_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_event(issue_event)
|
||||||
|
Event.create!(
|
||||||
|
project_id: project.id,
|
||||||
|
author_id: user_id,
|
||||||
|
action: 'closed',
|
||||||
|
target_type: Issue.name,
|
||||||
|
target_id: issue_event.issue_db_id,
|
||||||
|
created_at: issue_event.created_at,
|
||||||
|
updated_at: issue_event.created_at
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_state_event(issue_event)
|
||||||
|
ResourceStateEvent.create!(
|
||||||
|
user_id: user_id,
|
||||||
|
issue_id: issue_event.issue_db_id,
|
||||||
|
source_commit: issue_event.commit_id,
|
||||||
|
state: ResourceStateEvent.states[:closed],
|
||||||
|
close_after_error_tracking_resolve: false,
|
||||||
|
close_auto_resolve_prometheus_alert: false,
|
||||||
|
created_at: issue_event.created_at
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
module Importer
|
||||||
|
class IssueEventImporter
|
||||||
|
attr_reader :issue_event, :project, :client, :user_finder
|
||||||
|
|
||||||
|
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
|
||||||
|
# project - An instance of `Project`.
|
||||||
|
# client - An instance of `Gitlab::GithubImport::Client`.
|
||||||
|
def initialize(issue_event, project, client)
|
||||||
|
@issue_event = issue_event
|
||||||
|
@project = project
|
||||||
|
@client = client
|
||||||
|
@user_finder = UserFinder.new(project, client)
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
case issue_event.event
|
||||||
|
when 'closed'
|
||||||
|
Gitlab::GithubImport::Importer::Events::Closed.new(project, author_id)
|
||||||
|
.execute(issue_event)
|
||||||
|
else
|
||||||
|
Gitlab::GithubImport::Logger.debug(
|
||||||
|
message: 'UNSUPPORTED_EVENT_TYPE',
|
||||||
|
event_type: issue_event.event, event_github_id: issue_event.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def author_id
|
||||||
|
id, _status = user_finder.author_id_for(issue_event, author_key: :actor)
|
||||||
|
id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,15 +37,15 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def noteables
|
def parent_collection
|
||||||
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
|
project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_counter_id(merge_request)
|
def page_counter_id(merge_request)
|
||||||
"merge_request/#{merge_request.id}/#{collection_method}"
|
"merge_request/#{merge_request.id}/#{collection_method}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def notes_imported_cache_key
|
def parent_imported_cache_key
|
||||||
"github-importer/merge_request/diff_notes/already-imported/#{project.id}"
|
"github-importer/merge_request/diff_notes/already-imported/#{project.id}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
module Importer
|
||||||
|
class SingleEndpointIssueEventsImporter
|
||||||
|
include ParallelScheduling
|
||||||
|
include SingleEndpointNotesImporting
|
||||||
|
|
||||||
|
PROCESSED_PAGE_CACHE_KEY = 'issues/%{issue_iid}/%{collection}'
|
||||||
|
BATCH_SIZE = 100
|
||||||
|
|
||||||
|
def initialize(project, client, parallel: true)
|
||||||
|
@project = project
|
||||||
|
@client = client
|
||||||
|
@parallel = parallel
|
||||||
|
@already_imported_cache_key = ALREADY_IMPORTED_CACHE_KEY %
|
||||||
|
{ project: project.id, collection: collection_method }
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_associated(parent_record, associated)
|
||||||
|
return if already_imported?(associated)
|
||||||
|
|
||||||
|
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
|
||||||
|
|
||||||
|
associated.issue_db_id = parent_record.id
|
||||||
|
yield(associated)
|
||||||
|
|
||||||
|
mark_as_imported(associated)
|
||||||
|
end
|
||||||
|
|
||||||
|
def importer_class
|
||||||
|
IssueEventImporter
|
||||||
|
end
|
||||||
|
|
||||||
|
def representation_class
|
||||||
|
Representation::IssueEvent
|
||||||
|
end
|
||||||
|
|
||||||
|
def sidekiq_worker_class
|
||||||
|
ImportIssueEventWorker
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:issue_event
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection_method
|
||||||
|
:issue_timeline
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent_collection
|
||||||
|
project.issues.where.not(iid: already_imported_parents).select(:id, :iid) # rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent_imported_cache_key
|
||||||
|
"github-importer/issues/#{collection_method}/already-imported/#{project.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_counter_id(issue)
|
||||||
|
PROCESSED_PAGE_CACHE_KEY % { issue_iid: issue.iid, collection: collection_method }
|
||||||
|
end
|
||||||
|
|
||||||
|
def id_for_already_imported_cache(event)
|
||||||
|
event.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection_options
|
||||||
|
{ state: 'all', sort: 'created', direction: 'asc' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,15 +37,15 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def noteables
|
def parent_collection
|
||||||
project.issues.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
|
project.issues.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_counter_id(issue)
|
def page_counter_id(issue)
|
||||||
"issue/#{issue.id}/#{collection_method}"
|
"issue/#{issue.id}/#{collection_method}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def notes_imported_cache_key
|
def parent_imported_cache_key
|
||||||
"github-importer/issue/notes/already-imported/#{project.id}"
|
"github-importer/issue/notes/already-imported/#{project.id}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,15 +37,15 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def noteables
|
def parent_collection
|
||||||
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
|
project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_counter_id(merge_request)
|
def page_counter_id(merge_request)
|
||||||
"merge_request/#{merge_request.id}/#{collection_method}"
|
"merge_request/#{merge_request.id}/#{collection_method}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def notes_imported_cache_key
|
def parent_imported_cache_key
|
||||||
"github-importer/merge_request/notes/already-imported/#{project.id}"
|
"github-importer/merge_request/notes/already-imported/#{project.id}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GithubImport
|
||||||
|
module Representation
|
||||||
|
class IssueEvent
|
||||||
|
include ToHash
|
||||||
|
include ExposeAttribute
|
||||||
|
|
||||||
|
attr_reader :attributes
|
||||||
|
|
||||||
|
expose_attribute :id, :actor, :event, :commit_id, :created_at
|
||||||
|
expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
|
||||||
|
|
||||||
|
# Builds a event from a GitHub API response.
|
||||||
|
#
|
||||||
|
# event - An instance of `Sawyer::Resource` containing the event details.
|
||||||
|
def self.from_api_response(event)
|
||||||
|
new(
|
||||||
|
id: event.id,
|
||||||
|
actor: event.actor && Representation::User.from_api_response(event.actor),
|
||||||
|
event: event.event,
|
||||||
|
commit_id: event.commit_id,
|
||||||
|
issue_db_id: event.issue_db_id,
|
||||||
|
created_at: event.created_at
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Builds a event using a Hash that was built from a JSON payload.
|
||||||
|
def self.from_json_hash(raw_hash)
|
||||||
|
hash = Representation.symbolize_hash(raw_hash)
|
||||||
|
hash[:actor] = Representation::User.from_json_hash(hash[:actor]) if hash[:actor]
|
||||||
|
|
||||||
|
new(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
# attributes - A Hash containing the event details. The keys of this
|
||||||
|
# Hash (and any nested hashes) must be symbols.
|
||||||
|
def initialize(attributes)
|
||||||
|
@attributes = attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def github_identifiers
|
||||||
|
{ id: id }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,9 +4,14 @@
|
||||||
# - SingleEndpointDiffNotesImporter
|
# - SingleEndpointDiffNotesImporter
|
||||||
# - SingleEndpointIssueNotesImporter
|
# - SingleEndpointIssueNotesImporter
|
||||||
# - SingleEndpointMergeRequestNotesImporter
|
# - SingleEndpointMergeRequestNotesImporter
|
||||||
|
# if `github_importer_single_endpoint_notes_import` feature flag is on.
|
||||||
#
|
#
|
||||||
# `github_importer_single_endpoint_notes_import`
|
# - SingleEndpointIssueEventsImporter
|
||||||
# feature flag is on.
|
# if `github_importer_issue_events_import` feature flag is on.
|
||||||
|
#
|
||||||
|
# Fetches associated objects page by page to each item of parent collection.
|
||||||
|
# Currently `associated` is note or event.
|
||||||
|
# Currently `parent` is MergeRequest or Issue record.
|
||||||
#
|
#
|
||||||
# It fetches 1 PR's associated objects at a time using `issue_comments` or
|
# It fetches 1 PR's associated objects at a time using `issue_comments` or
|
||||||
# `pull_request_comments` endpoint, which is slower than `NotesImporter`
|
# `pull_request_comments` endpoint, which is slower than `NotesImporter`
|
||||||
|
@ -18,67 +23,75 @@ module Gitlab
|
||||||
module SingleEndpointNotesImporting
|
module SingleEndpointNotesImporting
|
||||||
BATCH_SIZE = 100
|
BATCH_SIZE = 100
|
||||||
|
|
||||||
def each_object_to_import
|
def each_object_to_import(&block)
|
||||||
each_notes_page do |page|
|
each_associated_page do |parent_record, associated_page|
|
||||||
page.objects.each do |note|
|
associated_page.objects.each do |associated|
|
||||||
next if already_imported?(note)
|
each_associated(parent_record, associated, &block)
|
||||||
|
|
||||||
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
|
|
||||||
|
|
||||||
yield(note)
|
|
||||||
|
|
||||||
mark_as_imported(note)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def id_for_already_imported_cache(note)
|
def id_for_already_imported_cache(associated)
|
||||||
note.id
|
associated.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent_collection
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent_imported_cache_key
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_counter_id(parent)
|
||||||
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def each_notes_page
|
# Sometimes we need to add some extra info from parent
|
||||||
noteables.each_batch(of: BATCH_SIZE, column: :iid) do |batch|
|
# to associated record that is not available by default
|
||||||
batch.each do |noteable|
|
# in Github API response object. For example:
|
||||||
# The page counter needs to be scoped by noteable to avoid skipping
|
# lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb:26
|
||||||
# pages of notes from already imported noteables.
|
def each_associated(_parent_record, associated)
|
||||||
page_counter = PageCounter.new(project, page_counter_id(noteable))
|
return if already_imported?(associated)
|
||||||
|
|
||||||
|
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
|
||||||
|
|
||||||
|
yield(associated)
|
||||||
|
|
||||||
|
mark_as_imported(associated)
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_associated_page
|
||||||
|
parent_collection.each_batch(of: BATCH_SIZE, column: :iid) do |batch|
|
||||||
|
batch.each do |parent_record|
|
||||||
|
# The page counter needs to be scoped by parent_record to avoid skipping
|
||||||
|
# pages of notes from already imported parent_record.
|
||||||
|
page_counter = PageCounter.new(project, page_counter_id(parent_record))
|
||||||
repo = project.import_source
|
repo = project.import_source
|
||||||
options = collection_options.merge(page: page_counter.current)
|
options = collection_options.merge(page: page_counter.current)
|
||||||
|
|
||||||
client.each_page(collection_method, repo, noteable.iid, options) do |page|
|
client.each_page(collection_method, repo, parent_record.iid, options) do |page|
|
||||||
next unless page_counter.set(page.number)
|
next unless page_counter.set(page.number)
|
||||||
|
|
||||||
yield page
|
yield parent_record, page
|
||||||
end
|
end
|
||||||
|
|
||||||
mark_notes_imported(noteable)
|
mark_parent_imported(parent_record)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_notes_imported(noteable)
|
def mark_parent_imported(parent)
|
||||||
Gitlab::Cache::Import::Caching.set_add(
|
Gitlab::Cache::Import::Caching.set_add(
|
||||||
notes_imported_cache_key,
|
parent_imported_cache_key,
|
||||||
noteable.iid
|
parent.iid
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def already_imported_noteables
|
def already_imported_parents
|
||||||
Gitlab::Cache::Import::Caching.values_from_set(notes_imported_cache_key)
|
Gitlab::Cache::Import::Caching.values_from_set(parent_imported_cache_key)
|
||||||
end
|
|
||||||
|
|
||||||
def noteables
|
|
||||||
NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def notes_imported_cache_key
|
|
||||||
NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def page_counter_id(noteable)
|
|
||||||
NotImplementedError
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,13 +39,9 @@ module Gitlab
|
||||||
#
|
#
|
||||||
# If the object has no author ID we'll use the ID of the GitLab ghost
|
# If the object has no author ID we'll use the ID of the GitLab ghost
|
||||||
# user.
|
# user.
|
||||||
def author_id_for(object)
|
def author_id_for(object, author_key: :author)
|
||||||
id =
|
user_info = author_key == :actor ? object&.actor : object&.author
|
||||||
if object&.author
|
id = user_info ? user_id_for(user_info) : GithubImport.ghost_user_id
|
||||||
user_id_for(object.author)
|
|
||||||
else
|
|
||||||
GithubImport.ghost_user_id
|
|
||||||
end
|
|
||||||
|
|
||||||
if id
|
if id
|
||||||
[id, true]
|
[id, true]
|
||||||
|
|
|
@ -8,6 +8,8 @@ module Gitlab
|
||||||
DEFAULT_MAX_BYTES = 10.gigabytes.freeze
|
DEFAULT_MAX_BYTES = 10.gigabytes.freeze
|
||||||
TIMEOUT_LIMIT = 210.seconds
|
TIMEOUT_LIMIT = 210.seconds
|
||||||
|
|
||||||
|
ServiceError = Class.new(StandardError)
|
||||||
|
|
||||||
def initialize(archive_path:, max_bytes: self.class.max_bytes)
|
def initialize(archive_path:, max_bytes: self.class.max_bytes)
|
||||||
@archive_path = archive_path
|
@archive_path = archive_path
|
||||||
@max_bytes = max_bytes
|
@max_bytes = max_bytes
|
||||||
|
@ -29,6 +31,8 @@ module Gitlab
|
||||||
pgrp = nil
|
pgrp = nil
|
||||||
valid_archive = true
|
valid_archive = true
|
||||||
|
|
||||||
|
validate_archive_path
|
||||||
|
|
||||||
Timeout.timeout(TIMEOUT_LIMIT) do
|
Timeout.timeout(TIMEOUT_LIMIT) do
|
||||||
stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
|
stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
|
||||||
stdin.close
|
stdin.close
|
||||||
|
@ -78,15 +82,29 @@ module Gitlab
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_archive_path
|
||||||
|
Gitlab::Utils.check_path_traversal!(@archive_path)
|
||||||
|
|
||||||
|
raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
|
||||||
|
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
|
||||||
|
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
|
||||||
|
end
|
||||||
|
|
||||||
def command
|
def command
|
||||||
"gzip -dc #{@archive_path} | wc -c"
|
"gzip -dc #{@archive_path} | wc -c"
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_error(error)
|
def log_error(error)
|
||||||
|
archive_size = begin
|
||||||
|
File.size(@archive_path)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
Gitlab::Import::Logger.info(
|
Gitlab::Import::Logger.info(
|
||||||
message: error,
|
message: error,
|
||||||
import_upload_archive_path: @archive_path,
|
import_upload_archive_path: @archive_path,
|
||||||
import_upload_archive_size: File.size(@archive_path)
|
import_upload_archive_size: archive_size
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,8 @@ module Gitlab
|
||||||
# @param [String] namespace
|
# @param [String] namespace
|
||||||
def self.restore_full_path(namespace:, project:)
|
def self.restore_full_path(namespace:, project:)
|
||||||
if project.include?(ENCODED_SLASH)
|
if project.include?(ENCODED_SLASH)
|
||||||
project.gsub(ENCODED_SLASH, SLASH)
|
# Replace multiple slashes with single ones to make sure the redirect stays on the same host
|
||||||
|
project.gsub(ENCODED_SLASH, SLASH).gsub(%r{\/{2,}}, '/')
|
||||||
else
|
else
|
||||||
"#{namespace}/#{project}"
|
"#{namespace}/#{project}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,20 +6,27 @@ module Gitlab
|
||||||
|
|
||||||
attr_reader :major, :minor, :patch
|
attr_reader :major, :minor, :patch
|
||||||
|
|
||||||
def self.parse(str)
|
VERSION_REGEX = /(\d+)\.(\d+)\.(\d+)/.freeze
|
||||||
if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
|
|
||||||
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
|
def self.parse(str, parse_suffix: false)
|
||||||
|
if str.is_a?(self.class)
|
||||||
|
str
|
||||||
|
elsif str && m = str.match(VERSION_REGEX)
|
||||||
|
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i, parse_suffix ? m.post_match : nil)
|
||||||
else
|
else
|
||||||
VersionInfo.new
|
VersionInfo.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(major = 0, minor = 0, patch = 0)
|
def initialize(major = 0, minor = 0, patch = 0, suffix = nil)
|
||||||
@major = major
|
@major = major
|
||||||
@minor = minor
|
@minor = minor
|
||||||
@patch = patch
|
@patch = patch
|
||||||
|
@suffix_s = suffix.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/CyclomaticComplexity
|
||||||
|
# rubocop:disable Metrics/PerceivedComplexity
|
||||||
def <=>(other)
|
def <=>(other)
|
||||||
return unless other.is_a? VersionInfo
|
return unless other.is_a? VersionInfo
|
||||||
return unless valid? && other.valid?
|
return unless valid? && other.valid?
|
||||||
|
@ -36,19 +43,31 @@ module Gitlab
|
||||||
1
|
1
|
||||||
elsif @patch < other.patch
|
elsif @patch < other.patch
|
||||||
-1
|
-1
|
||||||
|
elsif @suffix_s.empty? && other.suffix.present?
|
||||||
|
1
|
||||||
|
elsif other.suffix.empty? && @suffix_s.present?
|
||||||
|
-1
|
||||||
else
|
else
|
||||||
0
|
suffix <=> other.suffix
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/CyclomaticComplexity
|
||||||
|
# rubocop:enable Metrics/PerceivedComplexity
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
if valid?
|
if valid?
|
||||||
"%d.%d.%d" % [@major, @minor, @patch]
|
"%d.%d.%d%s" % [@major, @minor, @patch, @suffix_s]
|
||||||
else
|
else
|
||||||
"Unknown"
|
'Unknown'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def suffix
|
||||||
|
@suffix ||= @suffix_s.strip.gsub('-', '.pre.').scan(/\d+|[a-z]+/i).map do |s|
|
||||||
|
/^\d+$/ =~ s ? s.to_i : s
|
||||||
|
end.freeze
|
||||||
|
end
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
@major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
|
@major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
|
||||||
end
|
end
|
||||||
|
|
|
@ -21342,6 +21342,9 @@ msgstr ""
|
||||||
msgid "Invited"
|
msgid "Invited"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Invited group allowed email domains must contain a subset of the allowed email domains of the root ancestor group. Go to the group's 'Settings > General' page and check 'Restrict membership by email domain'."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}"
|
msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -52,8 +52,8 @@
|
||||||
"@babel/preset-env": "^7.18.2",
|
"@babel/preset-env": "^7.18.2",
|
||||||
"@gitlab/at.js": "1.5.7",
|
"@gitlab/at.js": "1.5.7",
|
||||||
"@gitlab/favicon-overlay": "2.0.0",
|
"@gitlab/favicon-overlay": "2.0.0",
|
||||||
"@gitlab/svgs": "2.22.0",
|
"@gitlab/svgs": "2.24.0",
|
||||||
"@gitlab/ui": "42.11.0",
|
"@gitlab/ui": "42.12.0",
|
||||||
"@gitlab/visual-review-tools": "1.7.3",
|
"@gitlab/visual-review-tools": "1.7.3",
|
||||||
"@rails/actioncable": "6.1.4-7",
|
"@rails/actioncable": "6.1.4-7",
|
||||||
"@rails/ujs": "6.1.4-7",
|
"@rails/ujs": "6.1.4-7",
|
||||||
|
|
|
@ -307,6 +307,18 @@ RSpec.describe Projects::ErrorTrackingController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'format json' do
|
describe 'format json' do
|
||||||
|
context 'when user is a reporter' do
|
||||||
|
before do
|
||||||
|
project.add_reporter(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 404 error' do
|
||||||
|
update_issue
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when update result is successful' do
|
context 'when update result is successful' do
|
||||||
before do
|
before do
|
||||||
allow(issue_update_service).to receive(:execute)
|
allow(issue_update_service).to receive(:execute)
|
||||||
|
|
|
@ -70,6 +70,7 @@ RSpec.describe 'Database schema' do
|
||||||
oauth_applications: %w[owner_id],
|
oauth_applications: %w[owner_id],
|
||||||
product_analytics_events_experimental: %w[event_id txn_id user_id],
|
product_analytics_events_experimental: %w[event_id txn_id user_id],
|
||||||
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
|
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
|
||||||
|
project_error_tracking_settings: %w[sentry_project_id],
|
||||||
project_group_links: %w[group_id],
|
project_group_links: %w[group_id],
|
||||||
project_statistics: %w[namespace_id],
|
project_statistics: %w[namespace_id],
|
||||||
projects: %w[creator_id ci_id mirror_user_id],
|
projects: %w[creator_id ci_id mirror_user_id],
|
||||||
|
|
|
@ -13,7 +13,7 @@ FactoryBot.define do
|
||||||
message { 'message' }
|
message { 'message' }
|
||||||
culprit { 'culprit' }
|
culprit { 'culprit' }
|
||||||
external_url { 'http://example.com/id' }
|
external_url { 'http://example.com/id' }
|
||||||
project_id { 'project1' }
|
project_id { '111111' }
|
||||||
project_name { 'project name' }
|
project_name { 'project name' }
|
||||||
project_slug { 'project_name' }
|
project_slug { 'project_name' }
|
||||||
short_id { 'ID' }
|
short_id { 'ID' }
|
||||||
|
|
|
@ -9,6 +9,7 @@ FactoryBot.define do
|
||||||
project_name { 'Sentry Project' }
|
project_name { 'Sentry Project' }
|
||||||
organization_name { 'Sentry Org' }
|
organization_name { 'Sentry Org' }
|
||||||
integrated { false }
|
integrated { false }
|
||||||
|
sentry_project_id { 10 }
|
||||||
|
|
||||||
trait :disabled do
|
trait :disabled do
|
||||||
enabled { false }
|
enabled { false }
|
||||||
|
|
|
@ -5,12 +5,17 @@ require 'spec_helper'
|
||||||
RSpec.describe Ci::RunnerJobsFinder do
|
RSpec.describe Ci::RunnerJobsFinder do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:runner) { create(:ci_runner, :instance) }
|
let(:runner) { create(:ci_runner, :instance) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:params) { {} }
|
||||||
|
|
||||||
subject { described_class.new(runner, params).execute }
|
subject { described_class.new(runner, user, params).execute }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
context 'when params is empty' do
|
context 'when params is empty' do
|
||||||
let(:params) { {} }
|
|
||||||
let!(:job) { create(:ci_build, runner: runner, project: project) }
|
let!(:job) { create(:ci_build, runner: runner, project: project) }
|
||||||
let!(:job1) { create(:ci_build, project: project) }
|
let!(:job1) { create(:ci_build, project: project) }
|
||||||
|
|
||||||
|
@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the user has guest access' do
|
||||||
|
it 'does not returns jobs the user does not have permission to see' do
|
||||||
|
another_project = create(:project)
|
||||||
|
job = create(:ci_build, runner: runner, project: another_project)
|
||||||
|
|
||||||
|
another_project.add_guest(user)
|
||||||
|
|
||||||
|
is_expected.not_to match_array(job)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has permission to read all resources' do
|
||||||
|
let(:user) { create(:user, :admin) }
|
||||||
|
|
||||||
|
it 'returns all the jobs assigned to a runner' do
|
||||||
|
jobs = create_list(:ci_build, 5, runner: runner, project: project)
|
||||||
|
|
||||||
|
is_expected.to match_array(jobs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has different access levels in different projects' do
|
||||||
|
it 'returns only the jobs the user has permission to see' do
|
||||||
|
guest_project = create(:project)
|
||||||
|
reporter_project = create(:project)
|
||||||
|
|
||||||
|
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
|
||||||
|
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
|
||||||
|
|
||||||
|
guest_project.add_guest(user)
|
||||||
|
reporter_project.add_reporter(user)
|
||||||
|
|
||||||
|
is_expected.to match_array(reporter_jobs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has reporter access level or greater' do
|
||||||
|
it 'returns jobs assigned to the Runner that the user has accesss to' do
|
||||||
|
jobs = create_list(:ci_build, 3, runner: runner, project: project)
|
||||||
|
|
||||||
|
is_expected.to match_array(jobs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when params contains status' do
|
context 'when params contains status' do
|
||||||
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
|
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
|
||||||
context "when status is #{target_status}" do
|
context "when status is #{target_status}" do
|
||||||
|
|
|
@ -2,22 +2,53 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe ::Packages::Conan::PackageFinder do
|
RSpec.describe ::Packages::Conan::PackageFinder do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
let_it_be_with_reload(:project) { create(:project) }
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :public) }
|
let_it_be(:private_project) { create(:project, :private) }
|
||||||
|
|
||||||
|
let_it_be(:conan_package) { create(:conan_package, project: project) }
|
||||||
|
let_it_be(:conan_package2) { create(:conan_package, project: project) }
|
||||||
|
let_it_be(:errored_package) { create(:conan_package, :error, project: project) }
|
||||||
|
let_it_be(:private_package) { create(:conan_package, project: private_project) }
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let!(:conan_package) { create(:conan_package, project: project) }
|
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
|
||||||
let!(:conan_package2) { create(:conan_package, project: project) }
|
let(:finder) { described_class.new(user, query: query) }
|
||||||
|
|
||||||
subject { described_class.new(user, query: query).execute }
|
subject { finder.execute }
|
||||||
|
|
||||||
context 'packages that are not installable' do
|
where(:visibility, :role, :packages_visible) do
|
||||||
let!(:conan_package3) { create(:conan_package, :error, project: project) }
|
:private | :maintainer | true
|
||||||
let!(:non_visible_project) { create(:project, :private) }
|
:private | :developer | true
|
||||||
let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
|
:private | :reporter | true
|
||||||
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
|
:private | :guest | false
|
||||||
|
:private | :anonymous | false
|
||||||
|
|
||||||
it { is_expected.to eq [conan_package, conan_package2] }
|
:internal | :maintainer | true
|
||||||
|
:internal | :developer | true
|
||||||
|
:internal | :reporter | true
|
||||||
|
:internal | :guest | true
|
||||||
|
:internal | :anonymous | false
|
||||||
|
|
||||||
|
:public | :maintainer | true
|
||||||
|
:public | :developer | true
|
||||||
|
:public | :reporter | true
|
||||||
|
:public | :guest | true
|
||||||
|
:public | :anonymous | true
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
|
||||||
|
let(:user) { role == :anonymous ? nil : super() }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
|
||||||
|
project.add_user(user, role) unless role == :anonymous
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(expected_packages) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{ "$ref": "member_user.json" }
|
{ "$ref": "member_user_default.json" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"state": { "type": "integer" },
|
"state": { "type": "integer" },
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"username",
|
||||||
|
"created_at",
|
||||||
|
"last_activity_on",
|
||||||
|
"avatar_url",
|
||||||
|
"web_url",
|
||||||
|
"blocked",
|
||||||
|
"show_status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer" },
|
||||||
|
"name": { "type": "string" },
|
||||||
|
"username": { "type": "string" },
|
||||||
|
"created_at": { "type": ["string"] },
|
||||||
|
"avatar_url": { "type": ["string", "null"] },
|
||||||
|
"web_url": { "type": "string" },
|
||||||
|
"blocked": { "type": "boolean" },
|
||||||
|
"two_factor_enabled": { "type": "boolean" },
|
||||||
|
"availability": { "type": ["string", "null"] },
|
||||||
|
"last_activity_on": { "type": ["string", "null"] },
|
||||||
|
"status": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["emoji"],
|
||||||
|
"properties": {
|
||||||
|
"emoji": { "type": "string" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"show_status": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
|
@ -130,7 +130,7 @@ describe('Markdown component', () => {
|
||||||
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
|
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
|
||||||
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
|
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
|
||||||
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
|
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
|
||||||
expect(columns[3].innerHTML).toContain('<img>');
|
expect(columns[3].innerHTML).toContain('<img src="attachment:bogus">');
|
||||||
expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
|
expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -159,4 +159,21 @@ describe('AccessDropdown', () => {
|
||||||
expect(template).not.toContain(user.name);
|
expect(template).not.toContain(user.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('deployKeyRowHtml', () => {
|
||||||
|
const deployKey = {
|
||||||
|
id: 1,
|
||||||
|
title: 'title <script>alert(document.domain)</script>',
|
||||||
|
fullname: 'fullname <script>alert(document.domain)</script>',
|
||||||
|
avatar_url: '',
|
||||||
|
username: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('escapes deploy key title and fullname', () => {
|
||||||
|
const template = dropdown.deployKeyRowHtml(deployKey);
|
||||||
|
|
||||||
|
expect(template).not.toContain(deployKey.title);
|
||||||
|
expect(template).not.toContain(deployKey.fullname);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,57 +1,30 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
|
import { GlDropdown } from '@gitlab/ui';
|
||||||
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
|
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
|
||||||
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
|
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
|
||||||
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
|
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
|
||||||
|
import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
|
||||||
|
|
||||||
import { color } from './mock_data';
|
import { color } from './mock_data';
|
||||||
|
|
||||||
const showDropdown = jest.fn();
|
|
||||||
const focusInput = jest.fn();
|
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
dropdownTitle: '',
|
dropdownTitle: '',
|
||||||
selectedColor: color,
|
selectedColor: color,
|
||||||
dropdownButtonText: '',
|
dropdownButtonText: 'Pick a color',
|
||||||
variant: '',
|
variant: '',
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GlDropdownStub = {
|
|
||||||
template: `
|
|
||||||
<div>
|
|
||||||
<slot name="header"></slot>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
methods: {
|
|
||||||
show: showDropdown,
|
|
||||||
hide: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DropdownHeaderStub = {
|
|
||||||
template: `
|
|
||||||
<div>Hello, I am a header</div>
|
|
||||||
`,
|
|
||||||
methods: {
|
|
||||||
focusInput,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('DropdownContent', () => {
|
describe('DropdownContent', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const createComponent = ({ propsData = {} } = {}) => {
|
const createComponent = ({ propsData = {} } = {}) => {
|
||||||
wrapper = shallowMount(DropdownContents, {
|
wrapper = mountExtended(DropdownContents, {
|
||||||
propsData: {
|
propsData: {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
...propsData,
|
...propsData,
|
||||||
},
|
},
|
||||||
stubs: {
|
|
||||||
GlDropdown: GlDropdownStub,
|
|
||||||
DropdownHeader: DropdownHeaderStub,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,16 +33,17 @@ describe('DropdownContent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
|
const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
|
||||||
const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub);
|
const findDropdownHeader = () => wrapper.findComponent(DropdownHeader);
|
||||||
const findDropdown = () => wrapper.findComponent(GlDropdownStub);
|
const findDropdown = () => wrapper.findComponent(GlDropdown);
|
||||||
|
|
||||||
it('calls dropdown `show` method on `isVisible` prop change', async () => {
|
it('calls dropdown `show` method on `isVisible` prop change', async () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show');
|
||||||
await wrapper.setProps({
|
await wrapper.setProps({
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(showDropdown).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not emit `setColor` event on dropdown hide if color did not change', () => {
|
it('does not emit `setColor` event on dropdown hide if color did not change', () => {
|
||||||
|
@ -110,4 +84,12 @@ describe('DropdownContent', () => {
|
||||||
|
|
||||||
expect(findDropdownHeader().exists()).toBe(true);
|
expect(findDropdownHeader().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles no selected color', () => {
|
||||||
|
createComponent({ propsData: { selectedColor: {} } });
|
||||||
|
|
||||||
|
expect(wrapper.findByTestId('fallback-button-text').text()).toEqual(
|
||||||
|
defaultProps.dropdownButtonText,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
|
||||||
|
|
||||||
describe '#resolve' do
|
describe '#resolve' do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
let_it_be(:sha) { nil }
|
let_it_be(:sha) { nil }
|
||||||
|
|
||||||
let_it_be(:content) do
|
let_it_be(:content) do
|
||||||
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
|
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:ci_lint) do
|
|
||||||
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
|
|
||||||
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
|
|
||||||
|
|
||||||
ci_lint_double
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject(:response) do
|
subject(:response) do
|
||||||
resolve(described_class,
|
resolve(described_class,
|
||||||
args: { project_path: project.full_path, content: content, sha: sha },
|
args: { project_path: project.full_path, content: content, sha: sha },
|
||||||
|
@ -51,52 +40,77 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a valid .gitlab-ci.yml' do
|
context 'when the user can create a pipeline' do
|
||||||
context 'with a sha' do
|
|
||||||
let(:sha) { '1231231' }
|
|
||||||
|
|
||||||
it_behaves_like 'a valid config file'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'without a sha' do
|
|
||||||
it_behaves_like 'a valid config file'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with an invalid .gitlab-ci.yml' do
|
|
||||||
let(:content) { 'invalid' }
|
|
||||||
|
|
||||||
let(:fake_result) do
|
|
||||||
Gitlab::Ci::Lint::Result.new(
|
|
||||||
jobs: [],
|
|
||||||
merged_yaml: content,
|
|
||||||
errors: ['Invalid configuration format'],
|
|
||||||
warnings: [],
|
|
||||||
includes: []
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'responds with errors about invalid syntax' do
|
|
||||||
expect(response[:status]).to eq(:invalid)
|
|
||||||
expect(response[:errors]).to eq(['Invalid configuration format'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with an invalid SHA' do
|
|
||||||
let_it_be(:sha) { ':' }
|
|
||||||
|
|
||||||
let(:ci_lint) do
|
let(:ci_lint) do
|
||||||
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
|
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
|
||||||
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
|
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
|
||||||
|
|
||||||
ci_lint_double
|
ci_lint_double
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'logs the invalid SHA to Sentry' do
|
before do
|
||||||
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
|
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
|
||||||
.with(GRPC::InvalidArgument, sha: ':')
|
|
||||||
|
|
||||||
response
|
project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a valid .gitlab-ci.yml' do
|
||||||
|
context 'with a sha' do
|
||||||
|
let(:sha) { '1231231' }
|
||||||
|
|
||||||
|
it_behaves_like 'a valid config file'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without a sha' do
|
||||||
|
it_behaves_like 'a valid config file'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid .gitlab-ci.yml' do
|
||||||
|
let(:content) { 'invalid' }
|
||||||
|
|
||||||
|
let(:fake_result) do
|
||||||
|
Gitlab::Ci::Lint::Result.new(
|
||||||
|
jobs: [],
|
||||||
|
merged_yaml: content,
|
||||||
|
errors: ['Invalid configuration format'],
|
||||||
|
warnings: [],
|
||||||
|
includes: []
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds with errors about invalid syntax' do
|
||||||
|
expect(response[:status]).to eq(:invalid)
|
||||||
|
expect(response[:errors]).to match_array(['Invalid configuration format'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid SHA' do
|
||||||
|
let_it_be(:sha) { ':' }
|
||||||
|
|
||||||
|
let(:ci_lint) do
|
||||||
|
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
|
||||||
|
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
|
||||||
|
|
||||||
|
ci_lint_double
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs the invalid SHA to Sentry' do
|
||||||
|
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
|
||||||
|
.with(GRPC::InvalidArgument, sha: ':')
|
||||||
|
|
||||||
|
response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user cannot create a pipeline' do
|
||||||
|
before do
|
||||||
|
project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an error stating that the user cannot access the linting' do
|
||||||
|
expect(response).to be_instance_of(::Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -150,19 +150,4 @@ RSpec.describe IntegrationsHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#jira_issue_breadcrumb_link' do
|
|
||||||
let(:issue_reference) { nil }
|
|
||||||
|
|
||||||
subject { helper.jira_issue_breadcrumb_link(issue_reference) }
|
|
||||||
|
|
||||||
context 'when issue_reference contains HTML' do
|
|
||||||
let(:issue_reference) { "<script>alert('XSS')</script>" }
|
|
||||||
|
|
||||||
it 'escapes issue reference' do
|
|
||||||
is_expected.not_to include(issue_reference)
|
|
||||||
is_expected.to include(html_escape(issue_reference))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
|
||||||
context 'api_url present' do
|
context 'api_url present' do
|
||||||
let(:json) do
|
let(:json) do
|
||||||
{
|
{
|
||||||
|
sentry_project_id: error_tracking_setting.sentry_project_id,
|
||||||
name: error_tracking_setting.project_name,
|
name: error_tracking_setting.project_name,
|
||||||
organization_name: error_tracking_setting.organization_name,
|
organization_name: error_tracking_setting.organization_name,
|
||||||
organization_slug: error_tracking_setting.organization_slug,
|
organization_slug: error_tracking_setting.organization_slug,
|
||||||
|
|
|
@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#recent_releases_with_counts" do
|
||||||
|
let_it_be(:milestone) { create(:milestone) }
|
||||||
|
let_it_be(:project) { milestone.project }
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
subject { helper.recent_releases_with_counts(milestone, user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns releases with counts" do
|
||||||
|
_old_releases = create_list(:release, 2, project: project, milestones: [milestone])
|
||||||
|
recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z')
|
||||||
|
|
||||||
|
is_expected.to match([match_array(recent_public_releases), 5, 2])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Net::HTTPResponse patch header read timeout' do
|
||||||
|
describe '.each_response_header' do
|
||||||
|
let(:server_response) do
|
||||||
|
<<~EOS
|
||||||
|
Content-Type: text/html
|
||||||
|
Header-Two: foo
|
||||||
|
|
||||||
|
Hello World
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } }
|
||||||
|
|
||||||
|
context 'with Net::BufferedIO' do
|
||||||
|
let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) }
|
||||||
|
|
||||||
|
it 'does not forward start time to the socket' do
|
||||||
|
allow(socket).to receive(:readuntil).and_call_original
|
||||||
|
expect(socket).to receive(:readuntil).with("\n", true)
|
||||||
|
|
||||||
|
each_response_header
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the response contains many consecutive spaces' do
|
||||||
|
before do
|
||||||
|
expect(socket).to receive(:readuntil).and_return(
|
||||||
|
"a: #{' ' * 100_000} b",
|
||||||
|
''
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has no regex backtracking issues' do
|
||||||
|
Timeout.timeout(1) do
|
||||||
|
each_response_header
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with Gitlab::BufferedIo' do
|
||||||
|
let(:mock_io) { StringIO.new(server_response) }
|
||||||
|
let(:socket) { Gitlab::BufferedIo.new(mock_io) }
|
||||||
|
|
||||||
|
it 'forwards start time to the socket' do
|
||||||
|
allow(socket).to receive(:readuntil).and_call_original
|
||||||
|
expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric))
|
||||||
|
|
||||||
|
each_response_header
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the response contains an infinite number of headers' do
|
||||||
|
before do
|
||||||
|
read_counter = 0
|
||||||
|
|
||||||
|
allow(mock_io).to receive(:read_nonblock) do
|
||||||
|
read_counter += 1
|
||||||
|
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
|
||||||
|
|
||||||
|
sleep 0.01
|
||||||
|
+"Yet-Another-Header: foo\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a timeout error' do
|
||||||
|
expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout,
|
||||||
|
/Request timed out after reading headers for 0\.[0-9]+ seconds/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
|
||||||
let(:project_data) do
|
let(:project_data) do
|
||||||
{
|
{
|
||||||
'visibility' => 'private',
|
'visibility' => 'private',
|
||||||
'created_at' => 10.days.ago,
|
'created_at' => '2016-08-12T09:41:03'
|
||||||
'archived' => false,
|
|
||||||
'shared_runners_enabled' => true,
|
|
||||||
'container_registry_enabled' => true,
|
|
||||||
'only_allow_merge_if_pipeline_succeeds' => true,
|
|
||||||
'only_allow_merge_if_all_discussions_are_resolved' => true,
|
|
||||||
'request_access_enabled' => true,
|
|
||||||
'printing_merge_request_link_enabled' => true,
|
|
||||||
'remove_source_branch_after_merge' => true,
|
|
||||||
'autoclose_referenced_issues' => true,
|
|
||||||
'suggestion_commit_message' => 'message',
|
|
||||||
'wiki_enabled' => true
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
|
||||||
|
|
||||||
expect(imported_project).not_to be_nil
|
expect(imported_project).not_to be_nil
|
||||||
expect(imported_project.group).to eq(group)
|
expect(imported_project.group).to eq(group)
|
||||||
expect(imported_project.suggestion_commit_message).to eq('message')
|
expect(imported_project.visibility).to eq(project_data['visibility'])
|
||||||
expect(imported_project.archived?).to eq(project_data['archived'])
|
expect(imported_project.created_at).to eq(project_data['created_at'])
|
||||||
expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled'])
|
|
||||||
expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled'])
|
|
||||||
expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds'])
|
|
||||||
expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved'])
|
|
||||||
expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled'])
|
|
||||||
expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled'])
|
|
||||||
expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge'])
|
|
||||||
expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues'])
|
|
||||||
expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled'])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
|
||||||
|
|
||||||
let(:data) do
|
let(:data) do
|
||||||
{
|
{
|
||||||
'name' => 'source_name',
|
'visibility' => 'private',
|
||||||
'visibility' => 'private'
|
'created_at' => '2016-11-18T09:29:42.634Z'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'converts all keys to symbols' do
|
context 'when data has extra keys' do
|
||||||
expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id)
|
it 'returns a fixed number of keys' do
|
||||||
|
data = {
|
||||||
|
'visibility' => 'private',
|
||||||
|
'created_at' => '2016-11-18T09:29:42.634Z',
|
||||||
|
'my_key' => 'my_key',
|
||||||
|
'another_key' => 'another_key',
|
||||||
|
'last_key' => 'last_key'
|
||||||
|
}
|
||||||
|
|
||||||
|
transformed_data = described_class.new.transform(context, data)
|
||||||
|
|
||||||
|
expect(transformed_data.keys)
|
||||||
|
.to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,54 +1,50 @@
|
||||||
# rubocop:disable Style/FrozenStringLiteralComment
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::BufferedIo do
|
RSpec.describe Gitlab::BufferedIo do
|
||||||
describe '#readuntil' do
|
describe '#readuntil' do
|
||||||
let(:never_ending_tcp_socket) do
|
let(:mock_io) { StringIO.new('a') }
|
||||||
Class.new do
|
let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
||||||
def initialize(*_)
|
|
||||||
@read_counter = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def setsockopt(*_); end
|
|
||||||
|
|
||||||
def closed?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_io
|
|
||||||
StringIO.new('Hello World!')
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_nonblock(data, *_)
|
|
||||||
data.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_nonblock(buffer_size, *_)
|
|
||||||
sleep 0.01
|
|
||||||
@read_counter += 1
|
|
||||||
|
|
||||||
raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10
|
|
||||||
|
|
||||||
'H' * buffer_size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
|
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
|
||||||
end
|
end
|
||||||
|
|
||||||
subject(:readuntil) do
|
subject(:readuntil) do
|
||||||
Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a')
|
Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises a timeout error' do
|
it 'does not raise a timeout error' do
|
||||||
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
|
expect { readuntil }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the response contains infinitely long headers' do
|
||||||
|
before do
|
||||||
|
read_counter = 0
|
||||||
|
|
||||||
|
allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_|
|
||||||
|
read_counter += 1
|
||||||
|
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
|
||||||
|
|
||||||
|
sleep 0.01
|
||||||
|
'H' * buffer_size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a timeout error' do
|
||||||
|
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not passing start_time' do
|
||||||
|
subject(:readuntil) do
|
||||||
|
Gitlab::BufferedIo.new(mock_io).readuntil('a', false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a timeout error' do
|
||||||
|
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Style/FrozenStringLiteralComment
|
|
||||||
|
|
|
@ -21,7 +21,9 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with available_runner_releases configured up to 14.1.1' do
|
context 'with available_runner_releases configured up to 14.1.1' do
|
||||||
let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] }
|
let(:available_runner_releases) do
|
||||||
|
%w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1]
|
||||||
|
end
|
||||||
|
|
||||||
context 'with nil runner_version' do
|
context 'with nil runner_version' do
|
||||||
let(:runner_version) { nil }
|
let(:runner_version) { nil }
|
||||||
|
@ -62,10 +64,11 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
|
||||||
'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
|
'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
|
||||||
'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
|
'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
|
||||||
'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
|
'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
|
||||||
|
'v14.0.2-rc1' | :recommended # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
|
||||||
'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
|
'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
|
||||||
'v13.10.1' | :available # available upgrade: 14.1.1
|
'v13.10.1' | :available # available upgrade: 14.1.1
|
||||||
'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled
|
'v13.10.1~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled, official 13.10.1 is available
|
||||||
'v13.10.1/1.1.0' | :available # suffixes are correctly handled
|
'v13.10.1/1.1.0' | :recommended # suffixes are correctly handled, official 13.10.1 is available
|
||||||
'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
|
'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
|
||||||
'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
|
'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
|
||||||
'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version
|
'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
|
||||||
|
subject(:importer) { described_class.new(project, user.id) }
|
||||||
|
|
||||||
|
let_it_be(:project) { create(:project, :repository) }
|
||||||
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
let(:commit_id) { nil }
|
||||||
|
|
||||||
|
let(:issue_event) do
|
||||||
|
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
|
||||||
|
'id' => 6501124486,
|
||||||
|
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
|
||||||
|
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
|
||||||
|
'actor' => { 'id' => 4, 'login' => 'alice' },
|
||||||
|
'event' => 'closed',
|
||||||
|
'created_at' => '2022-04-26 18:30:53 UTC',
|
||||||
|
'commit_id' => commit_id,
|
||||||
|
'issue_db_id' => issue.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_event_attrs) do
|
||||||
|
{
|
||||||
|
project_id: project.id,
|
||||||
|
author_id: user.id,
|
||||||
|
target_id: issue.id,
|
||||||
|
target_type: Issue.name,
|
||||||
|
action: 'closed',
|
||||||
|
created_at: issue_event.created_at,
|
||||||
|
updated_at: issue_event.created_at
|
||||||
|
}.stringify_keys
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_state_event_attrs) do
|
||||||
|
{
|
||||||
|
user_id: user.id,
|
||||||
|
issue_id: issue.id,
|
||||||
|
state: 'closed',
|
||||||
|
created_at: issue_event.created_at
|
||||||
|
}.stringify_keys
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates expected event and state event' do
|
||||||
|
importer.execute(issue_event)
|
||||||
|
|
||||||
|
expect(issue.events.count).to eq 1
|
||||||
|
expect(issue.events[0].attributes)
|
||||||
|
.to include expected_event_attrs
|
||||||
|
|
||||||
|
expect(issue.resource_state_events.count).to eq 1
|
||||||
|
expect(issue.resource_state_events[0].attributes)
|
||||||
|
.to include expected_state_event_attrs
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when closed by commit' do
|
||||||
|
let!(:closing_commit) { create(:commit, project: project) }
|
||||||
|
let(:commit_id) { closing_commit.id }
|
||||||
|
|
||||||
|
it 'creates expected event and state event' do
|
||||||
|
importer.execute(issue_event)
|
||||||
|
|
||||||
|
expect(issue.events.count).to eq 1
|
||||||
|
state_event = issue.resource_state_events.last
|
||||||
|
expect(state_event.source_commit).to eq commit_id[0..40]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,77 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do
|
||||||
|
let(:importer) { described_class.new(issue_event, project, client) }
|
||||||
|
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:client) { instance_double('Gitlab::GithubImport::Client') }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
|
||||||
|
let(:issue_event) do
|
||||||
|
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
|
||||||
|
'id' => 6501124486,
|
||||||
|
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
|
||||||
|
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
|
||||||
|
'actor' => { 'id' => actor_id, 'login' => 'alice' },
|
||||||
|
'event' => event_name,
|
||||||
|
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
'commit_url' =>
|
||||||
|
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
'created_at' => '2022-04-26 18:30:53 UTC',
|
||||||
|
'performed_via_github_app' => nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:actor_id) { user.id }
|
||||||
|
let(:event_name) { 'closed' }
|
||||||
|
|
||||||
|
shared_examples 'triggers specific event importer' do |importer_class|
|
||||||
|
it importer_class.name do
|
||||||
|
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
|
||||||
|
|
||||||
|
expect(importer_class)
|
||||||
|
.to receive(:new).with(project, user.id)
|
||||||
|
.and_return(specific_importer)
|
||||||
|
expect(specific_importer).to receive(:execute).with(issue_event)
|
||||||
|
|
||||||
|
importer.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
before do
|
||||||
|
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
|
||||||
|
allow(finder).to receive(:author_id_for)
|
||||||
|
.with(issue_event, author_key: :actor)
|
||||||
|
.and_return(user.id, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
issue_event.attributes[:issue_db_id] = issue.id
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when it's closed issue event" do
|
||||||
|
let(:event_name) { 'closed' }
|
||||||
|
|
||||||
|
it_behaves_like 'triggers specific event importer',
|
||||||
|
Gitlab::GithubImport::Importer::Events::Closed
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when it's unknown issue event" do
|
||||||
|
let(:event_name) { 'fake' }
|
||||||
|
|
||||||
|
it 'logs warning and skips' do
|
||||||
|
expect(Gitlab::GithubImport::Logger).to receive(:debug)
|
||||||
|
.with(
|
||||||
|
message: 'UNSUPPORTED_EVENT_TYPE',
|
||||||
|
event_type: issue_event.event,
|
||||||
|
event_github_id: issue_event.id
|
||||||
|
)
|
||||||
|
|
||||||
|
importer.execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,128 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
|
||||||
|
let(:client) { double }
|
||||||
|
|
||||||
|
let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
|
||||||
|
let_it_be(:issue) { create(:issue, project: project) }
|
||||||
|
|
||||||
|
subject { described_class.new(project, client, parallel: parallel) }
|
||||||
|
|
||||||
|
let(:parallel) { true }
|
||||||
|
|
||||||
|
it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
|
||||||
|
|
||||||
|
describe '#importer_class' do
|
||||||
|
it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#representation_class' do
|
||||||
|
it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#sidekiq_worker_class' do
|
||||||
|
it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#object_type' do
|
||||||
|
it { expect(subject.object_type).to eq(:issue_event) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#collection_method' do
|
||||||
|
it { expect(subject.collection_method).to eq(:issue_timeline) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#page_counter_id' do
|
||||||
|
it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#id_for_already_imported_cache' do
|
||||||
|
let(:event) { instance_double('Event', id: 1) }
|
||||||
|
|
||||||
|
it { expect(subject.id_for_already_imported_cache(event)).to eq(1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#collection_options' do
|
||||||
|
it do
|
||||||
|
expect(subject.collection_options)
|
||||||
|
.to eq({ state: 'all', sort: 'created', direction: 'asc' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#each_object_to_import', :clean_gitlab_redis_cache do
|
||||||
|
let(:issue_event) do
|
||||||
|
struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
|
||||||
|
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:page) do
|
||||||
|
instance_double(
|
||||||
|
Gitlab::GithubImport::Client::Page,
|
||||||
|
number: 1, objects: [issue_event]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(client).to receive(:each_page)
|
||||||
|
.once
|
||||||
|
.with(
|
||||||
|
:issue_timeline,
|
||||||
|
project.import_source,
|
||||||
|
issue.iid,
|
||||||
|
{ state: 'all', sort: 'created', direction: 'asc', page: 1 }
|
||||||
|
).and_yield(page)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'imports each issue event page by page' do
|
||||||
|
counter = 0
|
||||||
|
subject.each_object_to_import do |object|
|
||||||
|
expect(object).to eq issue_event
|
||||||
|
expect(issue_event.issue_db_id).to eq issue.id
|
||||||
|
counter += 1
|
||||||
|
end
|
||||||
|
expect(counter).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'triggers page number increment' do
|
||||||
|
expect(Gitlab::GithubImport::PageCounter)
|
||||||
|
.to receive(:new).with(project, 'issues/1/issue_timeline')
|
||||||
|
.and_return(page_counter)
|
||||||
|
expect(page_counter).to receive(:current).and_return(1)
|
||||||
|
expect(page_counter)
|
||||||
|
.to receive(:set).with(page.number).and_return(true)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
subject.each_object_to_import { counter += 1 }
|
||||||
|
expect(counter).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when page is already processed' do
|
||||||
|
before do
|
||||||
|
page_counter = Gitlab::GithubImport::PageCounter.new(
|
||||||
|
project, subject.page_counter_id(issue)
|
||||||
|
)
|
||||||
|
page_counter.set(page.number)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't process this page" do
|
||||||
|
counter = 0
|
||||||
|
subject.each_object_to_import { counter += 1 }
|
||||||
|
expect(counter).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when event is already processed' do
|
||||||
|
it "doesn't process this event" do
|
||||||
|
subject.mark_as_imported(issue_event)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
subject.each_object_to_import { counter += 1 }
|
||||||
|
expect(counter).to eq 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,111 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
|
||||||
|
shared_examples 'an IssueEvent' do
|
||||||
|
it 'returns an instance of IssueEvent' do
|
||||||
|
expect(issue_event).to be_an_instance_of(described_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the returned IssueEvent' do
|
||||||
|
it 'includes the issue event id' do
|
||||||
|
expect(issue_event.id).to eq(6501124486)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes the issue event "event"' do
|
||||||
|
expect(issue_event.event).to eq('closed')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes the issue event commit_id' do
|
||||||
|
expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes the issue_db_id' do
|
||||||
|
expect(issue_event.issue_db_id).to eq(100500)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when actor data present' do
|
||||||
|
it 'includes the actor details' do
|
||||||
|
expect(issue_event.actor)
|
||||||
|
.to be_an_instance_of(Gitlab::GithubImport::Representation::User)
|
||||||
|
|
||||||
|
expect(issue_event.actor.id).to eq(4)
|
||||||
|
expect(issue_event.actor.login).to eq('alice')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when actor data is empty' do
|
||||||
|
let(:with_actor) { false }
|
||||||
|
|
||||||
|
it 'does not return such info' do
|
||||||
|
expect(issue_event.actor).to eq nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes the created timestamp' do
|
||||||
|
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#github_identifiers' do
|
||||||
|
it 'returns a hash with needed identifiers' do
|
||||||
|
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.from_api_response' do
|
||||||
|
let(:response) do
|
||||||
|
event_resource = Struct.new(
|
||||||
|
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url,
|
||||||
|
:issue_db_id, :created_at, :performed_via_github_app,
|
||||||
|
keyword_init: true
|
||||||
|
)
|
||||||
|
user_resource = Struct.new(:id, :login, keyword_init: true)
|
||||||
|
event_resource.new(
|
||||||
|
id: 6501124486,
|
||||||
|
node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
|
||||||
|
url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
|
||||||
|
actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil,
|
||||||
|
event: 'closed',
|
||||||
|
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
|
||||||
|
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
issue_db_id: 100500,
|
||||||
|
created_at: '2022-04-26 18:30:53 UTC',
|
||||||
|
performed_via_github_app: nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:with_actor) { true }
|
||||||
|
|
||||||
|
it_behaves_like 'an IssueEvent' do
|
||||||
|
let(:issue_event) { described_class.from_api_response(response) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.from_json_hash' do
|
||||||
|
it_behaves_like 'an IssueEvent' do
|
||||||
|
let(:hash) do
|
||||||
|
{
|
||||||
|
'id' => 6501124486,
|
||||||
|
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
|
||||||
|
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
|
||||||
|
'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil),
|
||||||
|
'event' => 'closed',
|
||||||
|
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
'commit_url' =>
|
||||||
|
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
|
||||||
|
"issue_db_id" => 100500,
|
||||||
|
'created_at' => '2022-04-26 18:30:53 UTC',
|
||||||
|
'performed_via_github_app' => nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:with_actor) { true }
|
||||||
|
|
||||||
|
let(:issue_event) { described_class.from_json_hash(hash) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do
|
||||||
|
let(:importer_class) do
|
||||||
|
Class.new do
|
||||||
|
def self.name
|
||||||
|
'MyImporter'
|
||||||
|
end
|
||||||
|
|
||||||
|
include(Gitlab::GithubImport::SingleEndpointNotesImporting)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:importer_instance) { importer_class.new }
|
||||||
|
|
||||||
|
describe '#parent_collection' do
|
||||||
|
it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#parent_imported_cache_key' do
|
||||||
|
it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#page_counter_id' do
|
||||||
|
it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -420,6 +420,8 @@ project:
|
||||||
- zentao_integration
|
- zentao_integration
|
||||||
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
|
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
|
||||||
- dingtalk_integration
|
- dingtalk_integration
|
||||||
|
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
|
||||||
|
- feishu_integration
|
||||||
- redmine_integration
|
- redmine_integration
|
||||||
- youtrack_integration
|
- youtrack_integration
|
||||||
- custom_issue_tracker_integration
|
- custom_issue_tracker_integration
|
||||||
|
|
|
@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
|
||||||
include_examples 'logs raised exception and terminates validator process group'
|
include_examples 'logs raised exception and terminates validator process group'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'archive path validation' do
|
||||||
|
let(:filesize) { nil }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(Gitlab::Import::Logger)
|
||||||
|
.to receive(:info)
|
||||||
|
.with(
|
||||||
|
import_upload_archive_path: filepath,
|
||||||
|
import_upload_archive_size: filesize,
|
||||||
|
message: error_message
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when archive path is traversed' do
|
||||||
|
let(:filepath) { '/foo/../bar' }
|
||||||
|
let(:error_message) { 'Invalid path' }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.valid?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when archive path is not a string' do
|
||||||
|
let(:filepath) { 123 }
|
||||||
|
let(:error_message) { 'Archive path is not a string' }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.valid?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'which archive path is a symlink' do
|
||||||
|
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
|
||||||
|
let(:error_message) { 'Archive path is a symlink' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
FileUtils.ln_s(filepath, filepath, force: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.valid?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when archive path is not a file' do
|
||||||
|
let(:filepath) { Dir.mktmpdir }
|
||||||
|
let(:filesize) { File.size(filepath) }
|
||||||
|
let(:error_message) { 'Archive path is not a file' }
|
||||||
|
|
||||||
|
after do
|
||||||
|
FileUtils.rm_rf(filepath)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(subject.valid?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_compressed_file
|
def create_compressed_file
|
||||||
|
|
|
@ -2,28 +2,44 @@
|
||||||
|
|
||||||
require 'fast_spec_helper'
|
require 'fast_spec_helper'
|
||||||
|
|
||||||
RSpec.describe 'Gitlab::VersionInfo' do
|
RSpec.describe Gitlab::VersionInfo do
|
||||||
before do
|
before do
|
||||||
@unknown = Gitlab::VersionInfo.new
|
@unknown = described_class.new
|
||||||
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
|
@v0_0_1 = described_class.new(0, 0, 1)
|
||||||
@v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0)
|
@v0_1_0 = described_class.new(0, 1, 0)
|
||||||
@v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0)
|
@v1_0_0 = described_class.new(1, 0, 0)
|
||||||
@v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1)
|
@v1_0_1 = described_class.new(1, 0, 1)
|
||||||
@v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0)
|
@v1_0_1_b1 = described_class.new(1, 0, 1, '-b1')
|
||||||
@v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
|
@v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1')
|
||||||
|
@v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2')
|
||||||
|
@v1_1_0 = described_class.new(1, 1, 0)
|
||||||
|
@v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1')
|
||||||
|
@v2_0_0 = described_class.new(2, 0, 0)
|
||||||
|
@v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true)
|
||||||
|
@v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true)
|
||||||
|
@v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '>' do
|
describe '>' do
|
||||||
it { expect(@v2_0_0).to be > @v1_1_0 }
|
it { expect(@v2_0_0).to be > @v1_1_0 }
|
||||||
it { expect(@v1_1_0).to be > @v1_0_1 }
|
it { expect(@v1_1_0).to be > @v1_0_1 }
|
||||||
|
it { expect(@v1_0_1_b1).to be > @v1_0_0 }
|
||||||
|
it { expect(@v1_0_1_rc1).to be > @v1_0_0 }
|
||||||
|
it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 }
|
||||||
|
it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 }
|
||||||
|
it { expect(@v1_0_1).to be > @v1_0_1_rc1 }
|
||||||
|
it { expect(@v1_0_1).to be > @v1_0_1_rc2 }
|
||||||
it { expect(@v1_0_1).to be > @v1_0_0 }
|
it { expect(@v1_0_1).to be > @v1_0_0 }
|
||||||
it { expect(@v1_0_0).to be > @v0_1_0 }
|
it { expect(@v1_0_0).to be > @v0_1_0 }
|
||||||
|
it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 }
|
||||||
|
it { expect(@v1_1_0).to be > @v1_1_0_beta1 }
|
||||||
it { expect(@v0_1_0).to be > @v0_0_1 }
|
it { expect(@v0_1_0).to be > @v0_0_1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '>=' do
|
describe '>=' do
|
||||||
it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
|
it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) }
|
||||||
it { expect(@v2_0_0).to be >= @v1_1_0 }
|
it { expect(@v2_0_0).to be >= @v1_1_0 }
|
||||||
|
it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '<' do
|
describe '<' do
|
||||||
|
@ -31,64 +47,115 @@ RSpec.describe 'Gitlab::VersionInfo' do
|
||||||
it { expect(@v0_1_0).to be < @v1_0_0 }
|
it { expect(@v0_1_0).to be < @v1_0_0 }
|
||||||
it { expect(@v1_0_0).to be < @v1_0_1 }
|
it { expect(@v1_0_0).to be < @v1_0_1 }
|
||||||
it { expect(@v1_0_1).to be < @v1_1_0 }
|
it { expect(@v1_0_1).to be < @v1_1_0 }
|
||||||
|
it { expect(@v1_0_0).to be < @v1_0_1_rc2 }
|
||||||
|
it { expect(@v1_0_1_rc1).to be < @v1_0_1 }
|
||||||
|
it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 }
|
||||||
|
it { expect(@v1_0_1_rc2).to be < @v1_0_1 }
|
||||||
it { expect(@v1_1_0).to be < @v2_0_0 }
|
it { expect(@v1_1_0).to be < @v2_0_0 }
|
||||||
|
it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 }
|
||||||
|
it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '<=' do
|
describe '<=' do
|
||||||
it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
|
it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) }
|
||||||
it { expect(@v0_0_1).to be <= @v0_1_0 }
|
it { expect(@v0_0_1).to be <= @v0_1_0 }
|
||||||
|
it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 }
|
||||||
|
it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 }
|
||||||
|
it { expect(@v1_1_0_beta1).to be <= @v1_1_0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '==' do
|
describe '==' do
|
||||||
it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
|
it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) }
|
||||||
it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
|
it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) }
|
||||||
it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
|
it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) }
|
||||||
|
it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '!=' do
|
describe '!=' do
|
||||||
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
|
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
|
||||||
|
it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.unknown' do
|
describe '.unknown' do
|
||||||
it { expect(@unknown).not_to be @v0_0_1 }
|
it { expect(@unknown).not_to be @v0_0_1 }
|
||||||
it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
|
it { expect(@unknown).not_to be described_class.new }
|
||||||
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
|
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
|
||||||
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
|
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.parse' do
|
describe '.parse' do
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
|
it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
|
it { expect(described_class.parse("git 1.0b1")).not_to be_valid }
|
||||||
|
|
||||||
|
context 'with parse_suffix: true' do
|
||||||
|
let(:versions) do
|
||||||
|
<<-VERSIONS.lines
|
||||||
|
0.0.1
|
||||||
|
0.1.0
|
||||||
|
1.0.0
|
||||||
|
1.0.1-b1
|
||||||
|
1.0.1-rc1
|
||||||
|
1.0.1-rc2
|
||||||
|
1.0.1
|
||||||
|
1.1.0-beta1
|
||||||
|
1.1.0
|
||||||
|
2.0.0
|
||||||
|
v13.10.0-pre
|
||||||
|
v13.10.0-rc1
|
||||||
|
v13.10.0-rc2
|
||||||
|
v13.10.0
|
||||||
|
v13.10.1~beta.1574.gf6ea9389
|
||||||
|
v13.10.1~beta.1575.gf6ea9389
|
||||||
|
v13.10.1-rc1
|
||||||
|
v13.10.1-rc2
|
||||||
|
v13.10.1
|
||||||
|
VERSIONS
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:parsed_versions) do
|
||||||
|
versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'versions are returned in a correct order' do
|
||||||
|
expect(parsed_versions.shuffle.sort).to eq(parsed_versions)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.to_s' do
|
describe '.to_s' do
|
||||||
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
|
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
|
||||||
|
it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") }
|
||||||
it { expect(@unknown.to_s).to eq("Unknown") }
|
it { expect(@unknown.to_s).to eq("Unknown") }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.hash' do
|
describe '.hash' do
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
|
it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
|
it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
|
it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
|
||||||
|
it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.eql?' do
|
describe '.eql?' do
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
|
it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
|
||||||
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
|
it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
|
||||||
|
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy }
|
||||||
|
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey }
|
||||||
|
it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey }
|
||||||
it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
|
it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
|
||||||
it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
|
it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
|
||||||
it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
|
it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
|
||||||
it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] }
|
it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.same_minor_version?' do
|
describe '.same_minor_version?' do
|
||||||
it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
|
it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
|
||||||
it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
|
it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
|
||||||
|
it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy }
|
||||||
it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
|
it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
|
||||||
it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
|
it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
|
||||||
it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
|
it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
|
||||||
|
@ -98,5 +165,6 @@ RSpec.describe 'Gitlab::VersionInfo' do
|
||||||
it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
|
it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
|
||||||
it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
|
it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
|
||||||
it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
|
it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
|
||||||
|
it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue