diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-13 03:11:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-13 03:11:34 +0300 |
commit | ec808a3c7020a1f487499314fc4d9942ea188ec4 (patch) | |
tree | bed36c31e8dccc4c0b1ac1c0927ec188a84e9d59 | |
parent | a60e53c7671c299432f0c255ffaf0e0c9fa9eeab (diff) |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 509 insertions, 73 deletions
diff --git a/.gitlab/ci/qa-common/rules.gitlab-ci.yml b/.gitlab/ci/qa-common/rules.gitlab-ci.yml index c0a4e8d206f..c593ec4ccfb 100644 --- a/.gitlab/ci/qa-common/rules.gitlab-ci.yml +++ b/.gitlab/ci/qa-common/rules.gitlab-ci.yml @@ -45,7 +45,7 @@ # If Schedule pipeline .if-schedule-pipeline: &if-schedule-pipeline - if: '$CI_PIPELINE_SOURCE == "schedule"' + if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $SCHEDULE_TYPE == "maintenance"' # Selective test execution against omnibus instance have following execution scenarios: # * only e2e spec files changed - runs only changed specs diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue index 692ca6bf59b..2b38f5735d4 100644 --- a/app/assets/javascripts/boards/components/board_card_inner.vue +++ b/app/assets/javascripts/boards/components/board_card_inner.vue @@ -288,7 +288,7 @@ export default { <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-5" /> <span v-if="item.referencePath && !isLoading" - class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3 gl-font-sm gl-text-secondary" + class="board-card-number gl-overflow-hidden gl-display-flex gl-gap-2 gl-mr-3 gl-mt-3 gl-font-sm gl-text-secondary" :class="{ 'gl-font-base': isEpicBoard }" > <work-item-type-icon diff --git a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue index f60f00be368..a7b3f5536a4 100644 --- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue @@ -62,7 +62,7 @@ export default { tokensCE() { const { issue, incident } = this.$options.i18n; const { types } = this.$options; - const { fetchUsers, fetchLabels, fetchMilestones } = issueBoardFilters( + const { fetchUsers, fetchLabels } = issueBoardFilters( this.$apollo, this.fullPath, this.isGroupBoard, @@ -148,7 +148,8 @@ export default { token: MilestoneToken, unique: true, shouldSkipSort: true, - fetchMilestones, + isProject: !this.isGroupBoard, + fullPath: this.fullPath, }, { icon: 'issues', diff --git a/app/assets/javascripts/boards/issue_board_filters.js b/app/assets/javascripts/boards/issue_board_filters.js index 8a487822198..ba5da70c6ec 100644 --- a/app/assets/javascripts/boards/issue_board_filters.js +++ b/app/assets/javascripts/boards/issue_board_filters.js @@ -1,7 +1,5 @@ import { BoardType } from 'ee_else_ce/boards/constants'; import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql'; -import groupBoardMilestonesQuery from './graphql/group_board_milestones.query.graphql'; -import projectBoardMilestonesQuery from './graphql/project_board_milestones.query.graphql'; import boardLabels from './graphql/board_labels.query.graphql'; export default function issueBoardFilters(apollo, fullPath, isGroupBoard) { @@ -34,27 +32,8 @@ export default function issueBoardFilters(apollo, fullPath, isGroupBoard) { .then(transformLabels); }; - const fetchMilestones = (searchTerm) => { - const variables = { - fullPath, - searchTerm, - }; - - const query = isGroupBoard ? groupBoardMilestonesQuery : projectBoardMilestonesQuery; - - return apollo - .query({ - query, - variables, - }) - .then(({ data }) => { - return data.workspace?.milestones.nodes; - }); - }; - return { fetchLabels, fetchUsers, - fetchMilestones, }; } diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 8a8895e55a9..3d8ed3af816 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -99,7 +99,6 @@ import { import eventHub from '../eventhub'; import reorderIssuesMutation from '../queries/reorder_issues.mutation.graphql'; import searchLabelsQuery from '../queries/search_labels.query.graphql'; -import searchMilestonesQuery from '../queries/search_milestones.query.graphql'; import setSortPreferenceMutation from '../queries/set_sort_preference.mutation.graphql'; import { convertToApiParams, @@ -405,9 +404,10 @@ export default { title: TOKEN_TITLE_MILESTONE, icon: 'clock', token: MilestoneToken, - fetchMilestones: this.fetchMilestones, recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-milestone`, shouldSkipSort: true, + fullPath: this.fullPath, + isProject: this.isProject, }, { type: TOKEN_TYPE_LABEL, @@ -634,14 +634,6 @@ export default { fetchLatestLabels(search) { return this.fetchLabelsWithFetchPolicy(search, fetchPolicies.NETWORK_ONLY); }, - fetchMilestones(search) { - return this.$apollo - .query({ - query: searchMilestonesQuery, - variables: { fullPath: this.fullPath, search, isProject: this.isProject }, - }) - .then(({ data }) => data[this.namespace]?.milestones.nodes); - }, fetchUsers(search) { return this.$apollo .query({ diff --git a/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql index 040240cde99..941e71b7ca7 100644 --- a/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql +++ b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql @@ -1,6 +1,11 @@ #import "./milestone.fragment.graphql" -query searchMilestones($fullPath: ID!, $search: String, $isProject: Boolean = false) { +query searchMilestones( + $fullPath: ID! + $search: String + $isProject: Boolean = false + $state: MilestoneStateEnum +) { group(fullPath: $fullPath) @skip(if: $isProject) { id milestones( @@ -8,7 +13,7 @@ query searchMilestones($fullPath: ID!, $search: String, $isProject: Boolean = fa includeAncestors: true includeDescendants: true sort: EXPIRED_LAST_DUE_DATE_ASC - state: active + state: $state ) { nodes { ...Milestone @@ -21,7 +26,7 @@ query searchMilestones($fullPath: ID!, $search: String, $isProject: Boolean = fa searchTitle: $search includeAncestors: true sort: EXPIRED_LAST_DUE_DATE_ASC - state: active + state: $state ) { nodes { ...Milestone diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue index 8322fe92de4..77108ad3628 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue @@ -2,6 +2,8 @@ import { GlFilteredSearchSuggestion } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { __ } from '~/locale'; +import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants'; +import searchMilestonesQuery from '~/issues/list/queries/search_milestones.query.graphql'; import { sortMilestonesByDueDate } from '~/milestones/utils'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; import { stripQuotes } from '~/lib/utils/text_utility'; @@ -36,6 +38,14 @@ export default { defaultMilestones() { return this.config.defaultMilestones || DEFAULT_MILESTONES; }, + namespace() { + return this.config.isProject ? WORKSPACE_PROJECT : WORKSPACE_GROUP; + }, + fetchMilestonesQuery() { + return this.config.fetchMilestones + ? this.config.fetchMilestones + : this.fetchMilestonesBySearchTerm; + }, }, methods: { getActiveMilestone(milestones, data) { @@ -51,10 +61,17 @@ export default { ) || this.defaultMilestones.find(({ value }) => value === data) ); }, + fetchMilestonesBySearchTerm(search) { + return this.$apollo + .query({ + query: searchMilestonesQuery, + variables: { fullPath: this.config.fullPath, search, isProject: this.config.isProject }, + }) + .then(({ data }) => data[this.namespace]?.milestones.nodes); + }, fetchMilestones(searchTerm) { this.loading = true; - this.config - .fetchMilestones(searchTerm) + this.fetchMilestonesQuery(searchTerm) .then((response) => { const data = Array.isArray(response) ? response : response.data; diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue index 324228111d4..947a42d4184 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue @@ -251,6 +251,7 @@ export default { <div data-testid="issuable-title" class="issue-title title"> <work-item-type-icon v-if="showWorkItemTypeIcon" + class="gl-mr-2" :work-item-type="type" show-tooltip-on-hover /> diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue index fadd56fc697..c4b92454ac0 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue @@ -192,9 +192,8 @@ export default { </span> <work-item-type-icon v-if="shouldShowWorkItemTypeIcon" - class="gl-m-0!" show-text - :work-item-type="issuableType.toUpperCase()" + :work-item-type="issuableType" /> <gl-sprintf :message="createdMessage"> <template #timeAgo> diff --git a/app/assets/javascripts/work_items/components/work_item_created_updated.vue b/app/assets/javascripts/work_items/components/work_item_created_updated.vue index 60a74b9cdeb..14e55134048 100644 --- a/app/assets/javascripts/work_items/components/work_item_created_updated.vue +++ b/app/assets/javascripts/work_items/components/work_item_created_updated.vue @@ -90,7 +90,7 @@ export default { hide-text-in-small-screens /> <work-item-type-icon - class="gl-vertical-align-middle gl-mr-0!" + class="gl-vertical-align-middle" :work-item-icon-name="workItemIconName" :work-item-type="workItemType" show-text diff --git a/app/assets/javascripts/work_items/components/work_item_type_icon.vue b/app/assets/javascripts/work_items/components/work_item_type_icon.vue index f27ae5f4e6d..5426f3965b3 100644 --- a/app/assets/javascripts/work_items/components/work_item_type_icon.vue +++ b/app/assets/javascripts/work_items/components/work_item_type_icon.vue @@ -53,7 +53,7 @@ export default { </script> <template> - <span class="gl-mr-2"> + <span> <gl-icon v-gl-tooltip.hover="showTooltipOnHover" :name="iconName" diff --git a/config/feature_flags/development/merge_trains_create_ref_service.yml b/config/feature_flags/development/merge_trains_create_ref_service.yml index cd649589a93..cdbe6813210 100644 --- a/config/feature_flags/development/merge_trains_create_ref_service.yml +++ b/config/feature_flags/development/merge_trains_create_ref_service.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420161 milestone: '16.3' type: development group: group::pipeline execution -default_enabled: false +default_enabled: true diff --git a/doc/administration/settings/instance_template_repository.md b/doc/administration/settings/instance_template_repository.md index bb48a7411f5..510c88e7738 100644 --- a/doc/administration/settings/instance_template_repository.md +++ b/doc/administration/settings/instance_template_repository.md @@ -32,6 +32,9 @@ After you add templates, you can use them for the entire instance. They are available in the [Web Editor](../../user/project/repository/web_editor.md) and through the [API settings](../../api/settings.md). +These templates cannot be used as a value of the +[`include:template`](../../ci/yaml/index.md#includetemplate) key in `.gitlab-ci.yml`. + ## Supported file types and locations Templates must be added to a specific subdirectory in the repository, diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md index a764a59e869..8706e605c51 100644 --- a/doc/api/api_resources.md +++ b/doc/api/api_resources.md @@ -158,6 +158,7 @@ The following API resources are available outside of project and group contexts | [Code snippets](snippets.md) | `/snippets` | | [Code Suggestions](code_suggestions.md) | `/code_suggestions` | | [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | +| [Dependency list exports](dependency_list_export.md) **(ULTIMATE ALL)** | `/pipelines/:id/dependency_list_exports`, `/projects/:id/dependency_list_exports`, `/groups/:id/dependency_list_exports`, `/security/dependency_list_exports/:id`, `/security/dependency_list_exports/:id/download` | | [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | | [Deploy tokens](deploy_tokens.md) | `/deploy_tokens` (also available for projects and groups) | | [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | diff --git a/doc/api/dependency_list_export.md b/doc/api/dependency_list_export.md new file mode 100644 index 00000000000..79bf5f1a68f --- /dev/null +++ b/doc/api/dependency_list_export.md @@ -0,0 +1,149 @@ +--- +stage: Govern +group: Threat Insights +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Dependency list export API **(ULTIMATE ALL)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333463) in GitLab 16.4. + +Every call to this endpoint requires authentication. + +## Create a pipeline-level dependency list export + +Create a new CycloneDX JSON export for all the project dependencies detected in a pipeline. + +If an authenticated user doesn't have permission to +[read_dependency](../user/permissions.md#custom-role-requirements), +this request returns a `403 Forbidden` status code. + +SBOM exports can be only accessed by the export's author. + +```plaintext +POST /pipelines/:id/dependency_list_exports +``` + +| Attribute | Type | Required | Description | +| ------------------- | ----------------- | ---------- | -----------------------------------------------------------------------------------------------------------------------------| +| `id` | integer | yes | The ID of the pipeline which the authenticated user has access to. | +| `export_type` | string | yes | This must be set to `sbom`. | + +```shell +curl --request POST --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/pipelines/1/dependency_list_exports" --data "export_type=sbom" +``` + +The created dependency list export is automatically deleted after 1 hour. + +Example response: + +```json +{ + "id": 2, + "has_finished": false, + "self": "http://gitlab.example.com/api/v4/dependency_list_exports/2", + "download": "http://gitlab.example.com/api/v4/dependency_list_exports/2/download" +} +``` + +## Get single dependency list export + +Get a single dependency list export. + +```plaintext +GET /security/dependency_list_exports/:id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the dependency list export. | + +```shell +curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/security/dependency_list_exports/2" +``` + +The status code is `202 Accepted` when the dependency list export is being generated, and `200 OK` when it's ready. + +Example response: + +```json +{ + "id": 4, + "has_finished": true, + "self": "http://gitlab.example.com/api/v4/dependency_list_exports/4", + "download": "http://gitlab.example.com/api/v4/dependency_list_exports/4/download" +} +``` + +## Download dependency list export + +Download a single dependency list export. + +```plaintext +GET /security/dependency_list_exports/:id/download +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the dependency list export. | + +```shell +curl --header "PRIVATE-TOKEN: <private_token>" "https://gitlab.example.com/api/v4/security/dependency_list_exports/2/download" +``` + +The response is `404 Not Found` if the dependency list export is not finished yet or was not found. + +Example response: + +```json +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "serialNumber": "urn:uuid:aec33827-20ae-40d0-ae83-18ee846364d2", + "version": 1, + "metadata": { + "tools": [ + { + "vendor": "Gitlab", + "name": "Gemnasium", + "version": "2.34.0" + } + ], + "authors": [ + { + "name": "Gitlab", + "email": "support@gitlab.com" + } + ], + "properties": [ + { + "name": "gitlab:dependency_scanning:input_file", + "value": "package-lock.json" + } + ] + }, + "components": [ + { + "name": "com.fasterxml.jackson.core/jackson-core", + "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.9.2", + "version": "2.9.2", + "type": "library", + "licenses": [ + { + "license": { + "id": "MIT", + "url": "https://spdx.org/licenses/MIT.html" + } + }, + { + "license": { + "id": "BSD-3-Clause", + "url": "https://spdx.org/licenses/BSD-3-Clause.html" + } + } + ] + } + ] +} + +``` diff --git a/doc/development/code_suggestions/index.md b/doc/development/code_suggestions/index.md index 9b94e9020ba..38fd6200ace 100644 --- a/doc/development/code_suggestions/index.md +++ b/doc/development/code_suggestions/index.md @@ -36,3 +36,21 @@ This should enable everyone to see locally any change in an IDE being sent to th 1. LOG_FORMAT_JSON=false 1. LOG_TO_FILE=true 1. Watch the new log file ```modelgateway_debug.log``` , e.g. ```tail -f modelgateway_debug.log | fblog -a prefix -a suffix -a current_file_name -a suggestion -a language -a input -a parameters -a score -a exception``` + +### Setup instructions to use staging Model Gateway + +When testing interactions with the Model Gateway, you might want to integrate your local GDK +with the deployed staging Model Gateway. To do this: + +1. You need a [cloud staging license](../../user/project/repository/code_suggestions/self_managed.md#update-gitlab) that has the Code Suggestions add-on, because add-ons are enabled on staging. Drop a note in the `#s_fulfillment` internal Slack channel to request an add-on to your license. See this [handbook page](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee-developer-licenses) for how to request a license for local development. +1. Set env variables to point customers-dot to staging, and the Model Gateway to staging: + + ```shell + export GITLAB_LICENSE_MODE=test + export CUSTOMER_PORTAL_URL=https://customers.staging.gitlab.com + export CODE_SUGGESTIONS_BASE_URL=https://codesuggestions.staging.gitlab.com + ``` + +1. Restart the GDK. +1. Ensure you followed the necessary [steps to enable the Code Suggestions feature](../../user/project/repository/code_suggestions/self_managed.md#gitlab-163-and-later). +1. Test out the Code Suggestions feature by opening the Web IDE for a project. diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md index f4cb1d7412d..41db9e296f6 100644 --- a/doc/development/database/multiple_databases.md +++ b/doc/development/database/multiple_databases.md @@ -11,6 +11,9 @@ To allow GitLab to scale further we The two databases are `main` and `ci`. GitLab supports being run with either one database or two databases. On GitLab.com we are using two separate databases. +For the purpose of building the [Cells](../../architecture/blueprints/cells/index.md) architecture, we are decomposing +the databases further, to introduce another database `gitlab_main_clusterwide`. + ## GitLab Schema For properly discovering allowed patterns between different databases @@ -23,17 +26,22 @@ that we cannot use PostgreSQL schema due to complex migration procedures. Instea the concept of application-level classification. Each table of GitLab needs to have a `gitlab_schema` assigned: -- `gitlab_main`: describes all tables that are being stored in the `main:` database (for example, like `projects`, `users`). -- `gitlab_ci`: describes all CI tables that are being stored in the `ci:` database (for example, `ci_pipelines`, `ci_builds`). -- `gitlab_geo`: describes all Geo tables that are being stored in the `geo:` database (for example, like `project_registry`, `secondary_usage_data`). -- `gitlab_shared`: describes all application tables that contain data across all decomposed databases (for example, `loose_foreign_keys_deleted_records`) for models that inherit from `Gitlab::Database::SharedModel`. -- `gitlab_internal`: describes all internal tables of Rails and PostgreSQL (for example, `ar_internal_metadata`, `schema_migrations`, `pg_*`). -- `gitlab_pm`: describes all tables that store `package_metadata` (it is an alias for `gitlab_main`). -- `...`: more schemas to be introduced with additional decomposed databases +| Database | Description | Notes | +| -------- | ----------- | ------- | +| `gitlab_main`| All tables that are being stored in the `main:` database (for example, `projects` and `users`) | Currently, this is being replaced with `gitlab_main_cell`, for the purpose of building the [Cells](../../architecture/blueprints/cells/index.md) architecture. `gitlab_main_cell` schema describes all tables that are local to a cell in a GitLab installation. | +| `gitlab_main_clusterwide` | All tables that are being stored cluster-wide in a GitLab installation, in the [Cells](../../architecture/blueprints/cells/index.md) architecture. | | +| `gitlab_ci` | All CI tables that are being stored in the `ci:` database (for example, `ci_pipelines`, `ci_builds`) | | +| `gitlab_geo` | All Geo tables that are being stored in the `geo:` database (for example, like `project_registry`, `secondary_usage_data`) | | +| `gitlab_shared` | All application tables that contain data across all decomposed databases (for example, `loose_foreign_keys_deleted_records`) for models that inherit from `Gitlab::Database::SharedModel`. | | +| `gitlab_internal` | All internal tables of Rails and PostgreSQL (for example, `ar_internal_metadata`, `schema_migrations`, `pg_*`) | | +| `gitlab_pm` | All tables that store `package_metadata`| It is an alias for `gitlab_main`| + +More schemas to be introduced with additional decomposed databases The usage of schema enforces the base class to be used: -- `ApplicationRecord` for `gitlab_main` +- `ApplicationRecord` for `gitlab_main`/`gitlab_main_cell.` +- `MainClusterwide::ApplicationRecord` for `gitlab_main_clusterwide`. - `Ci::ApplicationRecord` for `gitlab_ci` - `Geo::TrackingBase` for `gitlab_geo` - `Gitlab::Database::SharedModel` for `gitlab_shared` @@ -465,6 +473,20 @@ You can see a real example of using this method for fixing a cross-join in #### Allowlist for existing cross-joins +The easiest way of identifying a cross-join is via failing pipelines. + +As an example, in [!130038](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130038/diffs) we moved the `notification_settings` table to the `gitlab_main_cell` schema, by marking it as such in the `db/docs/notification_settings.yml` file. + +The pipeline failed with the following [error](https://gitlab.com/gitlab-org/gitlab/-/jobs/4929130983): + +```ruby +Database::PreventCrossJoins::CrossJoinAcrossUnsupportedTablesError: + +Unsupported cross-join across 'users, notification_settings' querying 'gitlab_main_clusterwide, gitlab_main_cell' discovered when executing query 'SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT "notification_settings"."user_id" FROM ((SELECT "notification_settings"."user_id" FROM "notification_settings" WHERE "notification_settings"."source_id" = 119 AND "notification_settings"."source_type" = 'Project' AND (("notification_settings"."level" = 3 AND EXISTS (SELECT true FROM "notification_settings" "notification_settings_2" WHERE "notification_settings_2"."user_id" = "notification_settings"."user_id" AND "notification_settings_2"."source_id" IS NULL AND "notification_settings_2"."source_type" IS NULL AND "notification_settings_2"."level" = 2)) OR "notification_settings"."level" = 2))) notification_settings)' +``` + +To make the pipeline green, this cross-join query must be allow-listed. + A cross-join across databases can be explicitly allowed by wrapping the code in the `::Gitlab::Database.allow_cross_joins_across_databases` helper method. Alternative way is to mark a given relation as `relation.allow_cross_joins_across_databases`. @@ -554,7 +576,42 @@ more information, look at the [transaction guidelines](transaction_guidelines.md#dangerous-example-third-party-api-calls) page. -#### Fixing cross-database errors +#### Fixing cross-database transactions + +A transaction across databases can be explicitly allowed by wrapping the code in the +`Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction` helper method. + +For cross-database transactions in Rails callbacks, the `cross_database_ignore_tables` method can be used. + +These methods should only be used for existing code. + +The `temporary_ignore_tables_in_transaction` helper method can be used as follows: + +```ruby +class GroupMember < Member + def update_two_factor_requirement + return unless user + + # To mark and ignore cross-database transactions involving members and users/user_details/user_preferences + Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.temporary_ignore_tables_in_transaction( + %w[users user_details user_preferences], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424288' + ) do + user.update_two_factor_requirement + end + end +end +``` + +The `cross_database_ignore_tables` method can be used as follows: + +```ruby +class Namespace < ApplicationRecord + include CrossDatabaseIgnoredTables + + # To mark and ignore cross-database transactions involving namespaces and routes/redirect_routes happening within Rails callbacks. + cross_database_ignore_tables %w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424277' +end +``` ##### Removing the transaction block @@ -640,6 +697,23 @@ or records that point to nowhere, which might lead to bugs. As such we created ["loose foreign keys"](loose_foreign_keys.md) which is an asynchronous process of cleaning up orphaned records. +### Allowlist for existing cross-database foreign keys + +The easiest way of identifying a cross-database foreign key is via failing pipelines. + +As an example, in [!130038](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130038/diffs) we moved the `notification_settings` table to the `gitlab_main_cell` schema, by marking it in the `db/docs/notification_settings.yml` file. + +`notification_settings.user_id` is a column that points to `users`, but the `users` table belongs to a different database, thus this is now treated as a cross-database foreign key. + +We have a spec to capture such cases of cross-database foreign keys in [`no_cross_db_foreign_keys_spec.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/01d3a1e41513200368a22bbab5d4312174762ee0/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb), which would fail if such a cross-database foreign key is encountered. + +To make the pipeline green, this cross-database foreign key must be allow-listed. + +To do this, explicitly allow the existing cross-database foreign key to exist by adding it as an exception in the same spec (as in [this example](https://gitlab.com/gitlab-org/gitlab/-/blob/7d99387f399c548af24d93d564b35f2f9510662d/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb#L26)). +This way, the spec will not fail. + +Later, this foreign key can be converted to a loose foreign key, like we did in [!130080](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130080/diffs). + ## Testing for multiple databases In our testing CI pipelines, we test GitLab by default with multiple databases set up, using diff --git a/doc/tutorials/export_sbom.md b/doc/tutorials/export_sbom.md new file mode 100644 index 00000000000..cbbb1421283 --- /dev/null +++ b/doc/tutorials/export_sbom.md @@ -0,0 +1,95 @@ +--- +stage: Secure +group: Composition Analysis +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Tutorial: Export dependency list in SBOM format **(ULTIMATE ALL)** + +Dependency Scanning output can be exported to the CycloneDX JSON format. + +This tutorial shows you how to generate a CycloneDX JSON SBOM for a pipeline, and then to upload it as a CI job artifact. + +## Prerequisites + +Set up Dependency Scanning. For detailed instructions, follow [the Dependency Scanning tutorial](dependency_scanning.md). + +## Create configuration files + +1. Create a [snippet](../api/snippets.md) with the following code. + + Filename: `export.sh` + + ```shell + #! /bin/sh + + function create_export { + curl --silent \ + --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" \ + -X 'POST' --data "export_type=sbom" \ + "http://gitlab.localdev:3000/api/v4/pipelines/$CI_PIPELINE_ID/dependency_list_exports" \ + | jq '.id' + } + + function check_status { + curl --silent \ + --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" \ + --write-out "%{http_code}" --output /dev/null \ + http://gitlab.localdev:3000/api/v4/dependency_list_exports/$1 + } + + function download { + curl --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" \ + --output "gl-sbom-merged-$CI_PIPELINE_ID.cdx.json" \ + "http://gitlab.localdev:3000/api/v4/dependency_list_exports/$1/download" + } + + function export_sbom { + local ID=$(create_export) + + for run in $(seq 0 3); do + local STATUS=$(check_status $ID) + # Status is 200 when JSON is generated. + # Status is 202 when generate JSON job is running. + if [ $STATUS -eq "200" ]; then + download $ID + + exit 0 + elif [ $STATUS -ne "202" ]; then + exit 1 + fi + + echo "Waiting for JSON to be generated" + sleep 5 + done + + exit 1 + } + + export_sbom + ``` + + The above script works in the following steps: + + 1. Create a CycloneDX SBOM export for the current pipeline. + 1. Check the status of that export, and stop when it's ready. + 1. Download the CycloneDX SBOM file. + +1. Update `.gitlab-ci.yml` with the following code. + + ```yaml + export-merged-sbom: + before_script: + - apk add --update jq curl + stage: .post + script: + - curl --output export.sh --url "https://gitlab.example.com/api/v4/snippets/<SNIPPET_ID>/raw" + - /bin/sh export.sh + artifacts: + paths: + - "gl-sbom-merged-*.cdx.json" + ``` + +1. Go to **Build > Pipelines** and confirm that the latest pipeline completed successfully. + +In the job artifacts, `gl-sbom-merged-<pipeline_id>.cdx.json` file should be present. diff --git a/doc/tutorials/secure_application.md b/doc/tutorials/secure_application.md index 54235d0a6dc..96a4107e36c 100644 --- a/doc/tutorials/secure_application.md +++ b/doc/tutorials/secure_application.md @@ -11,6 +11,7 @@ GitLab can check your application for security vulnerabilities and that it meets | Topic | Description | Good for beginners | |-------|-------------|--------------------| | [Set up dependency scanning](dependency_scanning.md) | Learn how to detect vulnerabilities in an application's dependencies. | **{star}** | +| [Export Dependency List in SBOM format](export_sbom.md) | Learn how to export an application's dependencies to the CycloneDX SBOM format. | **{star}** | | [Create a compliance pipeline](compliance_pipeline/index.md) | Learn how to create compliance pipelines for your groups. | **{star}** | | [Set up a scan result policy](scan_result_policy/index.md) | Learn how to configure a scan result policy that takes action based on scan results. | **{star}** | | [Scan a Docker container for vulnerabilities](container_scanning/index.md) | Learn how to use container scanning templates to add container scanning to your projects. | **{star}** | diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md index 481fc774885..dbad5ff9c11 100644 --- a/doc/user/application_security/dependency_list/index.md +++ b/doc/user/application_security/dependency_list/index.md @@ -117,4 +117,10 @@ You can download your group's or project's list of dependencies and their detail ### Using the API +#### List project Dependencies + You can download your project's list of dependencies [using the API](../../../api/dependencies.md#list-project-dependencies). Note this only provides the dependencies identified by the [Gemnasium family of analyzers](../dependency_scanning/index.md#dependency-analyzers) and not any other of the GitLab dependency analyzers. + +#### Export pipeline dependency list + +You can download your project's list of dependencies identified in a pipeline [using the API](../../../api/dependency_list_export.md). diff --git a/doc/user/clusters/agent/ci_cd_workflow.md b/doc/user/clusters/agent/ci_cd_workflow.md index 6c7f43e2991..a1281d3d75c 100644 --- a/doc/user/clusters/agent/ci_cd_workflow.md +++ b/doc/user/clusters/agent/ci_cd_workflow.md @@ -15,9 +15,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - The ability to switch between certificate-based clusters and agents was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335089) in GitLab 14.9. The certificate-based cluster context is always called `gitlab-deploy`. > - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80508) from _CI/CD tunnel_ to _CI/CD workflow_ in GitLab 14.9. -You can use a GitLab CI/CD workflow to safely deploy to and update your Kubernetes clusters. +You can use GitLab CI/CD to safely connect, deploy, and update your Kubernetes clusters. -To do so, you must first [install an agent in your cluster](install/index.md). When done, you have a Kubernetes context and can +To do so, [install an agent in your cluster](install/index.md). When done, you have a Kubernetes context and can run Kubernetes API commands in your GitLab CI/CD pipeline. To ensure access to your cluster is safe: @@ -25,11 +25,11 @@ To ensure access to your cluster is safe: - Each agent has a separate context (`kubecontext`). - Only the project where the agent is configured, and any additional projects you authorize, can access the agent in your cluster. -The CI/CD workflow requires runners to be registered with GitLab, but these runners do not have to be in the cluster where the agent is. +To use GitLab CI/CD to interact with your cluster, runners must be registered with GitLab. However, these runners do not have to be in the cluster where the agent is. -## GitLab CI/CD workflow steps +## Use GitLab CI/CD with your cluster -To update a Kubernetes cluster by using GitLab CI/CD, complete the following steps. +To update a Kubernetes cluster with GitLab CI/CD: 1. Ensure you have a working Kubernetes cluster and the manifests are in a GitLab project. 1. In the same GitLab project, [register and install the GitLab agent](install/index.md). diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md index fb6d9c10f4c..d620a9f658c 100644 --- a/doc/user/clusters/agent/install/index.md +++ b/doc/user/clusters/agent/install/index.md @@ -43,7 +43,7 @@ To install the agent in your cluster: For configuration settings, the agent uses a YAML file in the GitLab project. You must create this file if: - You use [a GitOps workflow](../gitops/agent.md#gitops-workflow-steps). -- You use [a GitLab CI/CD workflow](../ci_cd_workflow.md#gitlab-cicd-workflow-steps) and want to authorize a different project to use the agent. +- You use [a GitLab CI/CD workflow](../ci_cd_workflow.md#use-gitlab-cicd-with-your-cluster) and want to authorize a different project to use the agent. - You [allow specific project or group members to access Kubernetes](../user_access.md). To create an agent configuration file: diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md index 491ae1ec528..87c1c548abd 100644 --- a/doc/user/group/custom_project_templates.md +++ b/doc/user/group/custom_project_templates.md @@ -69,6 +69,35 @@ gitlab.com/myorganization/ ... ``` +## What is copied from the templates + +The entire custom instance-level project templates repository is copied, including: + +- Branches +- Commits +- Tags + +If the user: + +- Has the Owner role on the custom instance-level project templates project or is a GitLab administrator, + all project settings are copied over to the new project. +- Doesn't have the Owner role or is not a GitLab administrator, + project deploy keys and project webhooks aren't copied over because they contain sensitive data. + +To learn more about what is migrated, see +[Items that are exported](../project/settings/import_export.md#items-that-are-exported). + +## User assignments in templates + +When you use a template created by another user, any items that were assigned +to a user in the template are reassigned to you. It's important to understand +this reassignment when you configure security features like protected branches +and tags. For example, if the template contains a protected branch: + +- In the template, the branch allows the _template owner_ to merge into the default branch. +- In the project created from the template, the branch allows _you_ to merge into + the default branch. + <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9ac4b89e6af..879f03519b0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -41427,6 +41427,9 @@ msgstr "" msgid "ScanResultPolicy|Attributes are automatically applied by the scanners" msgstr "" +msgid "ScanResultPolicy|Block users from modifying protected branches" +msgstr "" + msgid "ScanResultPolicy|Choose criteria type" msgstr "" @@ -41493,12 +41496,21 @@ msgstr "" msgid "ScanResultPolicy|Pre-existing" msgstr "" -msgid "ScanResultPolicy|Prevent branch protection modification" +msgid "ScanResultPolicy|Prevent approval by anyone who added a commit" +msgstr "" + +msgid "ScanResultPolicy|Prevent approval by merge request's author" msgstr "" msgid "ScanResultPolicy|Protected branch settings" msgstr "" +msgid "ScanResultPolicy|Remove all approvals when commit is added" +msgstr "" + +msgid "ScanResultPolicy|Require the user's password to approve" +msgstr "" + msgid "ScanResultPolicy|Select a scan type before adding criteria" msgstr "" @@ -41532,6 +41544,9 @@ msgstr "" msgid "ScanResultPolicy|When %{scanners} find scanner specified conditions in an open merge request targeting the %{branches} %{branchExceptions} and match %{boldDescription} of the following criteria" msgstr "" +msgid "ScanResultPolicy|When enabled, two person approval will be required on all MRs as merge request authors cannot approve their own MRs and merge them unilaterally" +msgstr "" + msgid "ScanResultPolicy|any commits" msgstr "" diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js index 5b5b68d5dbe..16ad54f0854 100644 --- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js +++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js @@ -61,12 +61,7 @@ describe('IssueBoardFilter', () => { ({ isSignedIn }) => { createComponent({ isSignedIn }); - const tokens = mockTokens( - fetchLabelsSpy, - fetchUsersSpy, - wrapper.vm.fetchMilestones, - isSignedIn, - ); + const tokens = mockTokens(fetchLabelsSpy, fetchUsersSpy, isSignedIn); expect(findBoardsFilteredSearch().props('tokens')).toEqual(orderBy(tokens, ['title'])); }, diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 8f57a6eb7da..dfcdb4c05d0 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -827,7 +827,7 @@ export const mockConfidentialToken = { ], }; -export const mockTokens = (fetchLabels, fetchUsers, fetchMilestones, isSignedIn) => [ +export const mockTokens = (fetchLabels, fetchUsers, isSignedIn) => [ { icon: 'user', title: TOKEN_TITLE_ASSIGNEE, @@ -870,7 +870,8 @@ export const mockTokens = (fetchLabels, fetchUsers, fetchMilestones, isSignedIn) shouldSkipSort: true, token: MilestoneToken, unique: true, - fetchMilestones, + fullPath: 'gitlab-org', + isProject: false, }, { icon: 'issues', diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index b2f4c780f51..a22ad4c450e 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -84,6 +84,19 @@ export const mockMilestones = [ mockEscapedMilestone, ]; +export const projectMilestonesResponse = { + data: { + project: { + id: 'gid://gitlab/Project/1', + attributes: { + nodes: mockMilestones, + __typename: 'MilestoneConnection', + }, + __typename: 'Project', + }, + }, +}; + export const mockCrmContacts = [ { __typename: 'CustomerRelationsContact', @@ -257,7 +270,8 @@ export const mockMilestoneToken = { symbol: '%', token: MilestoneToken, operators: OPERATORS_IS, - fetchMilestones: () => Promise.resolve({ data: mockMilestones }), + fullPath: 'gitlab-org', + isProject: true, }; export const mockReleaseToken = { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js index db51b4a05b1..36e82b39df4 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js @@ -6,17 +6,27 @@ import { } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { sortMilestonesByDueDate } from '~/milestones/utils'; +import searchMilestonesQuery from '~/issues/list/queries/search_milestones.query.graphql'; import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { mockMilestoneToken, mockMilestones, mockRegularMilestone } from '../mock_data'; +import { + mockMilestoneToken, + mockMilestones, + mockRegularMilestone, + projectMilestonesResponse, +} from '../mock_data'; + +Vue.use(VueApollo); jest.mock('~/alert'); jest.mock('~/milestones/utils'); @@ -31,6 +41,9 @@ const defaultStubs = { }, }; +const milestonesQueryHandler = jest.fn().mockResolvedValue(projectMilestonesResponse); +const mockApollo = createMockApollo([[searchMilestonesQuery, milestonesQueryHandler]]); + function createComponent(options = {}) { const { config = { ...mockMilestoneToken, shouldSkipSort: true }, @@ -39,6 +52,7 @@ function createComponent(options = {}) { stubs = defaultStubs, } = options; return mount(MilestoneToken, { + apolloProvider: mockApollo, propsData: { config, value, @@ -102,6 +116,33 @@ describe('MilestoneToken', () => { }); }); + describe('default - when fetchMilestones function is not provided in config', () => { + beforeEach(() => { + wrapper = createComponent({}); + return triggerFetchMilestones(); + }); + + it('calls searchMilestonesQuery to fetch milestones', () => { + expect(milestonesQueryHandler).toHaveBeenCalledWith({ + fullPath: mockMilestoneToken.fullPath, + isProject: mockMilestoneToken.isProject, + search: null, + }); + }); + + it('calls searchMilestonesQuery with search parameter when provided', async () => { + const searchTerm = 'foo'; + + await triggerFetchMilestones(searchTerm); + + expect(milestonesQueryHandler).toHaveBeenCalledWith({ + fullPath: mockMilestoneToken.fullPath, + isProject: mockMilestoneToken.isProject, + search: searchTerm, + }); + }); + }); + describe('when request is successful', () => { const searchTerm = 'foo'; diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js index a3d7b244685..7662aa535e3 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js @@ -170,7 +170,7 @@ describe('IssuableHeader component', () => { expect(findWorkItemTypeIcon().props()).toMatchObject({ showText: true, - workItemType: 'ISSUE', + workItemType: 'issue', }); }); |