diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-09 12:08:55 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-09 12:08:55 +0000 |
commit | ea054ec1c398c2903820284df597ccbd14ec2319 (patch) | |
tree | c8fee7ccbc73ab07534b0a0c452019c805758d2d | |
parent | d9f331328ab89d8423cb43ee9103f2a39b473d7f (diff) | |
download | gitlab-ce-ea054ec1c398c2903820284df597ccbd14ec2319.tar.gz |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 667 insertions, 178 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 79b4b8d48f1..79f8d9c16e3 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -103,6 +103,7 @@ build-assets-image: # We'll also need to pass GITLAB_ASSETS_TAG to the trigerred omnibus-gitlab pipeline similarly to how we do it for trigerred CNG pipelines # https://gitlab.com/gitlab-org/gitlab/issues/208389 - run_timed_command "scripts/build_assets_image" + retry: 2 .frontend-fixtures-base: extends: diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index dbf7bd604ab..f02c1eb67c0 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -1,13 +1,13 @@ build-qa-image: extends: - .use-kaniko - - .default-retry - .review:rules:build-qa-image stage: build-images needs: [] script: - export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}" - /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true + retry: 2 review-cleanup: extends: diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 3dcb96a6c54..626ff647589 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -1,7 +1,5 @@ <script> -/* eslint-disable vue/require-default-prop */ -/* eslint-disable @gitlab/vue-require-i18n-strings */ -import { GlLink, GlModalDirective } from '@gitlab/ui'; +import { GlLink, GlModalDirective, GlSprintf } from '@gitlab/ui'; import { s__, __, sprintf } from '~/locale'; import eventHub from '../event_hub'; import identicon from '../../vue_shared/components/identicon.vue'; @@ -17,6 +15,7 @@ export default { loadingButton, identicon, GlLink, + GlSprintf, UninstallApplicationButton, UninstallApplicationConfirmationModal, UpdateApplicationConfirmationModal, @@ -36,15 +35,17 @@ export default { titleLink: { type: String, required: false, + default: '', }, manageLink: { type: String, required: false, + default: '', }, logoUrl: { type: String, required: false, - default: null, + default: '', }, disabled: { type: Boolean, @@ -59,14 +60,17 @@ export default { status: { type: String, required: false, + default: '', }, statusReason: { type: String, required: false, + default: '', }, requestReason: { type: String, required: false, + default: '', }, installed: { type: Boolean, @@ -81,14 +85,17 @@ export default { installedVia: { type: String, required: false, + default: '', }, version: { type: String, required: false, + default: '', }, chartRepo: { type: String, required: false, + default: '', }, updateAvailable: { type: Boolean, @@ -206,15 +213,6 @@ export default { return sprintf(errorDescription, { title: this.title }); }, - versionLabel() { - if (this.updateFailed) { - return __('Update failed'); - } else if (this.isUpdating) { - return __('Updating'); - } - - return this.updateSuccessful ? __('Updated to') : __('Updated'); - }, updateFailureDescription() { return s__('ClusterIntegration|Update failed. Please check the logs and try again.'); }, @@ -365,14 +363,20 @@ export default { v-if="shouldShowUpdateDetails" class="form-text text-muted label p-0 js-cluster-application-update-details" > - {{ versionLabel }} - <gl-link - v-if="updateSuccessful" - :href="chartRepo" - target="_blank" - class="js-cluster-application-update-version" - >chart v{{ version }}</gl-link - > + <template v-if="updateFailed">{{ __('Update failed') }}</template> + <template v-else-if="isUpdating">{{ __('Updating') }}</template> + <template v-else> + <gl-sprintf :message="__('Updated to %{linkStart}chart v%{linkEnd}')"> + <template #link="{ content }"> + <gl-link + :href="chartRepo" + target="_blank" + class="js-cluster-application-update-version" + >{{ content }}{{ version }}</gl-link + > + </template> + </gl-sprintf> + </template> </div> <div diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 1a9b23161cd..710ad546e64 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -60,11 +60,10 @@ class Projects::ServicesController < Projects::ApplicationController return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end - data = @service.test_data(project, current_user) - outcome = @service.test(data) + result = Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute - unless outcome[:success] - return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } + unless result[:success] + return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true } end {} diff --git a/app/graphql/types/projects/service_type.rb b/app/graphql/types/projects/service_type.rb index 55dd828d4b8..4ae7cb77904 100644 --- a/app/graphql/types/projects/service_type.rb +++ b/app/graphql/types/projects/service_type.rb @@ -6,7 +6,7 @@ module Types include Types::BaseInterface graphql_name 'Service' - # TODO: Add all the fields that we want to expose for the project services intergrations + # TODO: Add all the fields that we want to expose for the project services integrations # https://gitlab.com/gitlab-org/gitlab/-/issues/213088 field :type, GraphQL::STRING_TYPE, null: true, description: 'Class name of the service' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 4723ce9ccdc..754ea70a155 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -215,11 +215,6 @@ class JiraService < IssueTrackerService { success: success, result: result } end - # Jira does not need test data. - def test_data(_, _) - nil - end - override :support_close_issue? def support_close_issue? true diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index a58a264de5e..c11b2f7cc65 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -56,12 +56,6 @@ class PipelinesEmailService < Service project&.ci_pipelines&.any? end - def test_data(project, user) - data = Gitlab::DataBuilder::Pipeline.build(project.ci_pipelines.last) - data[:user] = user.hook_attrs - data - end - def fields [ { type: 'textarea', diff --git a/app/models/service.rb b/app/models/service.rb index e041db76727..2880526c9de 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -144,10 +144,6 @@ class Service < ApplicationRecord data_fields.as_json(only: data_fields.class.column_names).except('id', 'service_id') end - def test_data(project, user) - Gitlab::DataBuilder::Push.build_sample(project, user) - end - def event_channel_names [] end diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb new file mode 100644 index 00000000000..4d551430315 --- /dev/null +++ b/app/services/concerns/integrations/project_test_data.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Integrations + module ProjectTestData + private + + def push_events_data + Gitlab::DataBuilder::Push.build_sample(project, current_user) + end + + def note_events_data + note = project.notes.first + return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present? + + Gitlab::DataBuilder::Note.build(note, current_user) + end + + def issues_events_data + issue = project.issues.first + return { error: s_('TestHooks|Ensure the project has issues.') } unless issue.present? + + issue.to_hook_data(current_user) + end + + def merge_requests_events_data + merge_request = project.merge_requests.first + return { error: s_('TestHooks|Ensure the project has merge requests.') } unless merge_request.present? + + merge_request.to_hook_data(current_user) + end + + def job_events_data + build = project.builds.first + return { error: s_('TestHooks|Ensure the project has CI jobs.') } unless build.present? + + Gitlab::DataBuilder::Build.build(build) + end + + def pipeline_events_data + pipeline = project.ci_pipelines.newest_first.first + return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present? + + Gitlab::DataBuilder::Pipeline.build(pipeline) + end + + def wiki_page_events_data + page = project.wiki.list_pages(limit: 1).first + if !project.wiki_enabled? || page.blank? + return { error: s_('TestHooks|Ensure the wiki is enabled and has pages.') } + end + + Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') + end + + def deployment_events_data + deployment = project.deployments.first + return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present? + + Gitlab::DataBuilder::Deployment.build(deployment) + end + end +end diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb new file mode 100644 index 00000000000..a8a027092d5 --- /dev/null +++ b/app/services/integrations/test/base_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Integrations + module Test + class BaseService + include BaseServiceUtility + + attr_accessor :integration, :current_user, :event + + # @param integration [Service] The external service that will be called + # @param current_user [User] The user calling the service + # @param event [String/nil] The event that triggered this + def initialize(integration, current_user, event = nil) + @integration = integration + @current_user = current_user + @event = event + end + + def execute + if event && (integration.supported_events.exclude?(event) || data.blank?) + return error('Testing not available for this event') + end + + return error(data[:error]) if data[:error].present? + + integration.test(data) + end + + private + + def data + raise NotImplementedError + end + end + end +end diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb new file mode 100644 index 00000000000..941d70c2cc4 --- /dev/null +++ b/app/services/integrations/test/project_service.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Integrations + module Test + class ProjectService < Integrations::Test::BaseService + include Integrations::ProjectTestData + include Gitlab::Utils::StrongMemoize + + def project + strong_memoize(:project) do + integration.project + end + end + + private + + def data + strong_memoize(:data) do + next pipeline_events_data if integration.is_a?(::PipelinesEmailService) + + case event + when 'push', 'tag_push' + push_events_data + when 'note', 'confidential_note' + note_events_data + when 'issue', 'confidential_issue' + issues_events_data + when 'merge_request' + merge_requests_events_data + when 'job' + job_events_data + when 'pipeline' + pipeline_events_data + when 'wiki_page' + wiki_page_events_data + when 'deployment' + deployment_events_data + else + push_events_data + end + end + end + end + end +end + +Integrations::Test::ProjectService.prepend_if_ee('::EE::Integrations::Test::ProjectService') diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb index ebebf29c28b..0fda6fb1ed0 100644 --- a/app/services/test_hooks/base_service.rb +++ b/app/services/test_hooks/base_service.rb @@ -2,6 +2,8 @@ module TestHooks class BaseService + include BaseServiceUtility + attr_accessor :hook, :current_user, :trigger def initialize(hook, current_user, trigger) @@ -12,31 +14,11 @@ module TestHooks def execute trigger_key = hook.class.triggers.key(trigger.to_sym) - trigger_data_method = "#{trigger}_data" - - if trigger_key.nil? || !self.respond_to?(trigger_data_method, true) - return error('Testing not available for this hook') - end - - error_message = catch(:validation_error) do # rubocop:disable Cop/BanCatchThrow - sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend - - return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks - end - - error(error_message) - end - - private - def error(message, http_status = nil) - result = { - message: message, - status: :error - } + return error('Testing not available for this hook') if trigger_key.nil? || data.blank? + return error(data[:error]) if data[:error].present? - result[:http_status] = http_status if http_status - result + hook.execute(data, trigger_key) end end end diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb index aa80cc928b9..4e554dce357 100644 --- a/app/services/test_hooks/project_service.rb +++ b/app/services/test_hooks/project_service.rb @@ -2,6 +2,9 @@ module TestHooks class ProjectService < TestHooks::BaseService + include Integrations::ProjectTestData + include Gitlab::Utils::StrongMemoize + attr_writer :project def project @@ -10,58 +13,25 @@ module TestHooks private - def push_events_data - throw(:validation_error, s_('TestHooks|Ensure the project has at least one commit.')) if project.empty_repo? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Push.build_sample(project, current_user) - end - - alias_method :tag_push_events_data, :push_events_data - - def note_events_data - note = project.notes.first - throw(:validation_error, s_('TestHooks|Ensure the project has notes.')) unless note.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Note.build(note, current_user) - end - - def issues_events_data - issue = project.issues.first - throw(:validation_error, s_('TestHooks|Ensure the project has issues.')) unless issue.present? # rubocop:disable Cop/BanCatchThrow - - issue.to_hook_data(current_user) - end - - alias_method :confidential_issues_events_data, :issues_events_data - - def merge_requests_events_data - merge_request = project.merge_requests.first - throw(:validation_error, s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow - - merge_request.to_hook_data(current_user) - end - - def job_events_data - build = project.builds.first - throw(:validation_error, s_('TestHooks|Ensure the project has CI jobs.')) unless build.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Build.build(build) - end - - def pipeline_events_data - pipeline = project.ci_pipelines.first - throw(:validation_error, s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Pipeline.build(pipeline) - end - - def wiki_page_events_data - page = project.wiki.list_pages(limit: 1).first - if !project.wiki_enabled? || page.blank? - throw(:validation_error, s_('TestHooks|Ensure the wiki is enabled and has pages.')) # rubocop:disable Cop/BanCatchThrow + def data + strong_memoize(:data) do + case trigger + when 'push_events', 'tag_push_events' + push_events_data + when 'note_events' + note_events_data + when 'issues_events', 'confidential_issues_events' + issues_events_data + when 'merge_requests_events' + merge_requests_events_data + when 'job_events' + job_events_data + when 'pipeline_events' + pipeline_events_data + when 'wiki_page_events' + wiki_page_events_data + end end - - Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') end end end diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb index 5c7961f417d..66d78bfc578 100644 --- a/app/services/test_hooks/system_service.rb +++ b/app/services/test_hooks/system_service.rb @@ -2,23 +2,26 @@ module TestHooks class SystemService < TestHooks::BaseService - private - - def push_events_data - Gitlab::DataBuilder::Push.sample_data - end + include Gitlab::Utils::StrongMemoize - def tag_push_events_data - Gitlab::DataBuilder::Push.sample_data - end + private - def repository_update_events_data - Gitlab::DataBuilder::Repository.sample_data + def data + strong_memoize(:data) do + case trigger + when 'push_events', 'tag_push_events' + Gitlab::DataBuilder::Push.sample_data + when 'repository_update_events' + Gitlab::DataBuilder::Repository.sample_data + when 'merge_requests_events' + merge_requests_events_data + end + end end def merge_requests_events_data merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first - throw(:validation_error, s_('TestHooks|Ensure one of your projects has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow + return { error: s_('TestHooks|Ensure one of your projects has merge requests.') } unless merge_request.present? merge_request.to_hook_data(current_user) end diff --git a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb index 8aa3d98aa80..1043616a1c7 100644 --- a/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb +++ b/db/migrate/20200424135319_create_nuget_dependency_link_metadata.rb @@ -7,7 +7,7 @@ class CreateNugetDependencyLinkMetadata < ActiveRecord::Migration[6.0] disable_ddl_transaction! - CONSTRAINT_NAME = 'packages_nuget_dependency_link_metadata_target_framework_constraint' + CONSTRAINT_NAME = 'packages_nuget_dependency_link_metadata_target_framework_constr' def up unless table_exists?(:packages_nuget_dependency_link_metadata) diff --git a/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb b/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb index 40ade22f321..ff60e3bdac1 100644 --- a/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb +++ b/db/migrate/20200519074709_update_resource_state_events_constraint_to_support_epic_id.rb @@ -8,7 +8,7 @@ class UpdateResourceStateEventsConstraintToSupportEpicId < ActiveRecord::Migrati disable_ddl_transaction! OLD_CONSTRAINT = 'resource_state_events_must_belong_to_issue_or_merge_request' - CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request_or_epic' + CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request_or_' def up remove_check_constraint :resource_state_events, OLD_CONSTRAINT diff --git a/db/migrate/20200603073101_change_constraint_name_on_resource_state_events.rb b/db/migrate/20200603073101_change_constraint_name_on_resource_state_events.rb index ae95bc6d3c5..d37027bf2fb 100644 --- a/db/migrate/20200603073101_change_constraint_name_on_resource_state_events.rb +++ b/db/migrate/20200603073101_change_constraint_name_on_resource_state_events.rb @@ -4,7 +4,7 @@ class ChangeConstraintNameOnResourceStateEvents < ActiveRecord::Migration[6.0] DOWNTIME = false NEW_CONSTRAINT_NAME = 'state_events_must_belong_to_issue_or_merge_request_or_epic' - OLD_CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request_or_epic' + OLD_CONSTRAINT_NAME = 'resource_state_events_must_belong_to_issue_or_merge_request_or_' def up execute "ALTER TABLE resource_state_events RENAME CONSTRAINT #{OLD_CONSTRAINT_NAME} TO #{NEW_CONSTRAINT_NAME};" diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 96be057f77e..fd09c31e994 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -3,6 +3,8 @@ module Gitlab module Database module MigrationHelpers + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS + MAX_IDENTIFIER_NAME_LENGTH = 63 BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time @@ -1209,6 +1211,8 @@ into similar problems in the future (e.g. when new tables are created). # # rubocop:disable Gitlab/RailsLogger def add_check_constraint(table, check, constraint_name, validate: true) + validate_check_constraint_name!(constraint_name) + # Transactions would result in ALTER TABLE locks being held for the # duration of the transaction, defeating the purpose of this method. if transaction_open? @@ -1244,6 +1248,8 @@ into similar problems in the future (e.g. when new tables are created). end def validate_check_constraint(table, constraint_name) + validate_check_constraint_name!(constraint_name) + unless check_constraint_exists?(table, constraint_name) raise missing_schema_object_message(table, "check constraint", constraint_name) end @@ -1256,6 +1262,8 @@ into similar problems in the future (e.g. when new tables are created). end def remove_check_constraint(table, constraint_name) + validate_check_constraint_name!(constraint_name) + # DROP CONSTRAINT requires an EXCLUSIVE lock # Use with_lock_retries to make sure that this will not timeout with_lock_retries do @@ -1330,6 +1338,12 @@ into similar problems in the future (e.g. when new tables are created). private + def validate_check_constraint_name!(constraint_name) + if constraint_name.to_s.length > MAX_IDENTIFIER_NAME_LENGTH + raise "The maximum allowed constraint name is #{MAX_IDENTIFIER_NAME_LENGTH} characters" + end + end + def statement_timeout_disabled? # This is a string of the form "100ms" or "0" when disabled connection.select_value('SHOW statement_timeout') == "0" diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c8b04ce2a5c..4cf887a8171 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,7 +2,59 @@ module Gitlab module Regex + module Packages + CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt].freeze + CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze + + def conan_file_name_regex + @conan_file_name_regex ||= + %r{\A#{(CONAN_RECIPE_FILES + CONAN_PACKAGE_FILES).join("|")}\z}.freeze + end + + def conan_package_reference_regex + @conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze + end + + def conan_revision_regex + @conan_revision_regex ||= %r{\A0\z}.freeze + end + + def conan_recipe_component_regex + @conan_recipe_component_regex ||= %r{\A[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{1,49}\z}.freeze + end + + def composer_package_version_regex + @composer_package_version_regex ||= %r{^v?(\d+(\.(\d+|x))*(-.+)?)}.freeze + end + + def package_name_regex + @package_name_regex ||= %r{\A\@?(([\w\-\.\+]*)\/)*([\w\-\.]+)@?(([\w\-\.\+]*)\/)*([\w\-\.]*)\z}.freeze + end + + def maven_file_name_regex + @maven_file_name_regex ||= %r{\A[A-Za-z0-9\.\_\-\+]+\z}.freeze + end + + def maven_path_regex + @maven_path_regex ||= %r{\A\@?(([\w\-\.]*)/)*([\w\-\.\+]*)\z}.freeze + end + + def maven_app_name_regex + @maven_app_name_regex ||= /\A[\w\-\.]+\z/.freeze + end + + def maven_app_group_regex + maven_app_name_regex + end + + def semver_regex + # see the official regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + @semver_regex ||= %r{\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?\z}.freeze + end + end + extend self + extend Packages def project_name_regex # The character range \p{Alnum} overlaps with \u{00A9}-\u{1f9ff} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4c6557ff278..93573d7dee3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21813,7 +21813,7 @@ msgstr "" msgid "TestHooks|Ensure the project has CI pipelines." msgstr "" -msgid "TestHooks|Ensure the project has at least one commit." +msgid "TestHooks|Ensure the project has deployments." msgstr "" msgid "TestHooks|Ensure the project has issues." @@ -23999,7 +23999,7 @@ msgstr "" msgid "Updated at" msgstr "" -msgid "Updated to" +msgid "Updated to %{linkStart}chart v%{linkEnd}" msgstr "" msgid "Updating" diff --git a/spec/factories/services.rb b/spec/factories/services.rb index b6696769da9..5364f62b2c5 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -165,6 +165,20 @@ FactoryBot.define do type { 'SlackService' } end + factory :github_service do + project + type { 'GithubService' } + active { true } + token { 'github-token' } + end + + factory :pipelines_email_service do + project + active { true } + type { 'PipelinesEmailService' } + recipients { 'test@example.com' } + end + # this is for testing storing values inside properties, which is deprecated and will be removed in # https://gitlab.com/gitlab-org/gitlab/issues/29404 trait :without_properties_callback do diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index ad1876296bb..94bdd7b7778 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlSprintf } from '@gitlab/ui'; import eventHub from '~/clusters/event_hub'; import { APPLICATION_STATUS, ELASTIC_STACK } from '~/clusters/constants'; import ApplicationRow from '~/clusters/components/application_row.vue'; @@ -16,6 +17,7 @@ describe('Application Row', () => { const mountComponent = data => { wrapper = shallowMount(ApplicationRow, { + stubs: { GlSprintf }, propsData: { ...DEFAULT_APPLICATION_STATE, ...data, @@ -356,6 +358,9 @@ describe('Application Row', () => { }); describe('Version', () => { + const updateDetails = () => wrapper.find('.js-cluster-application-update-details'); + const versionEl = () => wrapper.find('.js-cluster-application-update-version'); + it('displays a version number if application has been updated', () => { const version = '0.1.45'; mountComponent({ @@ -363,12 +368,8 @@ describe('Application Row', () => { updateSuccessful: true, version, }); - const updateDetails = wrapper.find('.js-cluster-application-update-details'); - const versionEl = wrapper.find('.js-cluster-application-update-version'); - expect(updateDetails.text()).toContain('Updated'); - expect(versionEl.exists()).toBe(true); - expect(versionEl.text()).toContain(version); + expect(updateDetails().text()).toBe(`Updated to chart v${version}`); }); it('contains a link to the chart repo if application has been updated', () => { @@ -380,10 +381,9 @@ describe('Application Row', () => { chartRepo, version, }); - const versionEl = wrapper.find('.js-cluster-application-update-version'); - expect(versionEl.attributes('href')).toEqual(chartRepo); - expect(versionEl.props('target')).toEqual('_blank'); + expect(versionEl().attributes('href')).toEqual(chartRepo); + expect(versionEl().props('target')).toEqual('_blank'); }); it('does not display a version number if application update failed', () => { @@ -393,11 +393,20 @@ describe('Application Row', () => { updateFailed: true, version, }); - const updateDetails = wrapper.find('.js-cluster-application-update-details'); - const versionEl = wrapper.find('.js-cluster-application-update-version'); - expect(updateDetails.text()).toContain('failed'); - expect(versionEl.exists()).toBe(false); + expect(updateDetails().text()).toBe('Update failed'); + expect(versionEl().exists()).toBe(false); + }); + + it('displays updating when the application update is currently updating', () => { + mountComponent({ + status: APPLICATION_STATUS.UPDATING, + updateSuccessful: true, + version: '1.2.3', + }); + + expect(updateDetails().text()).toBe('Updating'); + expect(versionEl().exists()).toBe(false); }); }); diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 2545ea2e0ea..bed444ee7c7 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -2075,6 +2075,34 @@ describe Gitlab::Database::MigrationHelpers do allow(model).to receive(:check_constraint_exists?).and_return(false) end + context 'constraint name validation' do + it 'raises an error when too long' do + expect do + model.add_check_constraint( + :test_table, + 'name IS NOT NULL', + 'a' * (Gitlab::Database::MigrationHelpers::MAX_IDENTIFIER_NAME_LENGTH + 1) + ) + end.to raise_error(RuntimeError) + end + + it 'does not raise error when the length is acceptable' do + constraint_name = 'a' * Gitlab::Database::MigrationHelpers::MAX_IDENTIFIER_NAME_LENGTH + + expect(model).to receive(:transaction_open?).and_return(false) + expect(model).to receive(:check_constraint_exists?).and_return(false) + expect(model).to receive(:with_lock_retries).and_call_original + expect(model).to receive(:execute).with(/ADD CONSTRAINT/) + + model.add_check_constraint( + :test_table, + 'name IS NOT NULL', + constraint_name, + validate: false + ) + end + end + context 'inside a transaction' do it 'raises an error' do expect(model).to receive(:transaction_open?).and_return(true) diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 9e596400904..18c83f407d0 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -163,4 +163,115 @@ describe Gitlab::Regex do it { is_expected.not_to match('-foo-') } it { is_expected.not_to match('foo/bar') } end + + describe '.conan_file_name_regex' do + subject { described_class.conan_file_name_regex } + + it { is_expected.to match('conanfile.py') } + it { is_expected.to match('conan_package.tgz') } + it { is_expected.not_to match('foo.txt') } + it { is_expected.not_to match('!!()()') } + end + + describe '.conan_package_reference_regex' do + subject { described_class.conan_package_reference_regex } + + it { is_expected.to match('123456789') } + it { is_expected.to match('asdf1234') } + it { is_expected.not_to match('@foo') } + it { is_expected.not_to match('0/pack+age/1@1/0') } + it { is_expected.not_to match('!!()()') } + end + + describe '.conan_revision_regex' do + subject { described_class.conan_revision_regex } + + it { is_expected.to match('0') } + it { is_expected.not_to match('foo') } + it { is_expected.not_to match('!!()()') } + end + + describe '.conan_recipe_component_regex' do + subject { described_class.conan_recipe_component_regex } + + let(:fifty_one_characters) { 'f_a' * 17} + + it { is_expected.to match('foobar') } + it { is_expected.to match('foo_bar') } + it { is_expected.to match('foo+bar') } + it { is_expected.to match('_foo+bar-baz+1.0') } + it { is_expected.to match('1.0.0') } + it { is_expected.not_to match('-foo_bar') } + it { is_expected.not_to match('+foo_bar') } + it { is_expected.not_to match('.foo_bar') } + it { is_expected.not_to match('foo@bar') } + it { is_expected.not_to match('foo/bar') } + it { is_expected.not_to match('!!()()') } + it { is_expected.not_to match(fifty_one_characters) } + end + + describe '.package_name_regex' do + subject { described_class.package_name_regex } + + it { is_expected.to match('123') } + it { is_expected.to match('foo') } + it { is_expected.to match('foo/bar') } + it { is_expected.to match('@foo/bar') } + it { is_expected.to match('com/mycompany/app/my-app') } + it { is_expected.to match('my-package/1.0.0@my+project+path/beta') } + it { is_expected.not_to match('my-package/1.0.0@@@@@my+project+path/beta') } + it { is_expected.not_to match('$foo/bar') } + it { is_expected.not_to match('@foo/@/bar') } + it { is_expected.not_to match('@@foo/bar') } + it { is_expected.not_to match('my package name') } + it { is_expected.not_to match('!!()()') } + it { is_expected.not_to match("..\n..\foo") } + end + + describe '.maven_file_name_regex' do + subject { described_class.maven_file_name_regex } + + it { is_expected.to match('123') } + it { is_expected.to match('foo') } + it { is_expected.to match('foo+bar-2_0.pom') } + it { is_expected.to match('foo.bar.baz-2.0-20190901.47283-1.jar') } + it { is_expected.to match('maven-metadata.xml') } + it { is_expected.to match('1.0-SNAPSHOT') } + it { is_expected.not_to match('../../foo') } + it { is_expected.not_to match('..\..\foo') } + it { is_expected.not_to match('%2f%2e%2e%2f%2essh%2fauthorized_keys') } + it { is_expected.not_to match('$foo/bar') } + it { is_expected.not_to match('my file name') } + it { is_expected.not_to match('!!()()') } + end + + describe '.maven_path_regex' do + subject { described_class.maven_path_regex } + + it { is_expected.to match('123') } + it { is_expected.to match('foo') } + it { is_expected.to match('foo/bar') } + it { is_expected.to match('@foo/bar') } + it { is_expected.to match('com/mycompany/app/my-app') } + it { is_expected.to match('com/mycompany/app/my-app/1.0-SNAPSHOT') } + it { is_expected.to match('com/mycompany/app/my-app/1.0-SNAPSHOT+debian64') } + it { is_expected.not_to match('com/mycompany/app/my+app/1.0-SNAPSHOT') } + it { is_expected.not_to match('$foo/bar') } + it { is_expected.not_to match('@foo/@/bar') } + it { is_expected.not_to match('my package name') } + it { is_expected.not_to match('!!()()') } + end + + describe '.semver_regex' do + subject { described_class.semver_regex } + + it { is_expected.to match('1.2.3') } + it { is_expected.to match('1.2.3-beta') } + it { is_expected.to match('1.2.3-alpha.3') } + it { is_expected.not_to match('1') } + it { is_expected.not_to match('1.2') } + it { is_expected.not_to match('1./2.3') } + it { is_expected.not_to match('../../../../../1.2.3') } + it { is_expected.not_to match('%2e%2e%2f1.2.3') } + end end diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index f29414c80c9..de1edf2099a 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -37,22 +37,6 @@ describe PipelinesEmailService, :mailer do end end - describe '#test_data' do - let(:build) { create(:ci_build) } - let(:project) { build.project } - let(:user) { create(:user) } - - before do - project.add_developer(user) - end - - it 'builds test data' do - data = subject.test_data(project, user) - - expect(data[:object_kind]).to eq('pipeline') - end - end - shared_examples 'sending email' do |branches_to_be_notified: nil| before do subject.recipients = recipients diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb new file mode 100644 index 00000000000..fdb43ca345a --- /dev/null +++ b/spec/services/integrations/test/project_service_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Integrations::Test::ProjectService do + let(:user) { double('user') } + + describe '#execute' do + let(:project) { create(:project) } + let(:integration) { create(:slack_service, project: project) } + let(:event) { nil } + let(:sample_data) { { data: 'sample' } } + let(:success_result) { { success: true, result: {} } } + + subject { described_class.new(integration, user, event).execute } + + context 'without event specified' do + it 'tests the integration with default data' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + + context 'PipelinesEmailService' do + let(:integration) { create(:pipelines_email_service, project: project) } + + it_behaves_like 'tests for integration with pipeline data' + end + end + + context 'with event specified' do + context 'event not supported by integration' do + let(:integration) { create(:jira_service, project: project) } + let(:event) { 'push' } + + it 'returns error message' do + expect(subject).to include({ status: :error, message: 'Testing not available for this event' }) + end + end + + context 'push' do + let(:event) { 'push' } + + it 'executes integration' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'tag_push' do + let(:event) { 'tag_push' } + + it 'executes integration' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'note' do + let(:event) { 'note' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has notes.' }) + end + + it 'executes integration' do + allow(project).to receive(:notes).and_return([Note.new]) + allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'issue' do + let(:event) { 'issue' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes integration' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'confidential_issue' do + let(:event) { 'confidential_issue' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes integration' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'merge_request' do + let(:event) { 'merge_request' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has merge requests.' }) + end + + it 'executes integration' do + create(:merge_request, source_project: project) + allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'deployment' do + let(:project) { create(:project, :test_repo) } + let(:event) { 'deployment' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has deployments.' }) + end + + it 'executes integration' do + create(:deployment, project: project) + allow(Gitlab::DataBuilder::Deployment).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'pipeline' do + let(:event) { 'pipeline' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has CI pipelines.' }) + end + + it 'executes integration' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'wiki_page' do + let(:project) { create(:project, :wiki_repo) } + let(:event) { 'wiki_page' } + + it 'returns error message if wiki disabled' do + allow(project).to receive(:wiki_enabled?).and_return(false) + + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'executes integration' do + create(:wiki_page, wiki: project.wiki) + allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + end + end +end diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index 8d30f5018dd..3c5bc0d85f2 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -31,15 +31,7 @@ describe TestHooks::ProjectService do let(:trigger) { 'push_events' } let(:trigger_key) { :push_hooks } - it 'returns error message if not enough data' do - allow(project).to receive(:empty_repo?).and_return(true) - - expect(hook).not_to receive(:execute) - expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) - end - it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result) @@ -51,15 +43,7 @@ describe TestHooks::ProjectService do let(:trigger) { 'tag_push_events' } let(:trigger_key) { :tag_push_hooks } - it 'returns error message if not enough data' do - allow(project).to receive(:empty_repo?).and_return(true) - - expect(hook).not_to receive(:execute) - expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) - end - it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result) diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb index 799b57eb04e..8a86b14a2a1 100644 --- a/spec/services/test_hooks/system_service_spec.rb +++ b/spec/services/test_hooks/system_service_spec.rb @@ -29,7 +29,6 @@ describe TestHooks::SystemService do let(:trigger_key) { :push_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result) @@ -55,7 +54,6 @@ describe TestHooks::SystemService do let(:trigger_key) { :repository_update_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) expect(Gitlab::DataBuilder::Repository).to receive(:sample_data).and_call_original expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key).and_return(success_result) diff --git a/spec/support/shared_examples/integrations/test_examples.rb b/spec/support/shared_examples/integrations/test_examples.rb new file mode 100644 index 00000000000..eb2e83ce5d1 --- /dev/null +++ b/spec/support/shared_examples/integrations/test_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'tests for integration with pipeline data' do + it 'tests the integration with pipeline data' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end +end |