diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b9d0aed82e8..68a01828592 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -a88ceb12912277ab1e07ca325792df40550e6bcc +6f0bfe7c326aeabcb5c3bc35bd3994160d20d6c3 diff --git a/app/assets/javascripts/admin/users/new.js b/app/assets/javascripts/admin/users/new.js new file mode 100644 index 00000000000..33565bfc14f --- /dev/null +++ b/app/assets/javascripts/admin/users/new.js @@ -0,0 +1,55 @@ +const DATA_ATTR_REGEX_PATTERN = 'data-user-internal-regex-pattern'; +const DATA_ATTR_REGEX_OPTIONS = 'data-user-internal-regex-options'; +export const ID_USER_EXTERNAL = 'user_external'; +export const ID_WARNING = 'warning_external_automatically_set'; +export const ID_USER_EMAIL = 'user_email'; + +const getAttributeValue = (attr) => document.querySelector(`[${attr}]`)?.getAttribute(attr); + +const getRegexPattern = () => getAttributeValue(DATA_ATTR_REGEX_PATTERN); + +const getRegexOptions = () => getAttributeValue(DATA_ATTR_REGEX_OPTIONS); + +export const setupInternalUserRegexHandler = () => { + const regexPattern = getRegexPattern(); + + if (!regexPattern) { + return; + } + + const regexOptions = getRegexOptions(); + const elExternal = document.getElementById(ID_USER_EXTERNAL); + const elWarningMessage = document.getElementById(ID_WARNING); + const elUserEmail = document.getElementById(ID_USER_EMAIL); + + const isEmailInternal = (email) => { + const regex = new RegExp(regexPattern, regexOptions); + return regex.test(email); + }; + + const setExternalCheckbox = (email) => { + const isChecked = elExternal.checked; + + if (isEmailInternal(email)) { + if (isChecked) { + elExternal.checked = false; + elWarningMessage.classList.remove('hidden'); + } + } else if (!isChecked) { + elExternal.checked = true; + elWarningMessage.classList.add('hidden'); + } + }; + + const setupListeners = () => { + elUserEmail.addEventListener('input', (event) => { + setExternalCheckbox(event.target.value); + }); + + elExternal.addEventListener('change', () => { + elWarningMessage.classList.add('hidden'); + }); + }; + + setupListeners(); +}; diff --git a/app/assets/javascripts/pages/admin/users/new/index.js b/app/assets/javascripts/pages/admin/users/new/index.js index 01710246c86..34c10e44f4c 100644 --- a/app/assets/javascripts/pages/admin/users/new/index.js +++ b/app/assets/javascripts/pages/admin/users/new/index.js @@ -1,49 +1,3 @@ -import $ from 'jquery'; +import { setupInternalUserRegexHandler } from '~/admin/users/new'; -export default class UserInternalRegexHandler { - constructor() { - this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern'); - if (this.regexPattern && this.regexPattern !== '') { - this.regexOptions = $('[data-user-internal-regex-options]').data( - 'user-internal-regex-options', - ); - this.external = $('#user_external'); - this.warningMessage = $('#warning_external_automatically_set'); - this.addListenerToEmailField(); - this.addListenerToUserExternalCheckbox(); - } - } - - addListenerToEmailField() { - $('#user_email').on('input', (event) => { - this.setExternalCheckbox(event.currentTarget.value); - }); - } - - addListenerToUserExternalCheckbox() { - this.external.on('click', () => { - this.warningMessage.addClass('hidden'); - }); - } - - isEmailInternal(email) { - const regex = new RegExp(this.regexPattern, this.regexOptions); - return regex.test(email); - } - - setExternalCheckbox(email) { - const isChecked = this.external.prop('checked'); - if (this.isEmailInternal(email)) { - if (isChecked) { - this.external.prop('checked', false); - this.warningMessage.removeClass('hidden'); - } - } else if (!isChecked) { - this.external.prop('checked', true); - this.warningMessage.addClass('hidden'); - } - } -} - -// eslint-disable-next-line no-new -new UserInternalRegexHandler(); +setupInternalUserRegexHandler(); diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 3354c0a5c9f..064857ff31c 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -10,6 +10,7 @@ class Groups::BoardsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false) push_frontend_feature_flag(:boards_filtered_search, group) + push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml) end feature_category :boards diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 418f2ee1592..69a609b6fd8 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController before_action :assign_endpoint_vars before_action do push_frontend_feature_flag(:add_issues_button) + push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml) end feature_category :boards diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index ec0f8b54789..6fa1aa943cc 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -9,6 +9,7 @@ module Mutations ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' field_class ::Types::BaseField + argument_class ::Types::BaseArgument field :errors, [GraphQL::STRING_TYPE], null: false, diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb index 4ad9e8c0e40..ff9a5a0611d 100644 --- a/app/graphql/types/base_argument.rb +++ b/app/graphql/types/base_argument.rb @@ -4,8 +4,10 @@ module Types class BaseArgument < GraphQL::Schema::Argument include GitlabStyleDeprecations + attr_reader :deprecation + def initialize(*args, **kwargs, &block) - kwargs = gitlab_deprecation(kwargs) + @deprecation = gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb index 527269846ff..a1adc70d64f 100644 --- a/app/graphql/types/base_enum.rb +++ b/app/graphql/types/base_enum.rb @@ -26,7 +26,7 @@ module Types def value(*args, **kwargs, &block) enum[args[0].downcase] = kwargs[:value] || args[0] - kwargs = gitlab_deprecation(kwargs) + gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 99f32894925..7c939f94dde 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -8,6 +8,8 @@ module Types DEFAULT_COMPLEXITY = 1 + attr_reader :deprecation + def initialize(**kwargs, &block) @calls_gitaly = !!kwargs.delete(:calls_gitaly) @constant_complexity = kwargs[:complexity].is_a?(Integer) && kwargs[:complexity] > 0 @@ -16,7 +18,7 @@ module Types kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity]) @feature_flag = kwargs[:feature_flag] kwargs = check_feature_flag(kwargs) - kwargs = gitlab_deprecation(kwargs) + @deprecation = gitlab_deprecation(kwargs) super(**kwargs, &block) diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb index ad195354930..802562ed958 100644 --- a/app/graphql/types/concerns/gitlab_style_deprecations.rb +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -7,25 +7,21 @@ module GitlabStyleDeprecations private + # Mutate the arguments, returns the deprecation def gitlab_deprecation(kwargs) if kwargs[:deprecation_reason].present? raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \ 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values' end - deprecation = kwargs.delete(:deprecated) - return kwargs unless deprecation + deprecation = ::Gitlab::Graphql::Deprecation.parse(kwargs.delete(:deprecated)) + return unless deprecation - milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence) + raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" unless deprecation.valid? - raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone - raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason - raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String) + kwargs[:deprecation_reason] = deprecation.deprecation_reason + kwargs[:description] = deprecation.edit_description(kwargs[:description]) - deprecated_in = "Deprecated in #{milestone}" - kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}." - kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description] - - kwargs + deprecation end end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 993d1123c86..9ed9f3c9285 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -29,6 +29,7 @@ class Packages::Package < ApplicationRecord delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan delegate :codename, :suite, to: :debian_distribution, prefix: :debian_distribution + delegate :target_sha, to: :composer_metadatum, prefix: :composer validates :project, presence: true validates :name, presence: true diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml index 728f035555e..9e9fc08dac0 100644 --- a/app/views/projects/default_branch/_show.html.haml +++ b/app/views/projects/default_branch/_show.html.haml @@ -28,4 +28,4 @@ = _("When merge requests and commits in the default branch close, any issues they reference also close.") = link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank' - = f.submit _('Save changes'), class: "gl-button btn btn-success" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/changelogs/unreleased/247531-composer-source-jsonn.yml b/changelogs/unreleased/247531-composer-source-jsonn.yml new file mode 100644 index 00000000000..ca338ca231c --- /dev/null +++ b/changelogs/unreleased/247531-composer-source-jsonn.yml @@ -0,0 +1,5 @@ +--- +title: Support for --prefer-source option for Composer registry +merge_request: 56693 +author: +type: changed diff --git a/changelogs/unreleased/ar-approval-settings-header.yml b/changelogs/unreleased/ar-approval-settings-header.yml new file mode 100644 index 00000000000..0a10bcaa06a --- /dev/null +++ b/changelogs/unreleased/ar-approval-settings-header.yml @@ -0,0 +1,5 @@ +--- +title: Updated MR Approvals to specify settings section +merge_request: 54985 +author: +type: other diff --git a/changelogs/unreleased/btn-confirm-project-default-branch.yml b/changelogs/unreleased/btn-confirm-project-default-branch.yml new file mode 100644 index 00000000000..659674079f8 --- /dev/null +++ b/changelogs/unreleased/btn-confirm-project-default-branch.yml @@ -0,0 +1,5 @@ +--- +title: Move from btn-success to btn-confirm in default_branch directory +merge_request: 56330 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/ps-refactor-user-internal-regex-handler.yml b/changelogs/unreleased/ps-refactor-user-internal-regex-handler.yml new file mode 100644 index 00000000000..156861328e1 --- /dev/null +++ b/changelogs/unreleased/ps-refactor-user-internal-regex-handler.yml @@ -0,0 +1,5 @@ +--- +title: In admin new user page, fix external checkbox warning hide with keyboard interaction +merge_request: 56896 +author: +type: fixed diff --git a/config/feature_flags/development/swimlanes_buffered_rendering.yml b/config/feature_flags/development/swimlanes_buffered_rendering.yml new file mode 100644 index 00000000000..30da5383406 --- /dev/null +++ b/config/feature_flags/development/swimlanes_buffered_rendering.yml @@ -0,0 +1,8 @@ +--- +name: swimlanes_buffered_rendering +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56614 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324994 +milestone: '13.11' +type: development +group: group::product planning +default_enabled: false \ No newline at end of file diff --git a/doc/administration/geo/setup/database.md b/doc/administration/geo/setup/database.md index 7128d4283d1..9c2cc8fc62e 100644 --- a/doc/administration/geo/setup/database.md +++ b/doc/administration/geo/setup/database.md @@ -672,7 +672,7 @@ For each Patroni instance on the secondary site: ## Migrating from repmgr to Patroni 1. Before migrating, it is recommended that there is no replication lag between the primary and secondary sites and that replication is paused. In GitLab 13.2 and later, you can pause and resume replication with `gitlab-ctl geo-replication-pause` and `gitlab-ctl geo-replication-resume` on a Geo secondary database node. -1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replicaton_slots'] = { '' => 'physical' }` +1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replication_slots'] = { '' => 'physical' }` to `gitlab.rb` where `` is the name of the replication slot for your Geo secondary. This will ensure that Patroni recognizes the replication slot as permanent and will not drop it upon restarting. 1. If database replication to the secondary was paused before migration, resume replication once Patroni is confirmed working on the primary. diff --git a/doc/administration/terraform_state.md b/doc/administration/terraform_state.md index 0f3fdf4bb93..0c01279b04c 100644 --- a/doc/administration/terraform_state.md +++ b/doc/administration/terraform_state.md @@ -97,6 +97,39 @@ The following settings are: | `remote_directory` | The bucket name where Terraform state files are stored | | | `connection` | Various connection options described below | | +### Migrate to object storage + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247042) in GitLab 13.9. + +To migrate Terraform state files to object storage, follow the instructions below. + +- For Omnibus package installations: + + ```shell + gitlab-rake gitlab:terraform_states:migrate + ``` + +- For source installations: + + ```shell + sudo -u git -H bundle exec rake gitlab:terraform_states:migrate RAILS_ENV=production + ``` + +For GitLab 13.8 and earlier versions, you can use a workaround for the Rake task: + +1. Open the GitLab [Rails console](operations/rails_console.md). +1. Run the following commands: + + ```ruby + Terraform::StateUploader.alias_method(:upload, :model) + + Terraform::StateVersion.where(file_store: ::ObjectStorage::Store::LOCAL). find_each(batch_size: 10) do |terraform_state_version| + puts "Migrating: #{terraform_state_version.inspect}" + + terraform_state_version.file.migrate!(::ObjectStorage::Store::REMOTE) + end + ``` + ### S3-compatible connection settings See [the available connection settings for different providers](object_storage.md#connection-settings). diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md index 606697b5247..11425d464b9 100644 --- a/doc/administration/troubleshooting/elasticsearch.md +++ b/doc/administration/troubleshooting/elasticsearch.md @@ -203,7 +203,7 @@ To do this: ```rails u = User.find_by_email('email_of_user_doing_search') s = SearchService.new(u, {:search => 'search_term'}) - pp s.search_objects.class.name + pp s.search_objects.class ``` The output from the last command is the key here. If it shows: @@ -217,7 +217,9 @@ The output from the last command is the key here. If it shows: If all the settings look correct and it is still not using Elasticsearch for the search function, it is best to escalate to GitLab support. This could be a bug/issue. -Moving past that, it is best to attempt the same search using the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) and compare the results from what you see in GitLab. +Moving past that, it is best to attempt the same [search via the Rails console](../../integration/elasticsearch.md#i-indexed-all-the-repositories-but-i-cant-get-any-hits-for-my-search-term-in-the-ui) +or the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html), +and compare the results from what you see in GitLab. If the results: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index e10b7d41f28..5c70671456c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -3210,7 +3210,8 @@ test: ``` With this configuration, GitLab adds a link **artifact 1** to the relevant merge request -that points to `file1.txt`. +that points to `file1.txt`. To access the link, select **View exposed artifact** +below the pipeline graph in the merge request overview. An example that matches an entire directory: diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index 9828a619c49..18de8948c7d 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -91,6 +91,14 @@ Since Elasticsearch can read and use indices created in the previous major versi The only thing worth noting is that if you have created your current index before GitLab 13.0, you might want to reindex from scratch (which will implicitly create an alias) in order to use some features, for example [Zero downtime reindexing](#zero-downtime-reindexing). Once you do that, you'll be able to perform zero-downtime reindexing and will benefit from any future features that make use of the alias. +If you are unsure when your current index was created, +you can check whether it was created after GitLab 13.0 by using the +[Elasticsearch cat aliases API](https://www.elastic.co/guide/en/elasticsearch/reference/7.11/cat-alias.html). +If the list of aliases returned contains an entry for `gitlab-production` that points to an index +named `gitlab-production-`, your index was created after GitLab 13.0. +If the `gitlab-production` alias is missing, you'll need to reindex from scratch to use +features such as Zero-downtime reindexing. + ## Elasticsearch repository indexer For indexing Git repository data, GitLab uses an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). @@ -936,3 +944,10 @@ sudo gitlab-rake gitlab:elastic:index cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:elastic:index ``` + +### How does Advanced Search handle private projects? + +Advanced Search will store all the projects in the same Elasticsearch indexes, +however searches will only surface results that can be viewed by the user. +Advanced Search will honor all permission checks in the application by +filtering out projects that a user does not have access to at search time. diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md index fa0f32a8fb0..7e052ecea0d 100644 --- a/doc/user/clusters/agent/index.md +++ b/doc/user/clusters/agent/index.md @@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # GitLab Kubernetes Agent **(PREMIUM SELF)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4. -> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834). +> - [In GitLab 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300960), KAS became available on GitLab.com under `wss://kas.gitlab.com` through an Early Adopter Program. WARNING: This feature might not be available to you. Check the **version history** note above for details. @@ -85,7 +85,7 @@ component, `agentk`. Upgrade your agent installations together with GitLab upgrades. To decide which version of `agentk`to install follow: -1. Open the [GITLAB_KAS_VERSION](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch. +1. Open the [`GITLAB_KAS_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch. 1. Change the `master` branch and select the Git tag associated with your version. For instance, you could change it to GitLab [v13.5.3-ee release](https://gitlab.com/gitlab-org/gitlab/-/blob/v13.5.3-ee/GITLAB_KAS_VERSION) The available `agentk` and `kas` versions can be found in @@ -96,20 +96,21 @@ The available `agentk` and `kas` versions can be found in [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10, the GitLab Kubernetes Agent Server (KAS) is available on GitLab.com under `wss://kas.gitlab.com`. If you are a GitLab.com user, skip this step and directly -[set up the configuration repository](#define-a-configuration-repository) +[set up the configuration repository](#define-a-configuration-repository) for your agent. -The GitLab Kubernetes Agent Server (KAS) can be deployed using [Omnibus -GitLab](https://docs.gitlab.com/omnibus/) or the [GitLab -chart](https://gitlab.com/gitlab-org/charts/gitlab). If you don't already have +The GitLab Kubernetes Agent Server (KAS) can be installed through Omnibus GitLab or +through the GitLab Helm Chart. If you don't already have GitLab installed, please refer to our [installation documentation](https://docs.gitlab.com/ee/install/README.html). +You can install the KAS within GitLab as explained below according to your GitLab installation method. +You can also opt to use an [external KAS](#use-an-external-kas-installation). -#### Install with Omnibus +#### Install KAS with Omnibus -When using the [Omnibus GitLab](https://docs.gitlab.com/omnibus/) package: +For [Omnibus](https://docs.gitlab.com/omnibus/) package installations: -1. Edit `/etc/gitlab/gitlab.rb`: +1. Edit `/etc/gitlab/gitlab.rb` to enable the Kubernetes Agent Server: ```plaintext gitlab_kas['enable'] = true @@ -121,9 +122,9 @@ To configure any additional options related to GitLab Kubernetes Agent Server, refer to the **Enable GitLab KAS** section of the [`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template). -#### Install with the Helm chart +#### Install KAS with GitLab Helm Chart -When installing or upgrading the GitLab Helm chart, consider the following Helm v3 example. +For GitLab [Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) installations, consider the following Helm v3 example. If you're using Helm v2, you must modify this example. See our [notes regarding deploy with Helm](https://docs.gitlab.com/charts/installation/deployment.html#deploy-using-helm). You must set `global.kas.enabled=true` for the KAS to be properly installed and configured: @@ -150,6 +151,29 @@ gitlab: For details, read [Using the GitLab-KAS chart](https://docs.gitlab.com/charts/charts/gitlab/kas/). +#### Use an external KAS installation + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299850) in GitLab 13.10. + +Besides installing KAS with GitLab, you can opt to configure GitLab to use an external KAS. + +For GitLab instances installed through the GitLab Helm Chart, see [how to configure your external KAS](https://docs.gitlab.com/charts/charts/globals.html#external-kas). + +For GitLab instances installed through Omnibus packages: + +1. Edit `/etc/gitlab/gitlab.rb` adding the paths to your external KAS: + + ```ruby + gitlab_kas['enable'] = false + gitlab_kas['api_secret_key'] = 'Your shared secret between GitLab and KAS' + + gitlab_rails['gitlab_kas_enabled'] = true + gitlab_rails['gitlab_kas_external_url'] = 'wss://kas.gitlab.example.com' # User-facing URL for the in-cluster agentk + gitlab_rails['gitlab_kas_internal_url'] = 'grpc://kas.internal.gitlab.example.com' # Internal URL for the GitLab backend + ``` + +1. [Reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure). + ### Define a configuration repository Next, you need a GitLab repository to contain your Agent configuration. The minimal diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index c61e49993e6..76a35454f71 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -121,7 +121,7 @@ NOTE: ## IP range -GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API +GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come from those IPs and allow them. diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md index 96b5a8513c5..26f41fe23e8 100644 --- a/doc/user/packages/composer_repository/index.md +++ b/doc/user/packages/composer_repository/index.md @@ -274,8 +274,21 @@ To install a package: composer update ``` + Or to install the single package: + + ```shell + composer req : + ``` + If successful, you should see output indicating that the package installed successfully. + You can also install from source (by pulling the Git repository directly) using the + `--prefer-source` option: + + ```shell + composer update --prefer-source + ``` + WARNING: Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 8606ce36a82..842f167f6ec 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: howto --- -# Deploy Tokens +# Deploy tokens > - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17894) in GitLab 10.7. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199370) from **Settings > Repository** in GitLab 12.9. @@ -23,15 +23,15 @@ Deploy tokens cannot be used with the GitLab API. If you have a key pair, you might want to use [deploy keys](../../project/deploy_keys/index.md) instead. -## Creating a Deploy Token +## Creating a Deploy token You can create as many deploy tokens as you need from the settings of your project. Alternatively, you can also create [group-scoped deploy tokens](#group-deploy-token). 1. Sign in to your GitLab account. -1. Go to the project (or group) you want to create Deploy Tokens for. +1. Go to the project (or group) you want to create deploy tokens for. 1. Go to **Settings > Repository**. -1. Click on "Expand" on **Deploy Tokens** section. +1. Expand the **Deploy tokens** section. 1. Choose a name, expiry date (optional), and username (optional) for the token. 1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token). 1. Select **Create deploy token**. @@ -46,8 +46,8 @@ Deploy tokens expire at midnight UTC on the date you define. ## Revoking a deploy token -At any time, you can revoke any deploy token by just clicking the respective -**Revoke** button under the 'Active deploy tokens' area. +To revoke a deploy token, under the **Active deploy tokens** area, +select the respective **Revoke** button. ## Limiting scopes of a deploy token @@ -75,11 +75,11 @@ username to be used when creating the deploy token. ### Git clone a repository -To download a repository using a Deploy Token, you just need to: +To download a repository using a deploy token: -1. Create a Deploy Token with `read_repository` as a scope. +1. Create a deploy token with `read_repository` as a scope. 1. Take note of your `username` and `token`. -1. `git clone` the project using the Deploy Token: +1. `git clone` the project using the deploy token: ```shell git clone https://:@gitlab.example.com/tanuki/awesome_project.git @@ -91,7 +91,7 @@ Replace `` and `` with the proper values. To read the container registry images, you must: -1. Create a Deploy Token with `read_registry` as a scope. +1. Create a deploy token with `read_registry` as a scope. 1. Take note of your `username` and `token`. 1. Sign in to the GitLab Container Registry using the deploy token: @@ -108,7 +108,7 @@ pull images from your Container Registry. To push the container registry images, you must: -1. Create a Deploy Token with `write_registry` as a scope. +1. Create a deploy token with `write_registry` as a scope. 1. Take note of your `username` and `token`. 1. Sign in to the GitLab Container Registry using the deploy token: @@ -125,7 +125,7 @@ push images to your Container Registry. To pull packages in the GitLab package registry, you must: -1. Create a Deploy Token with `read_package_registry` as a scope. +1. Create a deploy token with `read_package_registry` as a scope. 1. Take note of your `username` and `token`. 1. For the [package type of your choice](../../packages/index.md), follow the authentication instructions for deploy tokens. @@ -152,12 +152,12 @@ Example response: To upload packages in the GitLab package registry, you must: -1. Create a Deploy Token with `write_package_registry` as a scope. +1. Create a deploy token with `write_package_registry` as a scope. 1. Take note of your `username` and `token`. 1. For the [package type of your choice](../../packages/index.md), follow the authentication instructions for deploy tokens. -### Group Deploy Token +### Group deploy token > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21765) in GitLab 12.9. @@ -167,7 +167,7 @@ belong either to the specific group or to one of its subgroups. For an overview, see [Group Deploy Tokens](https://youtu.be/8kxTJvaD9ks). -The Group Deploy Tokens UI is now accessible under **Settings > Repository**, +The Group deploy tokens UI is now accessible under **Settings > Repository**, not **Settings > CI/CD** as indicated in the video. To use a group deploy token: @@ -179,12 +179,12 @@ To use a group deploy token: The scopes applied to a group deploy token (such as `read_repository`) apply consistently when cloning the repository of related projects. -### GitLab Deploy Token +### GitLab deploy token > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18414) in GitLab 10.8. -There's a special case when it comes to Deploy Tokens. If a user creates one -named `gitlab-deploy-token`, the username and token of the Deploy Token is +There's a special case when it comes to deploy tokens. If a user creates one +named `gitlab-deploy-token`, the username and token of the deploy token is automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD`, respectively. diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md index 09be846ba5c..00f209bff78 100644 --- a/doc/user/project/merge_requests/merge_request_approvals.md +++ b/doc/user/project/merge_requests/merge_request_approvals.md @@ -53,7 +53,7 @@ be merged, and optionally which users should do the approving. Approvals can be If no approval rules are defined, any user can approve a merge request. However, the default minimum number of required approvers can still be set in the -[project settings for merge request approvals](#merge-request-approvals-project-settings). +[settings for merge request approvals](#approval-settings). You can opt to define one single rule to approve a merge request among the available rules or choose more than one with [multiple approval rules](#multiple-approval-rules). @@ -278,9 +278,9 @@ else blocking it. Note that the merge request could still be blocked by other co such as merge conflicts, [pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved), or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md). -### Merge request approvals project settings +### Approval settings -The project settings for Merge request approvals are found by going to +The settings for Merge request approvals are found by going to **Settings > General** and expanding **Merge request approvals**. #### Prevent overriding default approvals diff --git a/lib/gitlab/composer/version_index.rb b/lib/gitlab/composer/version_index.rb index ac0071cdc53..fdff8fb32d3 100644 --- a/lib/gitlab/composer/version_index.rb +++ b/lib/gitlab/composer/version_index.rb @@ -28,20 +28,34 @@ module Gitlab def package_metadata(package) json = package.composer_metadatum.composer_json - json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version) + json.merge( + 'dist' => package_dist(package), + 'source' => package_source(package), + 'uid' => package.id, + 'version' => package.version + ) end def package_dist(package) - sha = package.composer_metadatum.target_sha archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true) { 'type' => 'zip', - 'url' => expose_url(archive_api_path) + "?sha=#{sha}", - 'reference' => sha, + 'url' => expose_url(archive_api_path) + "?sha=#{package.composer_target_sha}", + 'reference' => package.composer_target_sha, 'shasum' => '' } end + + def package_source(package) + git_url = package.project.http_url_to_repo + + { + 'type' => 'git', + 'url' => git_url, + 'reference' => package.composer_target_sha + } + end end end end diff --git a/lib/gitlab/graphql/deprecation.rb b/lib/gitlab/graphql/deprecation.rb new file mode 100644 index 00000000000..e0176e2d6e0 --- /dev/null +++ b/lib/gitlab/graphql/deprecation.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Gitlab + module Graphql + class Deprecation + REASONS = { + renamed: 'This was renamed.', + discouraged: 'Use of this is not recommended.' + }.freeze + + include ActiveModel::Validations + + validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' } + validates :reason, presence: true + validates :reason, + format: { with: /.*[^.]\z/, message: 'must not end with a period' }, + if: :reason_is_string? + validate :milestone_is_string + validate :reason_known_or_string + + def self.parse(options) + new(**options) if options + end + + def initialize(reason: nil, milestone: nil, replacement: nil) + @reason = reason.presence + @milestone = milestone.presence + @replacement = replacement.presence + end + + def ==(other) + return false unless other.is_a?(self.class) + + [reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr| + other.send(attr) # rubocop: disable GitlabSecurity/PublicSend + end + end + alias_method :eql, :== + + def markdown(context: :inline) + parts = [ + "#{deprecated_in(format: :markdown)}.", + reason_text, + replacement.then { |r| "Use: `#{r}`." if r } + ].compact + + case context + when :block + ['WARNING:', *parts].join("\n") + when :inline + parts.join(' ') + end + end + + def edit_description(original_description) + @original_description = original_description + return unless original_description + + original_description + description_suffix + end + + def original_description + return unless @original_description + return @original_description if @original_description.ends_with?('.') + + "#{@original_description}." + end + + def deprecation_reason + [ + reason_text, + replacement && "Please use `#{replacement}`.", + "#{deprecated_in}." + ].compact.join(' ') + end + + private + + attr_reader :reason, :milestone, :replacement + + def milestone_is_string + return if milestone.is_a?(String) + + errors.add(:milestone, 'must be a string') + end + + def reason_known_or_string + return if REASONS.key?(reason) + return if reason_is_string? + + errors.add(:reason, 'must be a known reason or a string') + end + + def reason_is_string? + reason.is_a?(String) + end + + def reason_text + @reason_text ||= REASONS[reason] || "#{reason.to_s.strip}." + end + + def description_suffix + " #{deprecated_in}: #{reason_text}" + end + + def deprecated_in(format: :plain) + case format + when :plain + "Deprecated in #{milestone}" + when :markdown + "**Deprecated** in #{milestone}" + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 22f146e0d06..3dac05de23f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3844,6 +3844,9 @@ msgstr "" msgid "Approval rules reset to project defaults" msgstr "" +msgid "Approval settings" +msgstr "" + msgid "ApprovalRuleRemove|%d member" msgid_plural "ApprovalRuleRemove|%d members" msgstr[0] "" @@ -13893,6 +13896,9 @@ msgstr "" msgid "Geo|In sync" msgstr "" +msgid "Geo|Internal URL" +msgstr "" + msgid "Geo|Last repository check run" msgstr "" @@ -13917,9 +13923,6 @@ msgstr "" msgid "Geo|Next sync scheduled at" msgstr "" -msgid "Geo|Node Details" -msgstr "" - msgid "Geo|Node name can't be blank" msgstr "" @@ -13938,6 +13941,9 @@ msgstr "" msgid "Geo|Please refer to Geo Troubleshooting." msgstr "" +msgid "Geo|Primary Details" +msgstr "" + msgid "Geo|Primary node" msgstr "" @@ -13986,6 +13992,9 @@ msgstr "" msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node." msgstr "" +msgid "Geo|Secondary Details" +msgstr "" + msgid "Geo|Secondary node" msgstr "" @@ -14214,6 +14223,9 @@ msgstr "" msgid "GitLab uses %{jaeger_link} to monitor distributed systems." msgstr "" +msgid "GitLab version" +msgstr "" + msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory." msgstr "" diff --git a/qa/qa.rb b/qa/qa.rb index 2ac199c9d26..be7c0de137e 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -96,6 +96,7 @@ module QA autoload :ProjectSnippet, 'qa/resource/project_snippet' autoload :Design, 'qa/resource/design' autoload :RegistryRepository, 'qa/resource/registry_repository' + autoload :Package, 'qa/resource/package' module KubernetesCluster autoload :Base, 'qa/resource/kubernetes_cluster/base' diff --git a/qa/qa/resource/package.rb b/qa/qa/resource/package.rb new file mode 100644 index 00000000000..1009353a296 --- /dev/null +++ b/qa/qa/resource/package.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class Package < Base + attr_accessor :name + + attribute :project do + Project.fabricate_via_api! do |resource| + resource.name = 'project-with-package' + resource.description = 'Project with Package' + end + end + + attribute :id do + packages = project.packages + + return unless (this_package = packages&.find { |package| package[:name] == "#{project.path_with_namespace}/#{name}" }) # rubocop:disable Cop/AvoidReturnFromBlocks + + this_package[:id] + end + + def fabricate! + end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def remove_via_api! + packages = project.packages + + if packages && !packages.empty? + QA::Runtime::Logger.debug("Deleting package '#{name}' from '#{project.path_with_namespace}' via API") + super + end + end + + def api_delete_path + "/projects/#{project.id}/packages/#{id}" + end + + def api_get_path + "/projects/#{project.id}/packages" + end + end + end +end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 23e2ec07491..7d78893d654 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -155,6 +155,10 @@ module QA "#{api_get_path}/registry/repositories" end + def api_packages_path + "#{api_get_path}/packages" + end + def api_commits_path "#{api_get_path}/repository/commits" end @@ -262,7 +266,11 @@ module QA def registry_repositories response = get Runtime::API::Request.new(api_client, "#{api_registry_repositories_path}").url + parse_body(response) + end + def packages + response = get Runtime::API::Request.new(api_client, "#{api_packages_path}").url parse_body(response) end diff --git a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb index 6c1a0cea209..2489545782a 100644 --- a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb @@ -1,18 +1,25 @@ # frozen_string_literal: true +require 'securerandom' + module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'Composer Repository' do include Runtime::Fixtures - let(:package_name) { 'my_package' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'composer-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "my_package-#{SecureRandom.hex(4)}" + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -30,7 +37,7 @@ module QA let(:composer_json_file) do <<~EOF { - "name": "#{project.path_with_namespace}/#{package_name}", + "name": "#{project.path_with_namespace}/#{package.name}", "description": "Library XY", "type": "library", "license": "GPL-3.0-only", @@ -94,14 +101,15 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes a composer package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1088' do Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -109,7 +117,7 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures 'package deletion' do expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb index 7effefc4d73..a1e2eb1046c 100644 --- a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb @@ -5,14 +5,19 @@ module QA describe 'Conan Repository' do include Runtime::Fixtures - let(:package_name) { 'conantest' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'conan-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = 'conantest' + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -29,6 +34,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes, installs, and deletes a Conan package', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1077' do @@ -47,10 +53,10 @@ module QA stage: deploy script: - "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan" - - "conan new #{package_name}/0.1 -t" + - "conan new #{package.name}/0.1 -t" - "conan create . mycompany/stable" - - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package_name}/0.1@mycompany/stable --all --remote=gitlab" - - "conan install conantest/0.1@mycompany/stable --remote=gitlab" + - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package.name}/0.1@mycompany/stable --all --remote=gitlab" + - "conan install #{package.name}/0.1@mycompany/stable --remote=gitlab" tags: - "runner-for-#{project.name}" YAML @@ -71,15 +77,15 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb index 3e3addf9eeb..bbf8ea8c05e 100644 --- a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb @@ -3,14 +3,19 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'Generic Repository' do - let(:package_name) { 'my_package' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'generic-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "my_package" + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -90,14 +95,15 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'uploads a generic package, downloads and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1108' do Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -105,7 +111,7 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures 'package deletion' do expect(index).to have_content("Package deleted successfully") - expect(index).to have_no_package(package_name) + expect(index).to have_no_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb index b5b050a5dfe..4d4f981f021 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb @@ -23,6 +23,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = package_name + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -39,6 +46,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb index 2f508d00790..7234edb46d3 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb @@ -31,6 +31,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = package_name + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb index 97df8fedf87..49b42bd1ff6 100644 --- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb @@ -5,8 +5,7 @@ module QA describe 'npm registry' do include Runtime::Fixtures - let(:registry_scope) { project.group.sandbox.path } - let(:package_name) { "@#{registry_scope}/#{project.name}" } + let!(:registry_scope) { project.group.sandbox.path } let(:auth_token) do unless Page::Main::Menu.perform(&:signed_in?) Flow::Login.sign_in @@ -15,12 +14,23 @@ module QA Resource::PersonalAccessToken.fabricate!.token end - let(:project) do + let!(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'npm-registry-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "@#{registry_scope}/#{project.name}" + package.project = project + end + end + + after do + package.remove_via_api! + end + it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do uri = URI.parse(Runtime::Scenario.gitlab_address) gitlab_host_with_port = "#{uri.host}:#{uri.port}" @@ -29,7 +39,7 @@ module QA file_path: 'package.json', content: <<~JSON { - "name": "#{package_name}", + "name": "#{package.name}", "version": "1.0.0", "description": "Example package for GitLab npm registry", "publishConfig": { @@ -56,20 +66,20 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) + expect(index).to have_package(package.name) - index.click_package(package_name) + index.click_package(package.name) end Page::Project::Packages::Show.perform do |show| - expect(show).to have_package_info(package_name, "1.0.0") + expect(show).to have_package_info(package.name, "1.0.0") show.click_delete end Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb index f143bc52095..c0d84d82e51 100644 --- a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb @@ -6,8 +6,6 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'NuGet Repository' do include Runtime::Fixtures - - let(:package_name) { "dotnetcore-#{SecureRandom.hex(8)}" } let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'nuget-package-project' @@ -15,6 +13,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "dotnetcore-#{SecureRandom.hex(8)}" + package.project = project + end + end + let(:another_project) do Resource::Project.fabricate_via_api! do |project| project.name = 'nuget-package-install-project' @@ -43,6 +48,7 @@ module QA after do runner.remove_via_api! another_runner.remove_via_api! + package.remove_via_api! end it 'publishes a nuget package at the project level, installs and deletes it at the group level', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do @@ -66,7 +72,7 @@ module QA script: - dotnet restore -p:Configuration=Release - dotnet build -c Release - - dotnet pack -c Release -p:PackageID=#{package_name} + - dotnet pack -c Release -p:PackageID=#{package.name} - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text - dotnet nuget push "bin/Release/*.nupkg" --source gitlab only: @@ -127,7 +133,7 @@ module QA script: - dotnet nuget locals all --clear - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text - - "dotnet add otherdotnet.csproj package #{package_name} --version 1.0.0" + - "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0" only: - "#{another_project.default_branch}" tags: @@ -153,15 +159,15 @@ module QA Page::Group::Menu.perform(&:go_to_group_packages) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb index 396863b33c4..06ce74a1e85 100644 --- a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb @@ -4,15 +4,19 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'PyPI Repository' do include Runtime::Fixtures - - let(:package_name) { 'mypypipackage' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'pypi-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = 'mypypipackage' + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -87,6 +91,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! project&.remove_via_api! end @@ -94,8 +99,8 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -103,7 +108,7 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures do expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end @@ -127,8 +132,8 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - index.wait_for_package_replication(package_name) - expect(index).to have_package(package_name) + index.wait_for_package_replication(package.name) + expect(index).to have_package(package.name) end end end diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index 6201cddecb0..ca4931bdc90 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -16,6 +16,15 @@ RSpec.describe Groups::BoardsController do expect { list_boards }.to change(group.boards, :count).by(1) end + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, group, default_enabled: :yaml) + + list_boards + end + context 'when format is HTML' do it 'renders template' do list_boards @@ -98,6 +107,15 @@ RSpec.describe Groups::BoardsController do describe 'GET show' do let!(:board) { create(:board, group: group) } + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, group, default_enabled: :yaml) + + read_board board: board + end + context 'when format is HTML' do it 'renders template' do expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1) diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index cde3a8d4761..48a12a27911 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -22,6 +22,15 @@ RSpec.describe Projects::BoardsController do expect(assigns(:boards_endpoint)).to eq project_boards_path(project) end + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, project, default_enabled: :yaml) + + list_boards + end + context 'when format is HTML' do it 'renders template' do list_boards @@ -116,6 +125,15 @@ RSpec.describe Projects::BoardsController do describe 'GET show' do let!(:board) { create(:board, project: project) } + it 'pushes swimlanes_buffered_rendering feature flag' do + allow(controller).to receive(:push_frontend_feature_flag).and_call_original + + expect(controller).to receive(:push_frontend_feature_flag) + .with(:swimlanes_buffered_rendering, project, default_enabled: :yaml) + + read_board board: board + end + it 'sets boards_endpoint instance variable to a boards path' do read_board board: board diff --git a/spec/frontend/admin/users/new_spec.js b/spec/frontend/admin/users/new_spec.js new file mode 100644 index 00000000000..692c583dca8 --- /dev/null +++ b/spec/frontend/admin/users/new_spec.js @@ -0,0 +1,76 @@ +import { + setupInternalUserRegexHandler, + ID_USER_EMAIL, + ID_USER_EXTERNAL, + ID_WARNING, +} from '~/admin/users/new'; + +describe('admin/users/new', () => { + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; + + let elExternal; + let elUserEmail; + let elWarningMessage; + + beforeEach(() => { + loadFixtures(FIXTURE); + setupInternalUserRegexHandler(); + + elExternal = document.getElementById(ID_USER_EXTERNAL); + elUserEmail = document.getElementById(ID_USER_EMAIL); + elWarningMessage = document.getElementById(ID_WARNING); + + elExternal.checked = true; + }); + + const changeEmail = (val) => { + elUserEmail.value = val; + elUserEmail.dispatchEvent(new Event('input')); + }; + + const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden'); + + describe('Behaviour of userExternal checkbox', () => { + it('hides warning by default', () => { + expect(hasHiddenWarning()).toBe(true); + }); + + describe('when matches email as internal', () => { + beforeEach(() => { + changeEmail('test@'); + }); + + it('has external unchecked', () => { + expect(elExternal.checked).toBe(false); + }); + + it('shows warning', () => { + expect(hasHiddenWarning()).toBe(false); + }); + + describe('when external is checked again', () => { + beforeEach(() => { + elExternal.dispatchEvent(new Event('change')); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); + + describe('when matches emails as external', () => { + beforeEach(() => { + changeEmail('test.ext@'); + }); + + it('has external checked', () => { + expect(elExternal.checked).toBe(true); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/pages/admin/users/new/index_spec.js b/spec/frontend/pages/admin/users/new/index_spec.js deleted file mode 100644 index ec9fe487030..00000000000 --- a/spec/frontend/pages/admin/users/new/index_spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import $ from 'jquery'; -import UserInternalRegexHandler from '~/pages/admin/users/new/index'; - -describe('UserInternalRegexHandler', () => { - const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; - let $userExternal; - let $userEmail; - let $warningMessage; - - beforeEach(() => { - loadFixtures(FIXTURE); - // eslint-disable-next-line no-new - new UserInternalRegexHandler(); - $userExternal = $('#user_external'); - $userEmail = $('#user_email'); - $warningMessage = $('#warning_external_automatically_set'); - if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked'); - }); - - describe('Behaviour of userExternal checkbox when', () => { - it('matches email as internal', (done) => { - expect($warningMessage.hasClass('hidden')).toBeTruthy(); - - $userEmail.val('test@').trigger('input'); - - expect($userExternal.prop('checked')).toBeFalsy(); - expect($warningMessage.hasClass('hidden')).toBeFalsy(); - done(); - }); - - it('matches email as external', (done) => { - expect($warningMessage.hasClass('hidden')).toBeTruthy(); - - $userEmail.val('test.ext@').trigger('input'); - - expect($userExternal.prop('checked')).toBeTruthy(); - expect($warningMessage.hasClass('hidden')).toBeTruthy(); - done(); - }); - }); -}); diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb index 7b0ed703f42..a4d016636aa 100644 --- a/spec/lib/gitlab/composer/version_index_spec.rb +++ b/spec/lib/gitlab/composer/version_index_spec.rb @@ -27,6 +27,11 @@ RSpec.describe Gitlab::Composer::VersionIndex do 'type' => 'zip', 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}" }, + 'source' => { + 'reference' => branch.target, + 'type' => 'git', + 'url' => project.http_url_to_repo + }, 'name' => package.name, 'uid' => package.id, 'version' => package.version diff --git a/spec/lib/gitlab/graphql/deprecation_spec.rb b/spec/lib/gitlab/graphql/deprecation_spec.rb new file mode 100644 index 00000000000..8b41145b855 --- /dev/null +++ b/spec/lib/gitlab/graphql/deprecation_spec.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'active_model' + +RSpec.describe ::Gitlab::Graphql::Deprecation do + let(:options) { {} } + + subject(:deprecation) { described_class.parse(options) } + + describe '.parse' do + context 'with nil' do + let(:options) { nil } + + it 'parses to nil' do + expect(deprecation).to be_nil + end + end + + context 'with empty options' do + let(:options) { {} } + + it 'parses to an empty deprecation' do + expect(deprecation).to eq(described_class.new) + end + end + + context 'with defined options' do + let(:options) { { reason: :renamed, milestone: '10.10' } } + + it 'assigns the properties' do + expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10')) + end + end + end + + describe 'validations' do + let(:options) { { reason: :renamed, milestone: '10.10' } } + + it { is_expected.to be_valid } + + context 'when the milestone is absent' do + before do + options.delete(:milestone) + end + + it { is_expected.not_to be_valid } + end + + context 'when the milestone is not milestone-ish' do + before do + options[:milestone] = 'next year' + end + + it { is_expected.not_to be_valid } + end + + context 'when the milestone is not a string' do + before do + options[:milestone] = 10.01 + end + + it { is_expected.not_to be_valid } + end + + context 'when the reason is absent' do + before do + options.delete(:reason) + end + + it { is_expected.not_to be_valid } + end + + context 'when the reason is not a known reason' do + before do + options[:reason] = :not_stylish_enough + end + + it { is_expected.not_to be_valid } + end + + context 'when the reason is a string' do + before do + options[:reason] = 'not stylish enough' + end + + it { is_expected.to be_valid } + end + + context 'when the reason is a string ending with a period' do + before do + options[:reason] = 'not stylish enough.' + end + + it { is_expected.not_to be_valid } + end + end + + describe '#deprecation_reason' do + context 'when there is a replacement' do + let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } } + + it 'renders as reason-replacement-milestone' do + expect(deprecation.deprecation_reason).to eq('This was renamed. Please use `X.y`. Deprecated in 10.10.') + end + end + + context 'when there is no replacement' do + let(:options) { { reason: :renamed, milestone: '10.10' } } + + it 'renders as reason-milestone' do + expect(deprecation.deprecation_reason).to eq('This was renamed. Deprecated in 10.10.') + end + end + + describe 'processing of reason' do + described_class::REASONS.each_key do |known_reason| + context "when the reason is a known reason such as #{known_reason.inspect}" do + let(:options) { { reason: known_reason } } + + it 'renders the reason_text correctly' do + expect(deprecation.deprecation_reason).to start_with(described_class::REASONS[known_reason]) + end + end + end + + context 'when the reason is any other string' do + let(:options) { { reason: 'unhelpful' } } + + it 'appends a period' do + expect(deprecation.deprecation_reason).to start_with('unhelpful.') + end + end + end + end + + describe '#edit_description' do + let(:options) { { reason: :renamed, milestone: '10.10' } } + + it 'appends milestone:reason with a leading space if there is a description' do + desc = deprecation.edit_description('Some description.') + + expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.') + end + + it 'returns nil if there is no description' do + desc = deprecation.edit_description(nil) + + expect(desc).to be_nil + end + end + + describe '#original_description' do + it 'records the description passed to it' do + deprecation.edit_description('Some description.') + + expect(deprecation.original_description).to eq('Some description.') + end + end + + describe '#markdown' do + context 'when there is a replacement' do + let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } } + + context 'when the context is :inline' do + it 'renders on one line' do + expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.' + + expect(deprecation.markdown).to eq(expectation) + expect(deprecation.markdown(context: :inline)).to eq(expectation) + end + end + + context 'when the context is :block' do + it 'renders a warning note' do + expectation = <<~MD.chomp + WARNING: + **Deprecated** in 10.10. + This was renamed. + Use: `X.y`. + MD + + expect(deprecation.markdown(context: :block)).to eq(expectation) + end + end + end + + context 'when there is no replacement' do + let(:options) { { reason: 'Removed', milestone: '10.10' } } + + context 'when the context is :inline' do + it 'renders on one line' do + expectation = '**Deprecated** in 10.10. Removed.' + + expect(deprecation.markdown).to eq(expectation) + expect(deprecation.markdown(context: :inline)).to eq(expectation) + end + end + + context 'when the context is :block' do + it 'renders a warning note' do + expectation = <<~MD.chomp + WARNING: + **Deprecated** in 10.10. + Removed. + MD + + expect(deprecation.markdown(context: :block)).to eq(expectation) + end + end + end + end +end diff --git a/spec/presenters/packages/composer/packages_presenter_spec.rb b/spec/presenters/packages/composer/packages_presenter_spec.rb index c4217b6e37c..2d8a45c1dff 100644 --- a/spec/presenters/packages/composer/packages_presenter_spec.rb +++ b/spec/presenters/packages/composer/packages_presenter_spec.rb @@ -9,8 +9,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do let_it_be(:json) { { 'name' => package_name } } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) } - let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) } - let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) } + let!(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) } + let!(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) } let(:branch) { project.repository.find_branch('master') } @@ -28,6 +28,11 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do 'type' => 'zip', 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}" }, + 'source' => { + 'reference' => branch.target, + 'type' => 'git', + 'url' => "http://localhost/#{group.path}/#{project.path}.git" + }, 'name' => package.name, 'uid' => package.id, 'version' => package.version diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb index bc091a678e2..efb2c466f70 100644 --- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb +++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb @@ -13,18 +13,18 @@ RSpec.shared_examples 'Gitlab-style deprecations' do it 'raises an error if a required property is missing', :aggregate_failures do expect { subject(deprecated: { milestone: '1.10' }) }.to raise_error( ArgumentError, - 'Please provide a `reason` within `deprecated`' + include("Reason can't be blank") ) expect { subject(deprecated: { reason: 'Deprecation reason' }) }.to raise_error( ArgumentError, - 'Please provide a `milestone` within `deprecated`' + include("Milestone can't be blank") ) end it 'raises an error if milestone is not a String', :aggregate_failures do expect { subject(deprecated: { milestone: 1.10, reason: 'Deprecation reason' }) }.to raise_error( ArgumentError, - '`milestone` must be a `String`' + include("Milestone must be a string") ) end end @@ -49,4 +49,22 @@ RSpec.shared_examples 'Gitlab-style deprecations' do expect(deprecable.description).to be_nil end + + it 'adds information about the replacement if provided' do + deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed, replacement: 'Foo.bar' }) + + expect(deprecable.deprecation_reason).to include 'Please use `Foo.bar`' + end + + it 'supports named reasons: renamed' do + deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed }) + + expect(deprecable.deprecation_reason).to include 'This was renamed.' + end + + it 'supports named reasons: discouraged' do + deprecable = subject(deprecated: { milestone: '1.10', reason: :discouraged }) + + expect(deprecable.deprecation_reason).to include 'Use of this is not recommended.' + end end