diff options
59 files changed, 762 insertions, 110 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 80628bb5523..311e0b7e11e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -409,6 +409,7 @@ Database/MultipleDatabases: - 'ee/lib/ee/gitlab/background_migration/**/*.rb' - 'spec/lib/gitlab/background_migration/**/*.rb' - 'spec/lib/gitlab/database/**/*.rb' + - 'spec/tasks/gitlab/db_rake_spec.rb' Migration/BatchMigrationsPostOnly: Enabled: true diff --git a/.rubocop_todo/database/multiple_databases.yml b/.rubocop_todo/database/multiple_databases.yml index 43da6f8a5b4..be91da8aad4 100644 --- a/.rubocop_todo/database/multiple_databases.yml +++ b/.rubocop_todo/database/multiple_databases.yml @@ -5,10 +5,3 @@ Database/MultipleDatabases: - 'db/post_migrate/20210811122206_update_external_project_bots.rb' - 'db/post_migrate/20210812013042_remove_duplicate_project_authorizations.rb' - 'ee/spec/services/ee/merge_requests/update_service_spec.rb' - - 'spec/support/caching.rb' - - 'spec/support/helpers/database/database_helpers.rb' - - 'spec/support/helpers/database/table_schema_helpers.rb' - - 'spec/support/helpers/migrations_helpers.rb' - - 'spec/support/helpers/query_recorder.rb' - - 'spec/support/helpers/usage_data_helpers.rb' - - 'spec/tasks/gitlab/db_rake_spec.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 07e7b374db2..0910e63f819 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -3156,7 +3156,6 @@ Layout/LineLength: - 'lib/gitlab/database/reflection.rb' - 'lib/gitlab/database/reindexing.rb' - 'lib/gitlab/database/reindexing/coordinator.rb' - - 'lib/gitlab/database/reindexing/grafana_notifier.rb' - 'lib/gitlab/database/reindexing/reindex_concurrently.rb' - 'lib/gitlab/database/schema_migrations/context.rb' - 'lib/gitlab/database/similarity_score.rb' @@ -4458,7 +4457,6 @@ Layout/LineLength: - 'spec/lib/gitlab/database/query_analyzer_spec.rb' - 'spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb' - 'spec/lib/gitlab/database/query_analyzers/restrict_allowed_schemas_spec.rb' - - 'spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb' - 'spec/lib/gitlab/database/reindexing/reindex_concurrently_spec.rb' - 'spec/lib/gitlab/database/reindexing_spec.rb' - 'spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb' @@ -574,6 +574,7 @@ gem 'arr-pm', '~> 0.0.12' # Apple plist parsing gem 'CFPropertyList' +gem 'app_store_connect' # For phone verification gem 'telesignenterprise', '~> 2.2' diff --git a/Gemfile.checksum b/Gemfile.checksum index 7de8a753de4..f13fab503a1 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -20,6 +20,7 @@ {"name":"akismet","version":"3.0.0","platform":"ruby","checksum":"74991b8e3d3257eeea996b47069abb8da2006c84a144255123e8dffd1c86b230"}, {"name":"android_key_attestation","version":"0.3.0","platform":"ruby","checksum":"467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b"}, {"name":"apollo_upload_server","version":"2.1.0","platform":"ruby","checksum":"e5f3c9dda0c2ca775d007072742b98d517dfd91a667111fedbcdc94dfabd904e"}, +{"name":"app_store_connect","version":"0.29.0","platform":"ruby","checksum":"01d7a923825a4221892099acb5a72f86f6ee7d8aa95815d3c459ba6816ea430f"}, {"name":"arr-pm","version":"0.0.12","platform":"ruby","checksum":"fdff482f75239239201f4d667d93424412639aad0b3b0ad4d827e7c637e0ad39"}, {"name":"asana","version":"0.10.13","platform":"ruby","checksum":"36d0d37f8dd6118a54580f1b80224875d7b6a9027598938e1722a508bfc2d7ac"}, {"name":"asciidoctor","version":"2.0.17","platform":"ruby","checksum":"ed5b5e399e8d64994cc16f0983f993d6e33990909a8415b6fc8b786cdeb00f3d"}, diff --git a/Gemfile.lock b/Gemfile.lock index d78ac593e4f..2737513fed5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -172,6 +172,9 @@ GEM apollo_upload_server (2.1.0) actionpack (>= 4.2) graphql (>= 1.8) + app_store_connect (0.29.0) + activesupport (>= 6.0.0) + jwt (>= 1.4, <= 2.5.0) arr-pm (0.0.12) asana (0.10.13) faraday (~> 1.0) @@ -1581,6 +1584,7 @@ DEPENDENCIES addressable (~> 2.8) akismet (~> 3.0) apollo_upload_server (~> 2.1.0) + app_store_connect arr-pm (~> 0.0.12) asana (~> 0.10.13) asciidoctor (~> 2.0.17) diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js index fbd2ce4ce69..dbb68b7facd 100644 --- a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js +++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js @@ -1,7 +1,12 @@ -import { cleanEndingSeparator } from '~/lib/utils/url_utility'; +import { cleanEndingSeparator, joinPaths } from '~/lib/utils/url_utility'; const getBaseUrl = () => { - const baseUrlObj = new URL(process.env.GITLAB_WEB_IDE_PUBLIC_PATH, window.location.origin); + const path = joinPaths( + '/', + window.gon.relative_url_root || '', + process.env.GITLAB_WEB_IDE_PUBLIC_PATH, + ); + const baseUrlObj = new URL(path, window.location.origin); return cleanEndingSeparator(baseUrlObj.href); }; diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb index 74d998503b7..1da612893ce 100644 --- a/app/controllers/concerns/integrations/params.rb +++ b/app/controllers/concerns/integrations/params.rb @@ -5,6 +5,9 @@ module Integrations extend ActiveSupport::Concern ALLOWED_PARAMS_CE = [ + :app_store_issuer_id, + :app_store_key_id, + :app_store_private_key, :active, :alert_events, :api_key, diff --git a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb index c6e32be245d..2ea7a02bf15 100644 --- a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb +++ b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb @@ -20,7 +20,7 @@ module Issues end def preloads - { + preload_hash = { alert_management_alert: [:alert_management_alert], assignees: [:assignees], participants: Issue.participant_includes, @@ -28,6 +28,9 @@ module Issues customer_relations_contacts: { customer_relations_contacts: [:group] }, escalation_status: [:incident_management_issuable_escalation_status] } + preload_hash[:type] = :work_item_type if Feature.enabled?(:issue_type_uses_work_item_types_table) + + preload_hash end end end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index dd2ad26ce49..4948063610a 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -117,7 +117,6 @@ module Types description: 'Collection of design images associated with this issue.' field :type, Types::IssueTypeEnum, null: true, - method: :issue_type, description: 'Type of the issue.' field :alert_management_alert, @@ -198,6 +197,14 @@ module Types def escalation_status object.supports_escalation? ? object.escalation_status&.status_name : nil end + + def type + if Feature.enabled?(:issue_type_uses_work_item_types_table) + object.work_item_type.base_type + else + object.issue_type + end + end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 111d2797fed..d45f770a0d2 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -68,6 +68,7 @@ module Ci delegate :service_specification, to: :runner_session, allow_nil: true delegate :gitlab_deploy_token, to: :project delegate :harbor_integration, to: :project + delegate :apple_app_store_integration, to: :project delegate :trigger_short_token, to: :trigger_request, allow_nil: true delegate :ensure_persistent_ref, to: :pipeline delegate :enable_debug_trace!, to: :metadata @@ -587,6 +588,7 @@ module Ci .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false) .concat(deploy_token_variables) .concat(harbor_variables) + .concat(apple_app_store_variables) end end @@ -630,6 +632,13 @@ module Ci Gitlab::Ci::Variables::Collection.new(harbor_integration.ci_variables) end + def apple_app_store_variables + return [] unless apple_app_store_integration.try(:activated?) + return [] unless pipeline.protected_ref? + + Gitlab::Ci::Variables::Collection.new(apple_app_store_integration.ci_variables) + end + def features { trace_sections: true, diff --git a/app/models/integration.rb b/app/models/integration.rb index 290bee6537e..df1560b87a5 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -281,10 +281,13 @@ class Integration < ApplicationRecord # Returns a list of available integration names. # Example: ["asana", ...] # @deprecated - def self.available_integration_names(include_project_specific: true, include_dev: true) + # @param [Boolean] include_feature_flagged used only in specs + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/386731 + def self.available_integration_names(include_project_specific: true, include_dev: true, include_feature_flagged: false) names = integration_names names += project_specific_integration_names if include_project_specific names += dev_integration_names if include_dev + names << 'apple_app_store' if Feature.enabled?(:apple_app_store_integration) || include_feature_flagged names.sort_by(&:downcase) end diff --git a/app/models/integrations/apple_app_store.rb b/app/models/integrations/apple_app_store.rb new file mode 100644 index 00000000000..84185542939 --- /dev/null +++ b/app/models/integrations/apple_app_store.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'app_store_connect' + +module Integrations + class AppleAppStore < Integration + ISSUER_ID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/.freeze + KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/.freeze + + with_options if: :activated? do + validates :app_store_issuer_id, presence: true, format: { with: ISSUER_ID_REGEX } + validates :app_store_key_id, presence: true, format: { with: KEY_ID_REGEX } + validates :app_store_private_key, presence: true, certificate_key: true + end + + field :app_store_issuer_id, + section: SECTION_TYPE_CONNECTION, + required: true, + title: -> { s_('AppleAppStore|The Apple App Store Connect Issuer ID.') } + + field :app_store_key_id, + section: SECTION_TYPE_CONNECTION, + required: true, + title: -> { s_('AppleAppStore|The Apple App Store Connect Key ID.') }, + is_secret: false + + field :app_store_private_key, + section: SECTION_TYPE_CONNECTION, + required: true, + type: 'textarea', + title: -> { s_('AppleAppStore|The Apple App Store Connect Private Key.') }, + is_secret: false + + def title + 'Apple App Store Connect' + end + + def description + s_('AppleAppStore|Use GitLab to build and release an app in the Apple App Store.') + end + + def help + variable_list = [ + '<code>APP_STORE_CONNECT_API_KEY_ISSUER_ID</code>', + '<code>APP_STORE_CONNECT_API_KEY_KEY_ID</code>', + '<code>APP_STORE_CONNECT_API_KEY_KEY</code>' + ] + + # rubocop:disable Layout/LineLength + texts = [ + s_("Use the Apple App Store Connect integration to easily connect to the Apple App Store with Fastlane in CI/CD pipelines."), + s_("After the Apple App Store Connect integration is activated, the following protected variables will be created for CI/CD use."), + variable_list.join('<br>'), + s_(format("To get started, see the <a href='%{url}' target='_blank'>integration documentation</a> for instructions on how to generate App Store Connect credentials, and how to use this integration.", url: "https://docs.gitlab.com/ee/integration/apple_app_store.html")).html_safe + ] + # rubocop:enable Layout/LineLength + + texts.join('<br><br>'.html_safe) + end + + def self.to_param + 'apple_app_store' + end + + def self.supported_events + [] + end + + def sections + [ + { + type: SECTION_TYPE_CONNECTION, + title: s_('Integrations|Integration details'), + description: help + } + ] + end + + def test(*_args) + response = client.apps + if response.has_key?(:errors) + { success: false, message: response[:errors].first[:title] } + else + { success: true } + end + end + + def ci_variables + return [] unless activated? + + [ + { key: 'APP_STORE_CONNECT_API_KEY_ISSUER_ID', value: app_store_issuer_id, masked: true, public: false }, + { key: 'APP_STORE_CONNECT_API_KEY_KEY', value: Base64.encode64(app_store_private_key), masked: true, + public: false }, + { key: 'APP_STORE_CONNECT_API_KEY_KEY_ID', value: app_store_key_id, masked: true, public: false } + ] + end + + private + + def client + config = { + issuer_id: app_store_issuer_id, + key_id: app_store_key_id, + private_key: app_store_private_key + } + + AppStoreConnect::Client.new(config) + end + end +end diff --git a/app/models/integrations/field.rb b/app/models/integrations/field.rb index 53c8f5f623e..329c046075f 100644 --- a/app/models/integrations/field.rb +++ b/app/models/integrations/field.rb @@ -4,7 +4,7 @@ module Integrations class Field SECRET_NAME = %r/token|key|password|passphrase|secret/.freeze - BOOLEAN_ATTRIBUTES = %i[required api_only exposes_secrets].freeze + BOOLEAN_ATTRIBUTES = %i[required api_only is_secret exposes_secrets].freeze ATTRIBUTES = %i[ section type placeholder choices value checkbox_label @@ -17,12 +17,13 @@ module Integrations attr_reader :name, :integration_class - def initialize(name:, integration_class:, type: 'text', api_only: false, **attributes) + def initialize(name:, integration_class:, type: 'text', is_secret: true, api_only: false, **attributes) @name = name.to_s.freeze @integration_class = integration_class - attributes[:type] = SECRET_NAME.match?(@name) ? 'password' : type + attributes[:type] = SECRET_NAME.match?(@name) && is_secret ? 'password' : type attributes[:api_only] = api_only + attributes[:is_secret] = is_secret @attributes = attributes.freeze invalid_attributes = attributes.keys - ATTRIBUTES diff --git a/app/models/project.rb b/app/models/project.rb index 217e801e07f..116f81207f6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -170,6 +170,7 @@ class Project < ApplicationRecord end # Project integrations + has_one :apple_app_store_integration, class_name: 'Integrations::AppleAppStore' has_one :asana_integration, class_name: 'Integrations::Asana' has_one :assembla_integration, class_name: 'Integrations::Assembla' has_one :bamboo_integration, class_name: 'Integrations::Bamboo' diff --git a/config/feature_flags/development/apple_app_store_integration.yml b/config/feature_flags/development/apple_app_store_integration.yml new file mode 100644 index 00000000000..ec55f1ef932 --- /dev/null +++ b/config/feature_flags/development/apple_app_store_integration.yml @@ -0,0 +1,8 @@ +--- +name: apple_app_store_integration +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/385335 +milestone: '15.8' +type: development +group: group::incubation +default_enabled: false diff --git a/config/feature_flags/development/issue_type_uses_work_item_types_table.yml b/config/feature_flags/development/issue_type_uses_work_item_types_table.yml new file mode 100644 index 00000000000..4ce66b7ab33 --- /dev/null +++ b/config/feature_flags/development/issue_type_uses_work_item_types_table.yml @@ -0,0 +1,8 @@ +--- +name: issue_type_uses_work_item_types_table +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107690 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/386603 +milestone: '15.8' +type: development +group: group::project management +default_enabled: false diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb index cdd91bac53f..a3a2550811b 100644 --- a/config/initializers/doorkeeper_openid_connect.rb +++ b/config/initializers/doorkeeper_openid_connect.rb @@ -31,8 +31,9 @@ Doorkeeper::OpenidConnect.configure do Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}" end - o.claim(:name) { |user| user.name } - o.claim(:nickname) { |user| user.username } + o.claim(:name) { |user| user.name } + o.claim(:nickname) { |user| user.username } + o.claim(:preferred_username) { |user| user.username } # Check whether the application has access to the email scope, and grant # access to the user's primary email address if so, otherwise their diff --git a/config/metrics/counts_all/20221209212603_projects_inheriting_apple_app_store_active.yml b/config/metrics/counts_all/20221209212603_projects_inheriting_apple_app_store_active.yml new file mode 100644 index 00000000000..5e00246a15c --- /dev/null +++ b/config/metrics/counts_all/20221209212603_projects_inheriting_apple_app_store_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.projects_inheriting_apple_app_store_active +description: Count of active projects inheriting integrations for Apple App Store +product_section: dev +product_stage: manage +product_group: integrations +product_category: integrations +value_type: number +status: active +milestone: "15.8" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20221209213642_groups_apple_app_store_active.yml b/config/metrics/counts_all/20221209213642_groups_apple_app_store_active.yml new file mode 100644 index 00000000000..9099752c62c --- /dev/null +++ b/config/metrics/counts_all/20221209213642_groups_apple_app_store_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.groups_apple_app_store_active +description: Count of active groups inheriting integrations for Apple App Store +product_section: dev +product_stage: manage +product_group: integrations +product_category: integrations +value_type: number +status: active +milestone: "15.8" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20221209214020_projects_apple_app_store_active.yml b/config/metrics/counts_all/20221209214020_projects_apple_app_store_active.yml new file mode 100644 index 00000000000..92e9acbcca0 --- /dev/null +++ b/config/metrics/counts_all/20221209214020_projects_apple_app_store_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.projects_apple_app_store_active +description: Count of projects with active integrations for Apple App Store +product_section: dev +product_stage: manage +product_group: integrations +product_category: integrations +value_type: number +status: active +milestone: "15.8" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20221209233053_groups_inheriting_apple_app_store_active.yml b/config/metrics/counts_all/20221209233053_groups_inheriting_apple_app_store_active.yml new file mode 100644 index 00000000000..f7835a4e072 --- /dev/null +++ b/config/metrics/counts_all/20221209233053_groups_inheriting_apple_app_store_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.groups_inheriting_apple_app_store_active +description: Count of active groups inheriting integrations for Apple App Store +product_section: dev +product_stage: manage +product_group: integrations +product_category: integrations +value_type: number +status: active +milestone: "15.8" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20221209233201_instances_apple_app_store_active.yml b/config/metrics/counts_all/20221209233201_instances_apple_app_store_active.yml new file mode 100644 index 00000000000..436f869cf0d --- /dev/null +++ b/config/metrics/counts_all/20221209233201_instances_apple_app_store_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.instances_apple_app_store_active +description: Count of instances with active integrations for Apple App Store +product_section: dev +product_stage: manage +product_group: integrations +product_category: integrations +value_type: number +status: active +milestone: "15.8" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888 +time_frame: all +data_source: database +data_category: optional +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/data/deprecations/15-8-conan-search-limited-to-project.yml b/data/deprecations/15-8-conan-search-limited-to-project.yml new file mode 100644 index 00000000000..e99e0a34ee8 --- /dev/null +++ b/data/deprecations/15-8-conan-search-limited-to-project.yml @@ -0,0 +1,13 @@ +- title: "Conan project-level search endpoint returns project-specific results" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead. + announcement_milestone: "15.8" # (required) The milestone when this feature was first announced as deprecated. + announcement_date: "2023-01-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed + removal_date: "2023-05-22" # (required) The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + breaking_change: true # (required) If this deprecation is a breaking change, set this value to true + reporter: trizzi # (required) GitLab username of the person reporting the deprecation + stage: Package # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/384455 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + You can use the GitLab Conan repository with [project-level](https://docs.gitlab.com/ee/user/packages/conan_repository/#add-a-remote-for-your-project) or [instance-level](https://docs.gitlab.com/ee/user/packages/conan_repository/#add-a-remote-for-your-instance) endpoints. Each level supports the conan search command. However, the search endpoint for the project level is also returning packages from outside the target project. + + This unintended functionality is deprecated in GitLab 15.8 and will be removed in GitLab 16.0. The search endpoint for the project level will only return packages from the target project. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 27565421489..8c2f10cd9ab 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -22776,6 +22776,7 @@ State of a Sentry error. | Value | Description | | ----- | ----------- | +| <a id="servicetypeapple_app_store_service"></a>`APPLE_APP_STORE_SERVICE` | AppleAppStoreService type. | | <a id="servicetypeasana_service"></a>`ASANA_SERVICE` | AsanaService type. | | <a id="servicetypeassembla_service"></a>`ASSEMBLA_SERVICE` | AssemblaService type. | | <a id="servicetypebamboo_service"></a>`BAMBOO_SERVICE` | BambooService type. | diff --git a/doc/api/integrations.md b/doc/api/integrations.md index f6ad095aad6..24e0f189aad 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -72,6 +72,44 @@ Example response: ] ``` +## Apple App Store + +Use GitLab to build and release an app in the Apple App Store. + +See also the [Apple App Store integration documentation](../user/project/integrations/apple_app_store.md). + +### Create/Edit Apple App Store integration + +Set Apple App Store integration for a project. + +```plaintext +PUT /projects/:id/integrations/apple_app_store +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `app_store_issuer_id` | string | true | The Apple App Store Connect Issuer ID. | +| `app_store_key_id` | string | true | The Apple App Store Connect Key ID. | +| `app_store_private_key` | string | true | The Apple App Store Connect Private Key. | + +### Disable Apple App Store integration + +Disable the Apple App Store integration for a project. Integration settings are preserved. + +```plaintext +DELETE /projects/:id/integrations/apple_app_store +``` + +### Get Apple App Store integration settings + +Get Apple App Store integration settings for a project. + +```plaintext +GET /projects/:id/integrations/apple_app_store +``` + ## Asana Add commit messages as comments to Asana tasks. diff --git a/doc/integration/openid_connect_provider.md b/doc/integration/openid_connect_provider.md index ad4cf195d7b..b0d85fae8c2 100644 --- a/doc/integration/openid_connect_provider.md +++ b/doc/integration/openid_connect_provider.md @@ -48,19 +48,20 @@ Similar URLs can be used for other GitLab instances. The following user information is shared with clients: -| Claim | Type | Description | -|:-----------------|:----------|:------------| -| `sub` | `string` | The ID of the user | -| `auth_time` | `integer` | The timestamp for the user's last authentication | -| `name` | `string` | The user's full name | -| `nickname` | `string` | The user's GitLab username | -| `email` | `string` | The user's email address<br>This is the user's *primary* email address if the application has access to the `email` claim and the user's *public* email address otherwise | -| `email_verified` | `boolean` | Whether the user's email address was verified | -| `website` | `string` | URL for the user's website | -| `profile` | `string` | URL for the user's GitLab profile | -| `picture` | `string` | URL for the user's GitLab avatar | -| `groups` | `array` | Paths for the groups the user is a member of, either directly or through an ancestor group. | -| `groups_direct` | `array` | Paths for the groups the user is a direct member of. | +| Claim | Type | Description | +|:---------------------|:----------|:------------| +| `sub` | `string` | The ID of the user | +| `auth_time` | `integer` | The timestamp for the user's last authentication | +| `name` | `string` | The user's full name | +| `nickname` | `string` | The user's GitLab username | +| `preferred_username` | `string` | The user's GitLab username | +| `email` | `string` | The user's email address<br>This is the user's *primary* email address if the application has access to the `email` claim and the user's *public* email address otherwise | +| `email_verified` | `boolean` | Whether the user's email address was verified | +| `website` | `string` | URL for the user's website | +| `profile` | `string` | URL for the user's GitLab profile | +| `picture` | `string` | URL for the user's GitLab avatar | +| `groups` | `array` | Paths for the groups the user is a member of, either directly or through an ancestor group. | +| `groups_direct` | `array` | Paths for the groups the user is a direct member of. | | `https://gitlab.org/claims/groups/owner` | `array` | Names of the groups the user is a direct member of with Owner role | | `https://gitlab.org/claims/groups/maintainer` | `array` | Names of the groups the user is a direct member of with Maintainer role | | `https://gitlab.org/claims/groups/developer` | `array` | Names of the groups the user is a direct member of with Developer role | diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 678f967b88f..042f2f089e6 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -52,6 +52,22 @@ sole discretion of GitLab Inc. <div class="deprecation removal-160 breaking-change"> +### Conan project-level search endpoint returns project-specific results + +Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22) + +WARNING: +This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). +Review the details carefully before upgrading. + +You can use the GitLab Conan repository with [project-level](https://docs.gitlab.com/ee/user/packages/conan_repository/#add-a-remote-for-your-project) or [instance-level](https://docs.gitlab.com/ee/user/packages/conan_repository/#add-a-remote-for-your-instance) endpoints. Each level supports the conan search command. However, the search endpoint for the project level is also returning packages from outside the target project. + +This unintended functionality is deprecated in GitLab 15.8 and will be removed in GitLab 16.0. The search endpoint for the project level will only return packages from the target project. + +</div> + +<div class="deprecation removal-160 breaking-change"> + ### Container Registry pull-through cache Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22) diff --git a/doc/user/project/integrations/apple_app_store.md b/doc/user/project/integrations/apple_app_store.md new file mode 100644 index 00000000000..f381e71812c --- /dev/null +++ b/doc/user/project/integrations/apple_app_store.md @@ -0,0 +1,57 @@ +--- +stage: Verify +group: Integrations +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Apple App Store integration **(FREE)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104888) in GitLab 15.8. + +The Apple App Store integration makes it easy to configure your CI/CD pipelines to connect to [App Store Connect](https://appstoreconnect.apple.com) to build and release apps for iOS, iPadOS, macOS, tvOS, and watchOS. + +The integration is designed to be able to work out of the box with [fastlane](http://fastlane.tools/), but can be used with other build tools as well. + +## Prerequisites + +An Apple ID enrolled in the [Apple Developer Program](https://developer.apple.com/programs/enroll/) is required to enable this integration. + +## Configure GitLab + +GitLab supports enabling the Apple App Store integration at the group or project level. Complete these steps in GitLab: + +1. In the Apple App Store Connect portal, generate a new private key for your project by following [these instructions](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api). +1. On the top bar, select **Main menu > Projects** and find your project. +1. On the left sidebar, select **Settings > Integrations**. +1. Select **Apple App Store**. +1. Turn on the **Active** toggle under **Enable Integration**. +1. Provide the Apple App Store Connect configuration information: + - **Issuer ID**: The Apple App Store Connect Issuer ID can be found in the *Keys* section under *Users and Access* the Apple App Store Connect portal. + - **Key ID**: The Key ID of the new private key that was just generated. + - **Private Key**: The Private Key that was just generated. Note: you are only be able to download this key one time. + +1. Select **Save changes**. + +After the Apple App Store integration is activated: + +- The global variables `$APP_STORE_CONNECT_API_KEY_ISSUER_ID`, `$APP_STORE_CONNECT_API_KEY_KEY_ID`, and `$APP_STORE_CONNECT_API_KEY_KEY` are created for CI/CD use. +- `$APP_STORE_CONNECT_API_KEY_KEY` contains the Base64 encoded Private Key. +- The project-level integration settings override the group-level integration settings. + +## Security considerations + +### CI/CD variable security + +Malicious code pushed to your `.gitlab-ci.yml` file could compromise your variables, including +`$APP_STORE_CONNECT_API_KEY_KEY`, and send them to a third-party server. For more details, see +[CI/CD variable security](../../../ci/variables/index.md#cicd-variable-security). + +## fastlane Example + +Because this integration works out of the box with fastlane adding the code below to an app's `fastlane/Fastfile` activates the integration, and create the connection for any interactions with the Apple App Store uploading a Test Flight or public App Store release. + +```ruby +app_store_connect_api_key( + is_key_content_base64: true +) +``` diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 543449c0349..e2549c4bffb 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -161,6 +161,26 @@ module API def self.integrations { + 'apple-app-store' => [ + { + required: true, + name: :app_store_issuer_id, + type: String, + desc: 'The Apple App Store Connect Issuer ID' + }, + { + required: true, + name: :app_store_key_id, + type: String, + desc: 'The Apple App Store Connect Key ID' + }, + { + required: true, + name: :app_store_private_key, + type: String, + desc: 'The Apple App Store Connect Private Key' + } + ], 'asana' => [ { required: true, @@ -871,6 +891,7 @@ module API def self.integration_classes [ + ::Integrations::AppleAppStore, ::Integrations::Asana, ::Integrations::Assembla, ::Integrations::Bamboo, diff --git a/lib/gitlab/database/reindexing/grafana_notifier.rb b/lib/gitlab/database/reindexing/grafana_notifier.rb index ece9327b658..e43eddbefc0 100644 --- a/lib/gitlab/database/reindexing/grafana_notifier.rb +++ b/lib/gitlab/database/reindexing/grafana_notifier.rb @@ -60,7 +60,9 @@ module Gitlab "Authorization": "Bearer #{@api_key}" } - success = Gitlab::HTTP.post("#{@api_url}/api/annotations", body: payload.to_json, headers: headers, allow_local_requests: true).success? + success = Gitlab::HTTP.post( + "#{@api_url}/api/annotations", body: payload.to_json, headers: headers, allow_local_requests: true + ).success? log_error("Response code #{response.code}") unless success diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9c4b16ec0ef..f018f85a031 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3613,6 +3613,9 @@ msgstr "" msgid "After it expires, you can't use merge approvals, epics, or many security features." msgstr "" +msgid "After the Apple App Store Connect integration is activated, the following protected variables will be created for CI/CD use." +msgstr "" + msgid "After the export is complete, download the data file from a notification email or from this page. You can then import the data file from the %{strong_text_start}Create new group%{strong_text_end} page of another GitLab instance." msgstr "" @@ -4707,6 +4710,18 @@ msgstr "" msgid "Append the comment with %{tableflip}" msgstr "" +msgid "AppleAppStore|The Apple App Store Connect Issuer ID." +msgstr "" + +msgid "AppleAppStore|The Apple App Store Connect Key ID." +msgstr "" + +msgid "AppleAppStore|The Apple App Store Connect Private Key." +msgstr "" + +msgid "AppleAppStore|Use GitLab to build and release an app in the Apple App Store." +msgstr "" + msgid "Application" msgstr "" @@ -22418,6 +22433,9 @@ msgstr "" msgid "Integrations|Instance-level integration management" msgstr "" +msgid "Integrations|Integration details" +msgstr "" + msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgstr "" @@ -45029,6 +45047,9 @@ msgstr "" msgid "Use the %{strongStart}Test%{strongEnd} option above to create an event." msgstr "" +msgid "Use the Apple App Store Connect integration to easily connect to the Apple App Store with Fastlane in CI/CD pipelines." +msgstr "" + msgid "Use the link below to confirm your email address (%{email})" msgstr "" diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index ebbf1b560e5..7740b2da911 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -254,6 +254,16 @@ FactoryBot.define do password { 'harborpassword' } end + factory :apple_app_store_integration, class: 'Integrations::AppleAppStore' do + project + active { true } + type { 'Integrations::AppleAppStore' } + + app_store_issuer_id { 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' } + app_store_key_id { 'ABC1' } + app_store_private_key { File.read('spec/fixtures/ssl_key.pem') } + 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/ide/lib/gitlab_web_ide/get_base_config_spec.js b/spec/frontend/ide/lib/gitlab_web_ide/get_base_config_spec.js index 4b4e96f3b41..ed67a0948e4 100644 --- a/spec/frontend/ide/lib/gitlab_web_ide/get_base_config_spec.js +++ b/spec/frontend/ide/lib/gitlab_web_ide/get_base_config_spec.js @@ -3,20 +3,32 @@ import { TEST_HOST } from 'helpers/test_constants'; const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/gitlab-web-ide/public/path'; const TEST_GITLAB_URL = 'https://gdk.test/'; +const TEST_RELATIVE_URL_ROOT = '/gl_rel_root'; describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { - it('returns base properties for @gitlab/web-ide config', () => { + beforeEach(() => { // why: add trailing "/" to test that it gets removed process.env.GITLAB_WEB_IDE_PUBLIC_PATH = `${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}/`; window.gon.gitlab_url = TEST_GITLAB_URL; + window.gon.relative_url_root = ''; + }); - // act + it('with default, returns base properties for @gitlab/web-ide config', () => { const actual = getBaseConfig(); - // asset expect(actual).toEqual({ baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`, gitlabUrl: TEST_GITLAB_URL, }); }); + + it('with relative_url_root, returns baseUrl with relative url root', () => { + window.gon.relative_url_root = TEST_RELATIVE_URL_ROOT; + + const actual = getBaseConfig(); + + expect(actual).toMatchObject({ + baseUrl: `${TEST_HOST}${TEST_RELATIVE_URL_ROOT}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`, + }); + }); }); diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index dc444f90627..498625dc642 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Issue'] do + let_it_be_with_reload(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Issue) } specify { expect(described_class.graphql_name).to eq('Issue') } @@ -26,8 +29,6 @@ RSpec.describe GitlabSchema.types['Issue'] do end describe 'pagination and count' do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } let_it_be(:now) { Time.now.change(usec: 0) } let_it_be(:issues) { create_list(:issue, 10, project: project, created_at: now) } @@ -130,8 +131,6 @@ RSpec.describe GitlabSchema.types['Issue'] do end describe "issue notes" do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let(:confidential_issue) { create(:issue, :confidential, project: project) } let(:private_note_body) { "mentioned in issue #{confidential_issue.to_reference(project)}" } @@ -211,8 +210,6 @@ RSpec.describe GitlabSchema.types['Issue'] do describe 'hidden', :enable_admin_mode do let_it_be(:admin) { create(:user, :admin) } let_it_be(:banned_user) { create(:user, :banned) } - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } let_it_be(:hidden_issue) { create(:issue, project: project, author: banned_user) } let_it_be(:visible_issue) { create(:issue, project: project, author: user) } @@ -259,8 +256,6 @@ RSpec.describe GitlabSchema.types['Issue'] do end describe 'escalation_status' do - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } let_it_be(:issue, reload: true) { create(:issue, project: project) } let(:execute) { GitlabSchema.execute(query, context: { current_user: user }).as_json } @@ -294,4 +289,44 @@ RSpec.describe GitlabSchema.types['Issue'] do end end end + + describe 'type' do + let_it_be(:issue) { create(:issue, project: project) } + + let(:query) do + %( + query { + issue(id: "#{issue.to_gid}") { + type + } + } + ) + end + + subject(:execute) { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + context 'when the issue_type_uses_work_item_types_table feature flag is enabled' do + it 'gets the type field from the work_item_types table' do + expect_next_instance_of(::IssuePresenter) do |presented_issue| + expect(presented_issue).to receive_message_chain(:work_item_type, :base_type) + end + + execute + end + end + + context 'when the issue_type_uses_work_item_types_table feature flag is disabled' do + before do + stub_feature_flags(issue_type_uses_work_item_types_table: false) + end + + it 'does not get the type field from the work_item_types table' do + expect_next_instance_of(::IssuePresenter) do |presented_issue| + expect(presented_issue).not_to receive(:work_item_type) + end + + execute + end + end + end end diff --git a/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb index db4383a79d4..1c0f5a0c420 100644 --- a/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb +++ b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::AutovacuumActiveOnTable do +RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::AutovacuumActiveOnTable, + feature_category: :database do include Database::DatabaseHelpers let(:connection) { Gitlab::Database.database_base_models[:main].connection } @@ -17,7 +18,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators:: subject { described_class.new(context).evaluate } before do - swapout_view_for_table(:postgres_autovacuum_activity) + swapout_view_for_table(:postgres_autovacuum_activity, connection: connection) end let(:tables) { [table] } diff --git a/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb b/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb index c1ac8f0c9cd..f24c4559349 100644 --- a/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb +++ b/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::PostgresAutovacuumActivity, type: :model do +RSpec.describe Gitlab::Database::PostgresAutovacuumActivity, type: :model, feature_category: :database do include Database::DatabaseHelpers it { is_expected.to be_a Gitlab::Database::SharedModel } @@ -13,7 +13,7 @@ RSpec.describe Gitlab::Database::PostgresAutovacuumActivity, type: :model do let(:tables) { %w[foo test] } before do - swapout_view_for_table(:postgres_autovacuum_activity) + swapout_view_for_table(:postgres_autovacuum_activity, connection: ApplicationRecord.connection) # unrelated create(:postgres_autovacuum_activity, table: 'bar') diff --git a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb index bb91617714a..d6a9edc76eb 100644 --- a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb +++ b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb @@ -2,13 +2,15 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::Reindexing::Coordinator do +RSpec.describe Gitlab::Database::Reindexing::Coordinator, feature_category: :database do include Database::DatabaseHelpers include ExclusiveLeaseHelpers - let(:notifier) { instance_double(Gitlab::Database::Reindexing::GrafanaNotifier, notify_start: nil, notify_end: nil) } let(:index) { create(:postgres_index) } let(:connection) { index.connection } + let(:notifier) do + instance_double(Gitlab::Database::Reindexing::GrafanaNotifier, notify_start: nil, notify_end: nil) + end let!(:lease) { stub_exclusive_lease(lease_key, uuid, timeout: lease_timeout) } let(:lease_key) { "gitlab/database/reindexing/coordinator/#{Gitlab::Database::PRIMARY_DATABASE_NAME}" } @@ -19,14 +21,11 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do model = Gitlab::Database.database_base_models[Gitlab::Database::PRIMARY_DATABASE_NAME] Gitlab::Database::SharedModel.using_connection(model.connection) do + swapout_view_for_table(:postgres_indexes, connection: model.connection) example.run end end - before do - swapout_view_for_table(:postgres_indexes) - end - describe '#perform' do subject { described_class.new(index, notifier).perform } diff --git a/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb index 1bccdda3be1..e67c97cbf9c 100644 --- a/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb +++ b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do let(:action) { create(:reindex_action) } before do - swapout_view_for_table(:postgres_indexes) + swapout_view_for_table(:postgres_indexes, connection: ApplicationRecord.connection) end let(:headers) do @@ -25,7 +25,9 @@ RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do let(:response) { double('response', success?: true) } def expect_api_call(payload) - expect(Gitlab::HTTP).to receive(:post).with("#{api_url}/api/annotations", body: payload.to_json, headers: headers, allow_local_requests: true).and_return(response) + expect(Gitlab::HTTP).to receive(:post).with( + "#{api_url}/api/annotations", body: payload.to_json, headers: headers, allow_local_requests: true + ).and_return(response) end shared_examples_for 'interacting with Grafana annotations API' do @@ -109,7 +111,9 @@ RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do end context 'additional tag is provided' do - subject { described_class.new(api_key: api_key, api_url: api_url, additional_tag: additional_tag).notify_start(action) } + subject do + described_class.new(api_key: api_key, api_url: api_url, additional_tag: additional_tag).notify_start(action) + end let(:payload) do { @@ -163,7 +167,9 @@ RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do end context 'additional tag is provided' do - subject { described_class.new(api_key: api_key, api_url: api_url, additional_tag: additional_tag).notify_end(action) } + subject do + described_class.new(api_key: api_key, api_url: api_url, additional_tag: additional_tag).notify_end(action) + end let(:payload) do { diff --git a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb index 9f31716ab94..bb745970e38 100644 --- a/spec/lib/gitlab/database/reindexing/index_selection_spec.rb +++ b/spec/lib/gitlab/database/reindexing/index_selection_spec.rb @@ -2,14 +2,16 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::Reindexing::IndexSelection do +RSpec.describe Gitlab::Database::Reindexing::IndexSelection, feature_category: :database do include Database::DatabaseHelpers subject { described_class.new(Gitlab::Database::PostgresIndex.all).to_a } + let(:connection) { ApplicationRecord.connection } + before do - swapout_view_for_table(:postgres_index_bloat_estimates) - swapout_view_for_table(:postgres_indexes) + swapout_view_for_table(:postgres_index_bloat_estimates, connection: connection) + swapout_view_for_table(:postgres_indexes, connection: connection) create_list(:postgres_index, 10, ondisk_size_bytes: 10.gigabytes).each_with_index do |index, i| create(:postgres_index_bloat_estimate, index: index, bloat_size_bytes: 2.gigabyte * (i + 1)) @@ -17,7 +19,7 @@ RSpec.describe Gitlab::Database::Reindexing::IndexSelection do end def execute(sql) - ActiveRecord::Base.connection.execute(sql) + connection.execute(sql) end it 'orders by highest relative bloat first' do diff --git a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb index 1b409924acc..06b89e08737 100644 --- a/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb +++ b/spec/lib/gitlab/database/reindexing/reindex_action_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' -RSpec.describe Gitlab::Database::Reindexing::ReindexAction do +RSpec.describe Gitlab::Database::Reindexing::ReindexAction, feature_category: :database do include Database::DatabaseHelpers let(:index) { create(:postgres_index) } before_all do - swapout_view_for_table(:postgres_indexes) + swapout_view_for_table(:postgres_indexes, connection: ApplicationRecord.connection) end it { is_expected.to be_a Gitlab::Database::SharedModel } diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb index fa26aa59329..05e7420ab34 100644 --- a/spec/lib/gitlab/database/reindexing_spec.rb +++ b/spec/lib/gitlab/database/reindexing_spec.rb @@ -76,7 +76,7 @@ RSpec.describe Gitlab::Database::Reindexing, feature_category: :database do let(:limit) { 5 } before_all do - swapout_view_for_table(:postgres_indexes) + swapout_view_for_table(:postgres_indexes, connection: ApplicationRecord.connection) end before do @@ -147,7 +147,7 @@ RSpec.describe Gitlab::Database::Reindexing, feature_category: :database do subject { described_class.perform_from_queue(maximum_records: limit) } before_all do - swapout_view_for_table(:postgres_indexes) + swapout_view_for_table(:postgres_indexes, connection: ApplicationRecord.connection) end let(:limit) { 2 } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 6ff77ad75c5..74ff110e7bd 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -424,6 +424,7 @@ project: - alert_hooks_integrations - incident_hooks_integrations - vulnerability_hooks_integrations +- apple_app_store_integration - campfire_integration - confluence_integration - datadog_integration diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb index 13d046b0816..5be1787fe7b 100644 --- a/spec/lib/gitlab/utils/usage_data_spec.rb +++ b/spec/lib/gitlab/utils/usage_data_spec.rb @@ -374,7 +374,7 @@ RSpec.describe Gitlab::Utils::UsageData do context 'when query timeout' do subject do - with_statement_timeout(0.001) do + with_statement_timeout(0.001, connection: ApplicationRecord.connection) do relation = AlertManagement::HttpIntegration.select('pg_sleep(0.002)') described_class.histogram(relation, column, buckets: 1..100) end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 534875a9bba..1e179d7a139 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3546,6 +3546,52 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do end end + context 'for the apple_app_store integration' do + let_it_be(:apple_app_store_integration) { create(:apple_app_store_integration) } + + let(:apple_app_store_variables) do + [ + { key: 'APP_STORE_CONNECT_API_KEY_ISSUER_ID', value: apple_app_store_integration.app_store_issuer_id, masked: true, public: false }, + { key: 'APP_STORE_CONNECT_API_KEY_KEY', value: Base64.encode64(apple_app_store_integration.app_store_private_key), masked: true, public: false }, + { key: 'APP_STORE_CONNECT_API_KEY_KEY_ID', value: apple_app_store_integration.app_store_key_id, masked: true, public: false } + ] + end + + context 'when the apple_app_store exists' do + context 'when a build is protected' do + before do + allow(build.pipeline).to receive(:protected_ref?).and_return(true) + build.project.update!(apple_app_store_integration: apple_app_store_integration) + end + + it 'includes apple_app_store variables' do + is_expected.to include(*apple_app_store_variables) + end + end + + context 'when a build is not protected' do + before do + allow(build.pipeline).to receive(:protected_ref?).and_return(false) + build.project.update!(apple_app_store_integration: apple_app_store_integration) + end + + it 'does not include the apple_app_store variables' do + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil + end + end + end + + context 'when the apple_app_store integration does not exist' do + it 'does not include apple_app_store variables' do + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil + end + end + end + context 'when build has dependency which has dotenv variable' do let!(:prepare) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: [prepare.name] }) } diff --git a/spec/models/factories_spec.rb b/spec/models/factories_spec.rb index 4915c0bd870..981ca3dfa2c 100644 --- a/spec/models/factories_spec.rb +++ b/spec/models/factories_spec.rb @@ -188,7 +188,13 @@ RSpec.describe 'factories', :saas do before do factories_based_on_view.each do |factory| view = build(factory).class.table_name - swapout_view_for_table(view) + view_gitlab_schema = Gitlab::Database::GitlabSchema.table_schema(view) + Gitlab::Database.database_base_models.each_value.select do |base_model| + connection = base_model.connection + next unless Gitlab::Database.gitlab_schemas_for_connection(connection).include?(view_gitlab_schema) + + swapout_view_for_table(view, connection: connection) + end end end diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb new file mode 100644 index 00000000000..dde26e383c7 --- /dev/null +++ b/spec/models/integrations/apple_app_store_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do + describe 'Validations' do + context 'when active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of :app_store_issuer_id } + it { is_expected.to validate_presence_of :app_store_key_id } + it { is_expected.to validate_presence_of :app_store_private_key } + it { is_expected.to allow_value('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee').for(:app_store_issuer_id) } + it { is_expected.not_to allow_value('abcde').for(:app_store_issuer_id) } + it { is_expected.to allow_value(File.read('spec/fixtures/ssl_key.pem')).for(:app_store_private_key) } + it { is_expected.not_to allow_value("foo").for(:app_store_private_key) } + it { is_expected.to allow_value('ABCD1EF12G').for(:app_store_key_id) } + it { is_expected.not_to allow_value('ABC').for(:app_store_key_id) } + it { is_expected.not_to allow_value('abc1').for(:app_store_key_id) } + it { is_expected.not_to allow_value('-A0-').for(:app_store_key_id) } + end + end + + context 'when integration is enabled' do + let(:apple_app_store_integration) { build(:apple_app_store_integration) } + + describe '#fields' do + it 'returns custom fields' do + expect(apple_app_store_integration.fields.pluck(:name)).to eq(%w[app_store_issuer_id app_store_key_id + app_store_private_key]) + end + end + + describe '#test' do + it 'returns true for a successful request' do + allow(AppStoreConnect::Client).to receive_message_chain(:new, :apps).and_return({}) + expect(apple_app_store_integration.test[:success]).to be true + end + + it 'returns false for an invalid request' do + allow(AppStoreConnect::Client).to receive_message_chain(:new, +:apps).and_return({ errors: [title: "error title"] }) + expect(apple_app_store_integration.test[:success]).to be false + end + end + + describe '#help' do + it 'renders prompt information' do + expect(apple_app_store_integration.help).not_to be_empty + end + end + + describe '.to_param' do + it 'returns the name of the integration' do + expect(described_class.to_param).to eq('apple_app_store') + end + end + + describe '#ci_variables' do + let(:apple_app_store_integration) { build_stubbed(:apple_app_store_integration) } + + it 'returns vars when the integration is activated' do + ci_vars = [ + { + key: 'APP_STORE_CONNECT_API_KEY_ISSUER_ID', + value: apple_app_store_integration.app_store_issuer_id, + masked: true, + public: false + }, + { + key: 'APP_STORE_CONNECT_API_KEY_KEY', + value: Base64.encode64(apple_app_store_integration.app_store_private_key), + masked: true, + public: false + }, + { + key: 'APP_STORE_CONNECT_API_KEY_KEY_ID', + value: apple_app_store_integration.app_store_key_id, + masked: true, + public: false + } + ] + + expect(apple_app_store_integration.ci_variables).to match_array(ci_vars) + end + + it 'returns an empty array when the integration is disabled' do + apple_app_store_integration = build_stubbed(:apple_app_store_integration, active: false) + expect(apple_app_store_integration.ci_variables).to match_array([]) + end + end + end + + context 'when integration is disabled' do + let(:apple_app_store_integration) { build_stubbed(:apple_app_store_integration, active: false) } + + describe '#ci_variables' do + it 'returns an empty array' do + expect(apple_app_store_integration.ci_variables).to match_array([]) + end + end + end +end diff --git a/spec/models/integrations/every_integration_spec.rb b/spec/models/integrations/every_integration_spec.rb index 33e89b3dabc..8666ef512fc 100644 --- a/spec/models/integrations/every_integration_spec.rb +++ b/spec/models/integrations/every_integration_spec.rb @@ -11,9 +11,9 @@ RSpec.describe 'Every integration' do let(:integration) { integration_class.new } context 'secret fields', :aggregate_failures do - it "uses type: 'password' for all secret fields" do + it "uses type: 'password' for all secret fields, except when bypassed" do integration.fields.each do |field| - next unless Integrations::Field::SECRET_NAME.match?(field[:name]) + next unless Integrations::Field::SECRET_NAME.match?(field[:name]) && field[:is_secret] expect(field[:type]).to eq('password'), "Field '#{field[:name]}' should use type 'password'" diff --git a/spec/models/integrations/field_spec.rb b/spec/models/integrations/field_spec.rb index 642fb1fbf7f..c30f9ef0d7b 100644 --- a/spec/models/integrations/field_spec.rb +++ b/spec/models/integrations/field_spec.rb @@ -83,6 +83,8 @@ RSpec.describe ::Integrations::Field do be false when :type eq 'text' + when :is_secret + eq true else be_nil end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ea458557f38..e3e8257af72 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -49,6 +49,7 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_one(:webex_teams_integration) } it { is_expected.to have_one(:packagist_integration) } it { is_expected.to have_one(:pushover_integration) } + it { is_expected.to have_one(:apple_app_store_integration) } it { is_expected.to have_one(:asana_integration) } it { is_expected.to have_many(:boards) } it { is_expected.to have_one(:campfire_integration) } diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index b45f4f1e39f..49279024bd0 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -32,6 +32,7 @@ RSpec.describe 'OpenID Connect requests', feature_category: :authentication_and_ { 'name' => 'Alice', 'nickname' => 'alice', + 'preferred_username' => 'alice', 'email' => 'public@example.com', 'email_verified' => true, 'website' => 'https://example.com', diff --git a/spec/support/caching.rb b/spec/support/caching.rb index 11e4f534971..b18223523db 100644 --- a/spec/support/caching.rb +++ b/spec/support/caching.rb @@ -37,8 +37,8 @@ RSpec.configure do |config| end config.around(:each, :use_sql_query_cache) do |example| - ActiveRecord::Base.cache do - example.run - end + base_models = Gitlab::Database.database_base_models_with_gitlab_shared.values + inner_proc = proc { example.run } + base_models.inject(inner_proc) { |proc, base_model| proc { base_model.cache { proc.call } } }.call end end diff --git a/spec/support/helpers/database/database_helpers.rb b/spec/support/helpers/database/database_helpers.rb index f3b2a2a6147..ecc42041e93 100644 --- a/spec/support/helpers/database/database_helpers.rb +++ b/spec/support/helpers/database/database_helpers.rb @@ -4,9 +4,7 @@ module Database module DatabaseHelpers # In order to directly work with views using factories, # we can swapout the view for a table of identical structure. - def swapout_view_for_table(view, connection: nil) - connection ||= ActiveRecord::Base.connection - + def swapout_view_for_table(view, connection:) connection.execute(<<~SQL.squish) CREATE TABLE #{view}_copy (LIKE #{view}); DROP VIEW #{view}; @@ -28,21 +26,20 @@ module Database # with_statement_timeout(0.1) do # model.select('pg_sleep(0.11)') # end - def with_statement_timeout(timeout) + def with_statement_timeout(timeout, connection:) # Force a positive value and a minimum of 1ms for very small values. timeout = (timeout * 1000).abs.ceil raise ArgumentError, 'Using a timeout of `0` means to disable statement timeout.' if timeout == 0 - previous_timeout = ActiveRecord::Base.connection - .exec_query('SHOW statement_timeout')[0].fetch('statement_timeout') + previous_timeout = connection.select_value('SHOW statement_timeout') - set_statement_timeout("#{timeout}ms") + connection.execute(format(%(SET LOCAL statement_timeout = '%s'), timeout)) yield ensure begin - set_statement_timeout(previous_timeout) + connection.execute(format(%(SET LOCAL statement_timeout = '%s'), previous_timeout)) rescue ActiveRecord::StatementInvalid # After a transaction was canceled/aborted due to e.g. a statement # timeout commands are ignored and will raise in PG::InFailedSqlTransaction. @@ -50,22 +47,5 @@ module Database # for the currrent transaction which will be closed anyway. end end - - # Set statement timeout for the current transaction. - # - # Note, that it does not restore the previous statement timeout. - # Use `with_statement_timeout` instead. - # - # @param timeout - Statement timeout in seconds - # - # Example: - # - # set_statement_timeout(0.1) - # model.select('pg_sleep(0.11)') - def set_statement_timeout(timeout) - ActiveRecord::Base.connection.execute( - format(%(SET LOCAL statement_timeout = '%s'), timeout) - ) - end end end diff --git a/spec/support/helpers/database/table_schema_helpers.rb b/spec/support/helpers/database/table_schema_helpers.rb index 472eaa45b4b..815c37e00e5 100644 --- a/spec/support/helpers/database/table_schema_helpers.rb +++ b/spec/support/helpers/database/table_schema_helpers.rb @@ -3,7 +3,9 @@ module Database module TableSchemaHelpers def connection - ActiveRecord::Base.connection + # We use ActiveRecord::Base.connection here because this is mainly used for database migrations + # where we override the connection on ActiveRecord::Base.connection + ActiveRecord::Base.connection # rubocop:disable Database/MultipleDatabases end def expect_table_to_be_replaced(original_table:, replacement_table:, archived_table:) diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index e1d28a807e3..6fc5904fc83 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -104,7 +104,7 @@ module MigrationsHelpers # We stub this way because we can't stub on # `current_application_settings` due to `method_missing` is # depending on current_application_settings... - allow(ActiveRecord::Base.connection) + allow(Gitlab::Database::Migration::V1_0::MigrationRecord.connection) .to receive(:active?) .and_return(false) allow(Gitlab::Runtime) @@ -158,10 +158,10 @@ module MigrationsHelpers end def migrate! - open_transactions = ActiveRecord::Base.connection.open_transactions + open_transactions = Gitlab::Database::Migration::V1_0::MigrationRecord.connection.open_transactions allow_next_instance_of(described_class) do |migration| allow(migration).to receive(:transaction_open?) do - ActiveRecord::Base.connection.open_transactions > open_transactions + Gitlab::Database::Migration::V1_0::MigrationRecord.connection.open_transactions > open_transactions end end diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index dd124ed9c7f..5be9ba9ae1e 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -19,9 +19,7 @@ module ActiveRecord def record(&block) # force replacement of bind parameters to give tests the ability to check for ids - ActiveRecord::Base.connection.unprepared_statement do - ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) - end + ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) end def show_backtrace(values, duration) diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 78ceaf297a8..438f0d129b9 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -116,8 +116,9 @@ module UsageDataHelpers ).freeze def stub_usage_data_connections - allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) - allow(::Ci::ApplicationRecord.connection).to receive(:transaction_open?).and_return(false) if ::Ci::ApplicationRecord.connection_class? + Gitlab::Database.database_base_models_with_gitlab_shared.each_value do |base_model| + allow(base_model.connection).to receive(:transaction_open?).and_return(false) + end allow(Gitlab::Prometheus::Internal).to receive(:prometheus_enabled?).and_return(false) end diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb index ca2fe8a6c54..005046166d8 100644 --- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -Integration.available_integration_names.each do |integration| +Integration.available_integration_names(include_feature_flagged: true).each do |integration| RSpec.shared_context integration do include JiraIntegrationHelpers if integration == 'jira' @@ -40,7 +40,7 @@ Integration.available_integration_names.each do |integration| let(:integration_attrs) do integration_attrs_list.inject({}) do |hash, k| - if k =~ /^(token*|.*_token|.*_key)/ + if k =~ /^(token*|.*_token|.*_key)/ && k =~ /^[^app_store]/ hash.merge!(k => 'secrettoken') elsif integration == 'confluence' && k == :confluence_url hash.merge!(k => 'https://example.atlassian.net/wiki') @@ -68,6 +68,12 @@ Integration.available_integration_names.each do |integration| hash.merge!(k => "match_any") elsif integration == 'campfire' && k == :room hash.merge!(k => '1234') + elsif integration == 'apple_app_store' && k == :app_store_issuer_id + hash.merge!(k => 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') + elsif integration == 'apple_app_store' && k == :app_store_private_key + hash.merge!(k => File.read('spec/fixtures/ssl_key.pem')) + elsif integration == 'apple_app_store' && k == :app_store_key_id + hash.merge!(k => 'ABC1') else hash.merge!(k => "someword") end diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index 22abfc33d1b..7671c65d22c 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do describe 'mark_migration_complete' do context 'with a single database' do - let(:main_model) { ActiveRecord::Base } + let(:main_model) { ApplicationRecord } before do skip_if_multiple_databases_are_setup |