diff options
31 files changed, 388 insertions, 73 deletions
diff --git a/.gitlab/issue_templates/Problem Validation.md b/.gitlab/issue_templates/Problem Validation.md index 3f92510b6af..dee026ee752 100644 --- a/.gitlab/issue_templates/Problem Validation.md +++ b/.gitlab/issue_templates/Problem Validation.md @@ -1,4 +1,4 @@ -<!-- This template is used as a starting point for understing and articulating a customer problem. +<!-- This template is used as a starting point for understanding and articulating a customer problem. Learn more about it in the handbook: https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation --> diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index 6ad57fd8985..cc048e2af1a 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -98,9 +98,6 @@ export default { return this.$options.i18n[this.currentPage].btnText; }, buttonKind() { - if (this.isNewForm) { - return 'success'; - } if (this.isDeleteForm) { return 'danger'; } diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index f0db3e5594b..4d9fe6ff851 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -765,6 +765,9 @@ "filter": { "oneOf": [ { + "type": "null" + }, + { "$ref": "#/definitions/filter_refs" }, { diff --git a/app/assets/javascripts/tabs/constants.js b/app/assets/javascripts/tabs/constants.js index 3b84d7394d4..90c9a89d652 100644 --- a/app/assets/javascripts/tabs/constants.js +++ b/app/assets/javascripts/tabs/constants.js @@ -1,8 +1,4 @@ -export const ACTIVE_TAB_CLASSES = Object.freeze([ - 'active', - 'gl-tab-nav-item-active', - 'gl-tab-nav-item-active-indigo', -]); +export const ACTIVE_TAB_CLASSES = Object.freeze(['active', 'gl-tab-nav-item-active']); export const ACTIVE_PANEL_CLASS = 'active'; diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index f96e080ecf8..b2940b802a4 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -209,6 +209,21 @@ class GroupsController < Groups::ApplicationController end end + def issues + return super if Feature.disabled?(:vue_issues_list, group, default_enabled: :yaml) + + @has_issues = IssuesFinder.new(current_user, group_id: group.id).execute + .non_archived + .exists? + + @has_projects = group_projects.exists? + + respond_to do |format| + format.html + format.atom { render layout: 'xml.atom' } + end + end + protected def render_show_html diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 63ac5f97420..dad73c37fea 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -131,11 +131,28 @@ class Projects::BranchesController < Projects::ApplicationController private def sort_value_for_mode - return params[:sort] if params[:sort].present? + custom_sort || default_sort + end + + def custom_sort + sort = params[:sort].presence + + unless sort.in?(supported_sort_options) + flash.now[:alert] = _("Unsupported sort value.") + sort = nil + end + sort + end + + def default_sort 'stale' == @mode ? sort_value_oldest_updated : sort_value_recently_updated end + def supported_sort_options + [nil, sort_value_name, sort_value_oldest_updated, sort_value_recently_updated] + end + # It can be expensive to calculate the diverging counts for each # branch. Normally the frontend should be specifying a set of branch # names, but prior to diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c459afbbcf6..4fabbe6a2b7 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,7 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController include RecordUserLastActivity ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze - SET_ISSUABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze + SET_ISSUABLES_INDEX_ONLY_ACTIONS = %i[calendar service_desk].freeze prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) } prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) } @@ -22,7 +22,10 @@ class Projects::IssuesController < Projects::ApplicationController before_action :issue, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) } after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) } - before_action :set_issuables_index, if: ->(c) { SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) } + before_action :set_issuables_index, if: ->(c) { + SET_ISSUABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) || + (c.action_name.to_sym == :index && Feature.disabled?(:vue_issues_list, project&.group, default_enabled: :yaml)) + } # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 5aa2aca37f3..9f11fb0d4b6 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -231,10 +231,10 @@ module IssuesHelper ) end - def group_issues_list_data(group, current_user, issues, projects) + def group_issues_list_data(group, current_user) common_issues_list_data(group, current_user).merge( - has_any_issues: issues.to_a.any?.to_s, - has_any_projects: any_projects?(projects).to_s + has_any_issues: @has_issues.to_s, + has_any_projects: @has_projects.to_s ) end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 2efc3f27dc7..dbbe7069ca4 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -33,7 +33,7 @@ module TabHelper # def gl_tab_link_to(name = nil, options = {}, html_options = {}, &block) link_classes = %w[nav-link gl-tab-nav-item] - active_link_classes = %w[active gl-tab-nav-item-active gl-tab-nav-item-active-indigo] + active_link_classes = %w[active gl-tab-nav-item-active] if block_given? # Shift params to skip the omitted "name" param diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 2af121f69a8..fd460d71867 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -173,10 +173,7 @@ module UsersHelper end def display_public_email?(user) - return false if user.public_email.blank? - return true unless user.provisioned_by_group - - !Feature.enabled?(:hide_public_email_on_profile, user.provisioned_by_group) + user.public_email.present? end private diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0883df5ed6b..53e192dc1a1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -72,7 +72,6 @@ module Ci has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent has_many :variables, class_name: 'Ci::PipelineVariable' - has_many :deployments, through: :builds has_many :latest_builds, -> { latest.with_project_and_metadata }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build' has_many :downloadable_artifacts, -> do not_expired.or(where_exists(::Ci::Pipeline.artifacts_locked.where('ci_pipelines.id = ci_builds.commit_id'))).downloadable.with_job diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 98575c614e5..0f8a4be03a7 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -157,9 +157,9 @@ module Ci from_union( [ belonging_to_project(project_id), - belonging_to_parent_group_of_project(project_id), + project.group_runners_enabled? ? belonging_to_parent_group_of_project(project_id) : nil, project.shared_runners - ], + ].compact, remove_duplicates: false ) end diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml index 13ac511f1b4..afceb6427e0 100644 --- a/app/views/admin/background_migrations/index.html.haml +++ b/app/views/admin/background_migrations/index.html.haml @@ -1,33 +1,26 @@ - page_title _('Background Migrations') -.tabs.gl-tabs - %div - %ul.nav.gl-tabs-nav{ role: 'tablist' } - - active_tab_classes = ['gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo'] += gl_tabs_nav do + = gl_tab_link_to admin_background_migrations_path, item_active: @current_tab == 'queued' do + = _('Queued') + = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['queued']) + = gl_tab_link_to admin_background_migrations_path(tab: 'failed'), item_active: @current_tab == 'failed' do + = _('Failed') + = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['failed']) + = gl_tab_link_to admin_background_migrations_path(tab: 'finished'), item_active: @current_tab == 'finished' do + = _('Finished') + = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['finished']) - %li.nav-item{ role: 'presentation' } - %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path, class: (active_tab_classes if @current_tab == 'queued'), role: 'tab' } - = _('Queued') - = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['queued']) - %li.nav-item{ role: 'presentation' } - %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'failed'), class: (active_tab_classes if @current_tab == 'failed'), role: 'tab' } - = _('Failed') - = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['failed']) - %li.nav-item{ role: 'presentation' } - %a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'finished'), class: (active_tab_classes if @current_tab == 'finished'), role: 'tab' } - = _('Finished') - = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['finished']) +.tab-content.gl-tab-content + .tab-pane.active{ role: 'tabpanel' } + %table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' } + %thead{ role: 'rowgroup' } + %tr{ role: 'row' } + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Migration') + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Progress') + %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Status') + %th.table-th-transparent.border-bottom{ role: 'cell' } + %tbody{ role: 'rowgroup' } + = render partial: 'migration', collection: @migrations - .tab-content.gl-tab-content - .tab-pane.active{ role: 'tabpanel' } - %table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' } - %thead{ role: 'rowgroup' } - %tr{ role: 'row' } - %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Migration') - %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Progress') - %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Status') - %th.table-th-transparent.border-bottom{ role: 'cell' } - %tbody{ role: 'rowgroup' } - = render partial: 'migration', collection: @migrations - - = paginate_collection @migrations + = paginate_collection @migrations diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index a9258a4e0d0..8afa6316c56 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -6,7 +6,7 @@ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues") - if Feature.enabled?(:vue_issues_list, @group, default_enabled: :yaml) - .js-issues-list{ data: group_issues_list_data(@group, current_user, @issues, @projects) } + .js-issues-list{ data: group_issues_list_data(@group, current_user) } - if @can_bulk_update = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues - else diff --git a/data/deprecations/14-7-deprecate-merged_by-api-field.yml b/data/deprecations/14-7-deprecate-merged_by-api-field.yml index 31b2d9c9244..e2ecef1aa6a 100644 --- a/data/deprecations/14-7-deprecate-merged_by-api-field.yml +++ b/data/deprecations/14-7-deprecate-merged_by-api-field.yml @@ -15,7 +15,7 @@ announcement_date: "2022-01-22" # 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: "15.0" # The milestone when this feature is planned to be removed removal_date: "2022-05-22" # the date of the milestone release when this feature is planned to be removed - breaking_change: false # If this deprecation is a breaking change, set this value to true + breaking_change: true # If this deprecation is a breaking change, set this value to true body: | # Do not modify this line, instead modify the lines below. The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `merge_user` field (already present in GraphQL) which more correctly identifies who merged a merge request when performing actions (merge when pipeline succeeds, add to merge train) other than a simple merge. # The following items are not published on the docs page, but may be used in the future. diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md index b3dc6ffc2b2..2d685c6a951 100644 --- a/doc/administration/packages/dependency_proxy.md +++ b/doc/administration/packages/dependency_proxy.md @@ -199,6 +199,21 @@ This section describes the earlier configuration format. 1. [Restart GitLab](../restart_gitlab.md#installations-from-source "How to restart GitLab") for the changes to take effect. +### Migrate from local storage to object storage + +GitLab currently [doesn't support](https://gitlab.com/gitlab-org/gitlab/-/issues/343064) +migrating the existing cache to object storage. + +To switch from local storage to object storage: + +1. [Reconfigure GitLab to use object storage](#using-object-storage). +1. Purge the dependency proxy cache (on both local and object storage) + using the [Dependency Proxy API](../../api/dependency_proxy.md#purge-the-dependency-proxy-for-a-group). + +Object storage now caches container images when they're requested. This also +[reduces the size of dependency proxy storage](../../user/packages/dependency_proxy/reduce_dependency_proxy_storage.md) +by removing unused images. + ## Disabling Authentication Authentication was introduced in 13.7 as part of [enabling private groups to use the diff --git a/doc/ci/cloud_services/google_cloud/index.md b/doc/ci/cloud_services/google_cloud/index.md new file mode 100644 index 00000000000..aa48c971dc6 --- /dev/null +++ b/doc/ci/cloud_services/google_cloud/index.md @@ -0,0 +1,182 @@ +--- +stage: Verify +group: Pipeline Authoring +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Configure OpenID Connect with GCP Workload Identity Federation + +WARNING: +The `CI_JOB_JWT_V2` variable is under development [(alpha)](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha) and is not yet suitable for production use. + +This tutorial demonstrates authenticating to Google Cloud from a GitLab CI/CD job +using a JSON Web Token (JWT) token and Workload Identity Federation. This configuration +generates on-demand, short-lived credentials without needing to store any secrets. + +To get started, configure OpenID Connect (OIDC) for identity federation between GitLab +and Google Cloud. For more information on using OIDC with GitLab, read +[Connect to cloud services](../index.md). + +This tutorial assumes you have a Google Cloud account and a Google Cloud project. +Your account must have at least the **Workload Identity Pool Admin** permission +on the Google Cloud project. + +To complete this tutorial: + +1. [Create the Google Cloud Workload Identity Pool](#create-the-google-cloud-workload-identity-pool). +1. [Create a Workload Identity Provider](#create-a-workload-identity-provider). +1. [Grant permissions for service account impersonation](#grant-permissions-for-service-account-impersonation). +1. [Retrieve a temporary credential](#retrieve-a-temporary-credential). + +## Create the Google Cloud Workload Identity Pool + +[Create a new Google Cloud Workload Identity Pool](https://cloud.google.com/iam/docs/configuring-workload-identity-federation#oidc) with the following options: + +- **Name**: Human-friendly name for the Workload Identity Pool, such as `GitLab`. +- **Pool ID**: Unique ID in the Google Cloud project for the Workload Identity Pool, + such as `gitlab`. This value is used to refer to the pool. and appears in URLs. +- **Description**: Optional. A description of the pool. +- **Enabled Pool**: Ensure this option is `true`. + +We recommend creating a single _pool_ per GitLab installation per Google Cloud project. If you have multiple GitLab repositories and CI/CD jobs on the same GitLab instance, they can authenticate using different _providers_ against the same _pool_. + +## Create a Workload Identity Provider + +[Create a new Google Cloud Workload Identity Provider](https://cloud.google.com/iam/docs/configuring-workload-identity-federation#create_the_workload_identity_pool_and_provider) +inside the Workload Identity Pool created in the previous step, using the following options: + +- **Provider type**: OpenID Connect (OIDC). +- **Provider name**: Human-friendly name for the Workload Identity Provider, + such as `gitlab/gitlab`. +- **Provider ID**: Unique ID in the pool for the Workload Identity Provider, + such as `gitlab-gitlab`. This value is used to refer to the provider, and appears in URLs. +- **Issuer (URL)**: The address of your GitLab instance, such as `https://gitlab.com` or + `https://gitlab.example.com`. + - The address must use the `https://` protocol. + - The address must end in a trailing slash. +- **Audiences**: Manually set the allowed audiences list to the address of your + GitLab instance, such as `https://gitlab.com` or `https://gitlab.example.com`. + - The address must use the `https://` protocol. + - The address must not end in a trailing slash. +- **Provider attributes mapping**: Create the following mappings, where `attribute.X` is the + name of the attribute you would like to be present on Google's claims, and `assertion.X` + is the value to extract from the [GitLab claim](../index.md#how-it-works): + + | Attribute (on Google) | Assertion (from GitLab) | + | --- | --- | + | `google.subject` | `assertion.sub` | + | `attribute.X` | `assertion.X` | + + You can also [build complex attributes](https://cloud.google.com/iam/help/workload-identity/attribute-mapping) + using Common Expression Language (CEL). + + You must map every attribute that you want to use for permission granting. For example, if you want to map permissions in the next step based on the user's email address, you must map `attribute.user_email` to `assertion.user_email`. + +## Grant permissions for Service Account impersonation + +Creating the Workload Identity Pool and Workload Identity Provider defines the _authentication_ +into Google Cloud. At this point, you can authenticate from GitLab CI/CD job into Google Cloud. +However, you have no permissions on Google Cloud (_authorization_). + +To grant your GitLab CI/CD job permissions on Google Cloud, you must: + +1. [Create a Google Cloud Service Account](https://www.google.com/search?q=google+cloud+create+service+account). + You can use whatever name and ID you prefer. +1. [Grant IAM permissions](https://cloud.google.com/iam/docs/granting-changing-revoking-access) to your + service account on Google Cloud resources. These permissions vary significantly based on + your use case. In general, grant this service account the permissions on your Google Cloud + project and resources you want your GitLab CI/CD job to be able to use. For example, if you needed to upload a file to a Google Cloud Storage bucket in your GitLab CI/CD job, you would grant this Service Account the `roles/storage.objectCreator` role on your Cloud Storage bucket. +1. [Grant the external identity permissions](https://cloud.google.com/iam/docs/using-workload-identity-federation#impersonate) + to impersonate that Service Account. This step enables a GitLab CI/CD job to _authorize_ + to Google Cloud, via Service Account impersonation. This step grants an IAM permission + _on the Service Account itself_, giving the external identity permissions to act as that + service account. External identities are expressed using the `principalSet://` protocol. + +Much like the previous step, this step depends heavily on your desired configuration. +For example, to allow a GitLab CI/CD job to impersonate a Service Account named +`my-service-account` if the GitLab CI/CD job was initiated by a GitLab user with the +username `chris`, you would grant the `roles/iam.workloadIdentityUser` IAM role to the +external identity on `my-service-account`. The external identity takes the format: + +```plaintext +principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.user_login/chris +``` + +where `PROJECT_NUMBER` is your Google Cloud project number, and `POOL_ID` is the +ID (not name) of the Workload Identity Pool created in the first section. + +This configuration also assumes you added `user_login` as an attribute mapped from +the assertion in the previous section. + +## Retrieve a temporary credential + +After you configure the OIDC and role, the GitLab CI/CD job can retrieve a temporary credential from the +[Google Cloud Security Token Service (STS)](https://cloud.google.com/iam/docs/reference/sts/rest). + +```shell +PAYLOAD="$(cat <<EOF +{ + "audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID", + "grantType": "urn:ietf:params:oauth:grant-type:token-exchange", + "requestedTokenType": "urn:ietf:params:oauth:token-type:access_token", + "scope": "https://www.googleapis.com/auth/cloud-platform", + "subjectTokenType": "urn:ietf:params:oauth:token-type:jwt", + "subjectToken": "${CI_JOB_JWT_V2}" +} +EOF +)" +``` + +```shell +FEDERATED_TOKEN="$(curl --fail "https://sts.googleapis.com/v1/token" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "${PAYLOAD}" \ + | jq -r '.access_token' +)" +``` + +Where: + +- `PROJECT_NUMBER` is your Google Cloud project number (not name). +- `POOL_ID` is the ID of the Workload Identity Pool created in the first section. +- `PROVIDER_ID` is the ID of the Workload Identity Provider created in the second section. +- `CI_JOB_JWT_V2` is injected into the CI/CD job by GitLab. For more information about + this variable, read [Connect to cloud services](../index.md). + +You can then use the resulting federated token to impersonate the service account created +in the previous section: + +```shell +ACCESS_TOKEN="$(curl --fail "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SERVICE_ACCOUNT_EMAIL:generateAccessToken" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --header "Authorization: Bearer FEDERATED_TOKEN" \ + --data '{"scope": ["https://www.googleapis.com/auth/cloud-platform"]}' \ + | jq -r '.accessToken' +)" +``` + +Where: + +- `SERVICE_ACCOUNT_EMAIL` is the full email address of the service account to impersonate, + created in the previous section. +- `FEDERATED_TOKEN` is the federated token retrieved from the previous step. + +The result is a Google Cloud OAuth 2.0 access token, which you can use to authenticate to +most Google Cloud APIs and services when used as a bearer token. You can also pass this +value to the `gcloud` CLI by setting the environment variable `CLOUDSDK_AUTH_ACCESS_TOKEN`. + +## Working example + +Review this +[reference project](https://gitlab.com/guided-explorations/gcp/configure-openid-connect-in-gcp) +for provisioning OIDC in GCP using Terraform and a sample script to retrieve temporary credentials. + +## Troubleshooting + +- When debugging `curl` responses, install the latest version of curl. Use `--fail-with-body` + instead of `-f`. This command prints the entire body, which can contain helpful error messages. + +- Review Google Cloud's documentation for + [Troubleshooting Workload Identity Federation](https://cloud.google.com/iam/docs/troubleshooting-workload-identity-federation). diff --git a/doc/ci/cloud_services/index.md b/doc/ci/cloud_services/index.md index 73e726ea8a9..43425333ce4 100644 --- a/doc/ci/cloud_services/index.md +++ b/doc/ci/cloud_services/index.md @@ -99,9 +99,9 @@ sequenceDiagram Note right of Cloud: Decode & verify JWT with public key (https://gitlab/-/jwks) Note right of Cloud: Validate audience defined in OIDC Note right of Cloud: Validate conditional (sub, aud) role - Note right of Cloud: Generate credential or fetch secret + Note right of Cloud: Generate credential or fetch secret Cloud->>GitLab: Return temporary credential - Note left of GitLab: Perform operation + Note left of GitLab: Perform operation ``` @@ -131,3 +131,4 @@ To configure the trust between GitLab and OIDC, you must create a conditional ro To connect with your cloud provider, see the following tutorials: - [Configure OpenID Connect in AWS](aws/index.md) +- [Configure OpenID Connect in Google Cloud](google_cloud/index.md) diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index d3ee30011db..2d458742d7a 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -715,6 +715,12 @@ only supported report file in 15.0, but this is the first step towards GitLab su ### merged_by API field +WARNING: +This feature will be changed or removed in 15.0 +as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes). +Before updating GitLab, review the details carefully to determine if you need to make any +changes to your code, settings, or workflow. + The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `merge_user` field (already present in GraphQL) which more correctly identifies who merged a merge request when performing actions (merge when pipeline succeeds, add to merge train) other than a simple merge. **Planned removal milestone: 15.0 (2022-05-22)** diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 462c4a3de4c..600973ac120 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -24,7 +24,7 @@ module API helpers do params :filter_params do optional :search, type: String, desc: 'Return list of branches matching the search criteria' - optional :sort, type: String, desc: 'Return list of branches sorted by the given field' + optional :sort, type: String, desc: 'Return list of branches sorted by the given field', values: %w[name_asc updated_asc updated_desc] end end diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index dc65e2abb46..d1a0f8e5859 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -64,8 +64,8 @@ module Banzai def internal_url?(uri) return false if uri.nil? - # Relative URLs miss a hostname - return true unless uri.hostname + # Relative URLs miss a hostname AND a scheme + return true if !uri.hostname && !uri.scheme uri.hostname == internal_url.hostname end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 367e1f8abd8..5bff1e04064 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -38452,6 +38452,9 @@ msgstr "" msgid "Unsubscribes from this %{quick_action_target}." msgstr "" +msgid "Unsupported sort value." +msgstr "" + msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}" msgstr "" diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index d9dedb04b0d..ea22e6b6f10 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -657,6 +657,36 @@ RSpec.describe Projects::BranchesController do end end + context 'sorting', :aggregate_failures do + let(:sort) { 'name_asc' } + + before do + get :index, format: :html, params: { + namespace_id: project.namespace, project_id: project, state: 'all', sort: sort + } + end + + it { expect(assigns[:sort]).to eq('name_asc') } + + context 'when sort is not provided' do + let(:sort) { nil } + + it 'uses a default sort without an error message' do + expect(assigns[:sort]).to eq('updated_desc') + expect(controller).not_to set_flash.now[:alert] + end + end + + context 'when sort is not supported' do + let(:sort) { 'unknown' } + + it 'uses a default sort and shows an error message' do + expect(assigns[:sort]).to eq('updated_desc') + expect(controller).to set_flash.now[:alert].to(/Unsupported sort/) + end + end + end + context 'when gitaly is not available' do before do allow_next_instance_of(Gitlab::GitalyClient::RefService) do |ref_service| diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb index 94fb3a0314f..e71cb11b413 100644 --- a/spec/features/admin/admin_sees_background_migrations_spec.rb +++ b/spec/features/admin/admin_sees_background_migrations_spec.rb @@ -68,7 +68,7 @@ RSpec.describe "Admin > Admin sees background migrations" do tab.click expect(page).to have_current_path(admin_background_migrations_path(tab: 'failed')) - expect(tab[:class]).to include('gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo') + expect(tab[:class]).to include('gl-tab-nav-item-active') expect(page).to have_selector('tbody tr', count: 1) @@ -93,7 +93,7 @@ RSpec.describe "Admin > Admin sees background migrations" do tab.click expect(page).to have_current_path(admin_background_migrations_path(tab: 'finished')) - expect(tab[:class]).to include('gl-tab-nav-item-active', 'gl-tab-nav-item-active-indigo') + expect(tab[:class]).to include('gl-tab-nav-item-active') expect(page).to have_selector('tbody tr', count: 1) diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js index 692fd3ec555..5678da2a246 100644 --- a/spec/frontend/boards/components/board_form_spec.js +++ b/spec/frontend/boards/components/board_form_spec.js @@ -130,7 +130,7 @@ describe('BoardForm', () => { it('passes correct primary action text and variant', () => { expect(findModalActionPrimary().text).toBe('Create board'); - expect(findModalActionPrimary().attributes[0].variant).toBe('success'); + expect(findModalActionPrimary().attributes[0].variant).toBe('confirm'); }); it('does not render delete confirmation message', () => { diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 065ac526ae4..50504bb4366 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -342,8 +342,6 @@ RSpec.describe IssuesHelper do describe '#group_issues_list_data' do let(:group) { create(:group) } let(:current_user) { double.as_null_object } - let(:issues) { [] } - let(:projects) { [] } it 'returns expected result' do allow(helper).to receive(:current_user).and_return(current_user) @@ -351,20 +349,23 @@ RSpec.describe IssuesHelper do allow(helper).to receive(:image_path).and_return('#') allow(helper).to receive(:url_for).and_return('#') + assign(:has_issues, false) + assign(:has_projects, true) + expected = { autocomplete_award_emojis_path: autocomplete_award_emojis_path, calendar_path: '#', empty_state_svg_path: '#', full_path: group.full_path, - has_any_issues: issues.to_a.any?.to_s, - has_any_projects: any_projects?(projects).to_s, + has_any_issues: false.to_s, + has_any_projects: true.to_s, is_signed_in: current_user.present?.to_s, jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), rss_path: '#', sign_in_path: new_user_session_path } - expect(helper.group_issues_list_data(group, current_user, issues, projects)).to include(expected) + expect(helper.group_issues_list_data(group, current_user)).to include(expected) end end diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb index f338eddedfd..dd5707e2aff 100644 --- a/spec/helpers/tab_helper_spec.rb +++ b/spec/helpers/tab_helper_spec.rb @@ -45,7 +45,7 @@ RSpec.describe TabHelper do end it 'creates an active tab with item_active = true' do - expect(helper.gl_tab_link_to('Link', '/url', { item_active: true })).to match(/<a class=".*active gl-tab-nav-item-active gl-tab-nav-item-active-indigo.*"/) + expect(helper.gl_tab_link_to('Link', '/url', { item_active: true })).to match(/<a class=".*active gl-tab-nav-item-active.*"/) end context 'when on the active page' do @@ -54,7 +54,7 @@ RSpec.describe TabHelper do end it 'creates an active tab' do - expect(helper.gl_tab_link_to('Link', '/url')).to match(/<a class=".*active gl-tab-nav-item-active gl-tab-nav-item-active-indigo.*"/) + expect(helper.gl_tab_link_to('Link', '/url')).to match(/<a class=".*active gl-tab-nav-item-active.*"/) end it 'creates an inactive tab with item_active = false' do diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index a2d91414e78..82f4ae596e1 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -11,6 +11,20 @@ RSpec.describe UsersHelper do badges.reject { |badge| badge[:text] == 'Is using seat' } end + describe 'display_public_email?' do + let_it_be(:user) { create(:user, :public_email) } + + subject { helper.display_public_email?(user) } + + it { is_expected.to be true } + + context 'when user public email is blank' do + let_it_be(:user) { create(:user, public_email: '') } + + it { is_expected.to be false } + end + end + describe '#user_link' do subject { helper.user_link(user) } diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index 24d13bdb42c..036817834d5 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -71,6 +71,13 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter do expect(doc.to_html).to eq(expected) end + + it 'adds rel and target attributes to improperly formatted protocols' do + doc = filter %q(<p><a target="_blank" href="http:evil.com">Reverse Tabnabbing</a></p>) + expected = %q(<p><a target="_blank" href="http:evil.com" rel="nofollow noreferrer noopener">Reverse Tabnabbing</a></p>) + + expect(doc.to_html).to eq(expected) + end end context 'for links with a username' do diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b076c1c7704..08bbdfd8185 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -302,20 +302,38 @@ RSpec.describe Ci::Runner do context 'with instance runners sharing disabled' do # group specific let_it_be(:group) { create(:group, shared_runners_enabled: false) } - let_it_be(:project) { create(:project, group: group, shared_runners_enabled: false) } let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) } + let(:group_runners_enabled) { true } + let(:project) { create(:project, group: group, shared_runners_enabled: false) } + # project specific - let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) } + let(:project_runner) { create(:ci_runner, :project, projects: [project]) } # globally shared let_it_be(:shared_runner) { create(:ci_runner, :instance) } + before do + project.update!(group_runners_enabled: group_runners_enabled) + end + describe '.owned_or_instance_wide' do subject { described_class.owned_or_instance_wide(project.id) } - it 'returns a project specific and a group specific runner' do - is_expected.to contain_exactly(group_runner, project_runner) + context 'with group runners disabled' do + let(:group_runners_enabled) { false } + + it 'returns only the project specific runner' do + is_expected.to contain_exactly(project_runner) + end + end + + context 'with group runners enabled' do + let(:group_runners_enabled) { true } + + it 'returns a project specific and a group specific runner' do + is_expected.to contain_exactly(group_runner, project_runner) + end end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index ad517a05533..780e45cf443 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -188,6 +188,24 @@ RSpec.describe API::Branches do end end + context 'when sort parameter is passed' do + it 'sorts branches' do + get api(route, user), params: { sort: 'name_asc', per_page: 10 } + + sorted_branch_names = json_response.map { |branch| branch['name'] } + + project_branch_names = project.repository.branch_names.sort.take(10) + + expect(sorted_branch_names).to eq(project_branch_names) + end + + context 'when sort value is not supported' do + it_behaves_like '400 response' do + let(:request) { get api(route, user), params: { sort: 'unknown' }} + end + end + end + context 'when unauthenticated', 'and project is public' do before do project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) |